Improve and unify the indexing between dev and deploy
This commit is contained in:
Vendored
+1
-1
@@ -41,7 +41,7 @@
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"name": "Debug V3 Deploy CLI",
|
||||
"command": "pnpm exec triggerdev deploy --self-hosted",
|
||||
"command": "pnpm exec triggerdev deploy --self-hosted --load-image",
|
||||
"cwd": "${workspaceFolder}/references/v3-catalog",
|
||||
"sourceMaps": true
|
||||
},
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import {
|
||||
DeploymentErrorData,
|
||||
ExternalBuildData,
|
||||
TaskMetadataFailedToParseData,
|
||||
groupTaskMetadataIssuesByTask,
|
||||
prepareDeploymentError,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import { WorkerDeployment, WorkerDeploymentStatus } from "@trigger.dev/database";
|
||||
import { z } from "zod";
|
||||
import { WorkerDeployment } from "@trigger.dev/database";
|
||||
import { PrismaClient, prisma } from "~/db.server";
|
||||
import { Organization } from "~/models/organization.server";
|
||||
import { Project } from "~/models/project.server";
|
||||
import { User } from "~/models/user.server";
|
||||
import { safeJsonParse } from "~/utils/json";
|
||||
import { getUsername } from "~/utils/username";
|
||||
|
||||
export type ErrorData = {
|
||||
@@ -164,75 +161,12 @@ export class DeploymentPresenter {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedErrorData = DeploymentErrorData.safeParse(errorData);
|
||||
const deploymentError = DeploymentErrorData.safeParse(errorData);
|
||||
|
||||
if (!parsedErrorData.success) {
|
||||
if (!deploymentError.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsedErrorData.data.name === "TaskMetadataParseError") {
|
||||
const errorJson = safeJsonParse(parsedErrorData.data.stack);
|
||||
|
||||
if (errorJson) {
|
||||
const parsedError = TaskMetadataFailedToParseData.safeParse(errorJson);
|
||||
|
||||
if (parsedError.success) {
|
||||
return {
|
||||
name: parsedErrorData.data.name,
|
||||
message: parsedErrorData.data.message,
|
||||
stack: createTaskMetadataFailedErrorStack(parsedError.data),
|
||||
stderr: parsedErrorData.data.stderr,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: parsedErrorData.data.name,
|
||||
message: parsedErrorData.data.message,
|
||||
stderr: parsedErrorData.data.stderr,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
name: parsedErrorData.data.name,
|
||||
message: parsedErrorData.data.message,
|
||||
stderr: parsedErrorData.data.stderr,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: parsedErrorData.data.name,
|
||||
message: parsedErrorData.data.message,
|
||||
stack: parsedErrorData.data.stack,
|
||||
stderr: parsedErrorData.data.stderr,
|
||||
};
|
||||
return prepareDeploymentError(deploymentError.data);
|
||||
}
|
||||
}
|
||||
|
||||
function createTaskMetadataFailedErrorStack(
|
||||
data: z.infer<typeof TaskMetadataFailedToParseData>
|
||||
): string {
|
||||
const stack = [];
|
||||
|
||||
const groupedIssues = groupTaskMetadataIssuesByTask(data.tasks, data.zodIssues);
|
||||
|
||||
for (const key in groupedIssues) {
|
||||
const taskWithIssues = groupedIssues[key];
|
||||
|
||||
if (!taskWithIssues) {
|
||||
continue;
|
||||
}
|
||||
|
||||
stack.push("\n");
|
||||
stack.push(` ❯ ${taskWithIssues.exportName} in ${taskWithIssues.filePath}`);
|
||||
|
||||
for (const issue of taskWithIssues.issues) {
|
||||
if (issue.path) {
|
||||
stack.push(` x ${issue.path} ${issue.message}`);
|
||||
} else {
|
||||
stack.push(` x ${issue.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stack.join("\n");
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
"resolve": "^1.22.8",
|
||||
"signal-exit": "^4.1.0",
|
||||
"source-map-support": "0.5.21",
|
||||
"std-env": "^3.7.0",
|
||||
"terminal-link": "^3.0.0",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"tinyexec": "^0.2.0",
|
||||
|
||||
@@ -9,11 +9,12 @@ import {
|
||||
deployEntryPoints,
|
||||
devEntryPoints,
|
||||
isConfigEntryPoint,
|
||||
isExecutorEntryPointForTarget,
|
||||
isIndexerEntryPointForTarget,
|
||||
isRunWorkerForTarget,
|
||||
isIndexWorkerForTarget,
|
||||
isLoaderEntryPoint,
|
||||
isWorkerEntryPointForTarget,
|
||||
isRunControllerForTarget,
|
||||
shims,
|
||||
isIndexControllerForTarget,
|
||||
} from "./packageModules.js";
|
||||
import { buildPlugins } from "./plugins.js";
|
||||
|
||||
@@ -34,9 +35,10 @@ export type BundleResult = {
|
||||
files: TaskFile[];
|
||||
configPath: string;
|
||||
loaderEntryPoint: string | undefined;
|
||||
workerEntryPoint: string | undefined;
|
||||
indexerEntryPoint: string | undefined;
|
||||
executorEntryPoint: string | undefined;
|
||||
runWorkerEntryPoint: string | undefined;
|
||||
runControllerEntryPoint: string | undefined;
|
||||
indexWorkerEntryPoint: string | undefined;
|
||||
indexControllerEntryPoint: string | undefined;
|
||||
stop: (() => Promise<void>) | undefined;
|
||||
};
|
||||
|
||||
@@ -145,9 +147,10 @@ export async function getBundleResultFromBuild(
|
||||
|
||||
let configPath: string | undefined;
|
||||
let loaderEntryPoint: string | undefined;
|
||||
let workerEntryPoint: string | undefined;
|
||||
let executorEntryPoint: string | undefined;
|
||||
let indexerEntryPoint: string | undefined;
|
||||
let runWorkerEntryPoint: string | undefined;
|
||||
let runControllerEntryPoint: string | undefined;
|
||||
let indexWorkerEntryPoint: string | undefined;
|
||||
let indexControllerEntryPoint: string | undefined;
|
||||
|
||||
for (const [outputPath, outputMeta] of Object.entries(result.metafile.outputs)) {
|
||||
if (outputPath.endsWith(".mjs")) {
|
||||
@@ -161,12 +164,14 @@ export async function getBundleResultFromBuild(
|
||||
configPath = $outputPath;
|
||||
} else if (isLoaderEntryPoint(outputMeta.entryPoint)) {
|
||||
loaderEntryPoint = $outputPath;
|
||||
} else if (isWorkerEntryPointForTarget(outputMeta.entryPoint, target)) {
|
||||
workerEntryPoint = $outputPath;
|
||||
} else if (isIndexerEntryPointForTarget(outputMeta.entryPoint, target)) {
|
||||
indexerEntryPoint = $outputPath;
|
||||
} else if (isExecutorEntryPointForTarget(outputMeta.entryPoint, target)) {
|
||||
executorEntryPoint = $outputPath;
|
||||
} else if (isRunControllerForTarget(outputMeta.entryPoint, target)) {
|
||||
runControllerEntryPoint = $outputPath;
|
||||
} else if (isRunWorkerForTarget(outputMeta.entryPoint, target)) {
|
||||
runWorkerEntryPoint = $outputPath;
|
||||
} else if (isIndexControllerForTarget(outputMeta.entryPoint, target)) {
|
||||
indexControllerEntryPoint = $outputPath;
|
||||
} else if (isIndexWorkerForTarget(outputMeta.entryPoint, target)) {
|
||||
indexWorkerEntryPoint = $outputPath;
|
||||
} else {
|
||||
if (
|
||||
!outputMeta.entryPoint.startsWith("..") &&
|
||||
@@ -189,9 +194,10 @@ export async function getBundleResultFromBuild(
|
||||
files,
|
||||
configPath: configPath,
|
||||
loaderEntryPoint,
|
||||
workerEntryPoint,
|
||||
executorEntryPoint,
|
||||
indexerEntryPoint,
|
||||
runWorkerEntryPoint,
|
||||
runControllerEntryPoint,
|
||||
indexWorkerEntryPoint,
|
||||
indexControllerEntryPoint,
|
||||
contentHash: hasher.digest("hex"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,14 @@ export async function copyManifestToDir(
|
||||
|
||||
updatedManifest.configPath = updatedManifest.configPath.replace(source, destination);
|
||||
updatedManifest.loaderEntryPoint = updatedManifest.loaderEntryPoint?.replace(source, destination);
|
||||
updatedManifest.workerEntryPoint = updatedManifest.workerEntryPoint?.replace(source, destination);
|
||||
updatedManifest.runWorkerEntryPoint = updatedManifest.runWorkerEntryPoint.replace(
|
||||
source,
|
||||
destination
|
||||
);
|
||||
updatedManifest.indexWorkerEntryPoint = updatedManifest.indexWorkerEntryPoint.replace(
|
||||
source,
|
||||
destination
|
||||
);
|
||||
|
||||
updatedManifest.files = updatedManifest.files.map((file) => {
|
||||
return {
|
||||
|
||||
@@ -2,20 +2,22 @@ import { join } from "node:path";
|
||||
import { sourceDir } from "../sourceDir.js";
|
||||
import { BuildTarget } from "@trigger.dev/core/v3";
|
||||
|
||||
export const devExecutorEntryPoint = join(sourceDir, "entryPoints", "dev-executor.js");
|
||||
export const devIndexerEntryPoint = join(sourceDir, "entryPoints", "dev-indexer.js");
|
||||
export const devRunWorker = join(sourceDir, "entryPoints", "dev-run-worker.js");
|
||||
export const devIndexWorker = join(sourceDir, "entryPoints", "dev-index-worker.js");
|
||||
|
||||
export const deployEntryPoint = join(sourceDir, "entryPoints", "deploy.js");
|
||||
export const deployExecutorEntryPoint = join(sourceDir, "entryPoints", "deploy-executor.js");
|
||||
export const deployIndexerEntryPoint = join(sourceDir, "entryPoints", "deploy-indexer.js");
|
||||
export const deployRunController = join(sourceDir, "entryPoints", "deploy-run-controller.js");
|
||||
export const deployRunWorker = join(sourceDir, "entryPoints", "deploy-run-worker.js");
|
||||
export const deployIndexController = join(sourceDir, "entryPoints", "deploy-index-controller.js");
|
||||
export const deployIndexWorker = join(sourceDir, "entryPoints", "deploy-index-worker.js");
|
||||
|
||||
export const telemetryEntryPoint = join(sourceDir, "entryPoints", "loader.js");
|
||||
|
||||
export const devEntryPoints = [devExecutorEntryPoint, devIndexerEntryPoint, telemetryEntryPoint];
|
||||
export const devEntryPoints = [devRunWorker, devIndexWorker, telemetryEntryPoint];
|
||||
export const deployEntryPoints = [
|
||||
deployIndexerEntryPoint,
|
||||
deployExecutorEntryPoint,
|
||||
deployEntryPoint,
|
||||
deployRunController,
|
||||
deployRunWorker,
|
||||
deployIndexController,
|
||||
deployIndexWorker,
|
||||
telemetryEntryPoint,
|
||||
];
|
||||
|
||||
@@ -23,41 +25,61 @@ export const esmShimPath = join(sourceDir, "shims", "esm.js");
|
||||
|
||||
export const shims = [esmShimPath];
|
||||
|
||||
function isDevExecutorEntryPoint(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "dev-executor.js"));
|
||||
function isDevRunWorker(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "dev-run-worker.js"));
|
||||
}
|
||||
|
||||
function isDevIndexerEntryPoint(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "dev-indexer.js"));
|
||||
function isDevIndexWorker(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "dev-index-worker.js"));
|
||||
}
|
||||
|
||||
function isDeployIndexerEntryPoint(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "deploy-indexer.js"));
|
||||
function isDeployIndexController(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "deploy-index-controller.js"));
|
||||
}
|
||||
|
||||
function isDeployEntryPoint(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "deploy.js"));
|
||||
function isDeployIndexWorker(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "deploy-index-worker.js"));
|
||||
}
|
||||
|
||||
function isDeployExecutorEntryPoint(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "deploy-executor.js"));
|
||||
function isDeployRunController(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "deploy-run-controller.js"));
|
||||
}
|
||||
|
||||
function isDeployRunWorker(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "deploy-run-worker.js"));
|
||||
}
|
||||
|
||||
export function isLoaderEntryPoint(entryPoint: string) {
|
||||
return entryPoint.includes(join("dist", "esm", "entryPoints", "loader.js"));
|
||||
}
|
||||
|
||||
export function isExecutorEntryPointForTarget(entryPoint: string, target: BuildTarget) {
|
||||
export function isRunWorkerForTarget(entryPoint: string, target: BuildTarget) {
|
||||
if (target === "dev") {
|
||||
return isDevExecutorEntryPoint(entryPoint);
|
||||
return isDevRunWorker(entryPoint);
|
||||
} else {
|
||||
return isDeployExecutorEntryPoint(entryPoint);
|
||||
return isDeployRunWorker(entryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
export function isWorkerEntryPointForTarget(entryPoint: string, target: BuildTarget) {
|
||||
export function isRunControllerForTarget(entryPoint: string, target: BuildTarget) {
|
||||
if (target === "deploy") {
|
||||
return isDeployEntryPoint(entryPoint);
|
||||
return isDeployRunController(entryPoint);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isIndexWorkerForTarget(entryPoint: string, target: BuildTarget) {
|
||||
if (target === "dev") {
|
||||
return isDevIndexWorker(entryPoint);
|
||||
} else {
|
||||
return isDeployIndexWorker(entryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
export function isIndexControllerForTarget(entryPoint: string, target: BuildTarget) {
|
||||
if (target === "deploy") {
|
||||
return isDeployIndexController(entryPoint);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -66,11 +88,3 @@ export function isWorkerEntryPointForTarget(entryPoint: string, target: BuildTar
|
||||
export function isConfigEntryPoint(entryPoint: string) {
|
||||
return entryPoint.startsWith("trigger.config.ts");
|
||||
}
|
||||
|
||||
export function isIndexerEntryPointForTarget(entryPoint: string, target: BuildTarget) {
|
||||
if (target === "dev") {
|
||||
return isDevIndexerEntryPoint(entryPoint);
|
||||
} else {
|
||||
return isDeployIndexerEntryPoint(entryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { intro, outro } from "@clack/prompts";
|
||||
import { CORE_VERSION } from "@trigger.dev/core/v3";
|
||||
import { CORE_VERSION, prepareDeploymentError } from "@trigger.dev/core/v3";
|
||||
import { DEFAULT_RUNTIME, ResolvedConfig } from "@trigger.dev/core/v3/build";
|
||||
import { BuildManifest, InitializeDeploymentResponseBody } from "@trigger.dev/core/v3/schemas";
|
||||
import { Command, Option as CommandOption } from "commander";
|
||||
@@ -16,10 +16,12 @@ import {
|
||||
resolvePluginsForContext,
|
||||
} from "../build/extensions.js";
|
||||
import { createExternalsBuildExtension } from "../build/externals.js";
|
||||
import { getInstrumentedPackageNames } from "../build/instrumentation.js";
|
||||
import {
|
||||
deployEntryPoint,
|
||||
deployExecutorEntryPoint,
|
||||
deployIndexerEntryPoint,
|
||||
deployIndexController,
|
||||
deployIndexWorker,
|
||||
deployRunController,
|
||||
deployRunWorker,
|
||||
telemetryEntryPoint,
|
||||
} from "../build/packageModules.js";
|
||||
import {
|
||||
@@ -39,7 +41,8 @@ import {
|
||||
saveLogs,
|
||||
} from "../deploy/logs.js";
|
||||
import { buildManifestToJSON } from "../utilities/buildManifest.js";
|
||||
import { chalkError, cliLink } from "../utilities/cliOutput.js";
|
||||
import { chalkError, cliLink, prettyError } from "../utilities/cliOutput.js";
|
||||
import { loadDotEnvVars } from "../utilities/dotEnv.js";
|
||||
import { writeJSONFile } from "../utilities/fileSystem.js";
|
||||
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
|
||||
import { logger } from "../utilities/logger.js";
|
||||
@@ -50,8 +53,6 @@ import { spinner } from "../utilities/windows.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { login } from "./login.js";
|
||||
import { updateTriggerPackages } from "./update.js";
|
||||
import { loadDotEnvVars, resolveDotEnvVars } from "../utilities/dotEnv.js";
|
||||
import { getInstrumentedPackageNames } from "../build/instrumentation.js";
|
||||
|
||||
const DeployCommandOptions = CommonCommandOptions.extend({
|
||||
dryRun: z.boolean().default(false),
|
||||
@@ -250,9 +251,10 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
|
||||
dirs: resolvedConfig.dirs,
|
||||
},
|
||||
outputPath: destination.path,
|
||||
workerEntryPoint: bundleResult.workerEntryPoint ?? deployEntryPoint,
|
||||
executorEntryPoint: bundleResult.executorEntryPoint ?? deployExecutorEntryPoint,
|
||||
indexerEntryPoint: bundleResult.indexerEntryPoint ?? deployIndexerEntryPoint,
|
||||
runControllerEntryPoint: bundleResult.runControllerEntryPoint ?? deployRunController,
|
||||
runWorkerEntryPoint: bundleResult.runWorkerEntryPoint ?? deployRunWorker,
|
||||
indexControllerEntryPoint: bundleResult.indexControllerEntryPoint ?? deployIndexController,
|
||||
indexWorkerEntryPoint: bundleResult.indexWorkerEntryPoint ?? deployIndexWorker,
|
||||
loaderEntryPoint: bundleResult.loaderEntryPoint ?? telemetryEntryPoint,
|
||||
configPath: bundleResult.configPath,
|
||||
deploy: {
|
||||
@@ -484,11 +486,14 @@ function rewriteBuildManifestPaths(
|
||||
})),
|
||||
outputPath: rewriteOutputPath(destinationDir, buildManifest.outputPath),
|
||||
configPath: rewriteOutputPath(destinationDir, buildManifest.configPath),
|
||||
executorEntryPoint: rewriteOutputPath(destinationDir, buildManifest.executorEntryPoint),
|
||||
indexerEntryPoint: rewriteOutputPath(destinationDir, buildManifest.indexerEntryPoint),
|
||||
workerEntryPoint: buildManifest.workerEntryPoint
|
||||
? rewriteOutputPath(destinationDir, buildManifest.workerEntryPoint)
|
||||
runControllerEntryPoint: buildManifest.runControllerEntryPoint
|
||||
? rewriteOutputPath(destinationDir, buildManifest.runControllerEntryPoint)
|
||||
: undefined,
|
||||
runWorkerEntryPoint: rewriteOutputPath(destinationDir, buildManifest.runWorkerEntryPoint),
|
||||
indexControllerEntryPoint: buildManifest.indexControllerEntryPoint
|
||||
? rewriteOutputPath(destinationDir, buildManifest.indexControllerEntryPoint)
|
||||
: undefined,
|
||||
indexWorkerEntryPoint: rewriteOutputPath(destinationDir, buildManifest.indexWorkerEntryPoint),
|
||||
loaderEntryPoint: buildManifest.loaderEntryPoint
|
||||
? rewriteOutputPath(destinationDir, buildManifest.loaderEntryPoint)
|
||||
: undefined,
|
||||
@@ -550,15 +555,15 @@ function rewriteOutputPath(destinationDir: string, filePath: string) {
|
||||
}
|
||||
|
||||
async function writeContainerfile(outputPath: string, buildManifest: BuildManifest) {
|
||||
if (!buildManifest.workerEntryPoint) {
|
||||
throw new Error("No worker entry point found in build manifest");
|
||||
if (!buildManifest.runControllerEntryPoint || !buildManifest.indexControllerEntryPoint) {
|
||||
throw new Error("Something went wrong with the build. Aborting deployment. [code 7789]");
|
||||
}
|
||||
|
||||
const containerfile = await generateContainerfile({
|
||||
runtime: buildManifest.runtime,
|
||||
workerEntryPoint: buildManifest.workerEntryPoint,
|
||||
entrypoint: buildManifest.runControllerEntryPoint,
|
||||
build: buildManifest.build,
|
||||
indexerEntryPoint: buildManifest.indexerEntryPoint,
|
||||
indexScript: buildManifest.indexControllerEntryPoint,
|
||||
});
|
||||
|
||||
await writeFile(join(outputPath, "Containerfile"), containerfile);
|
||||
@@ -589,25 +594,94 @@ async function failDeploy(
|
||||
) {
|
||||
$spinner.stop(`Failed to deploy project`);
|
||||
|
||||
// If there are logs, let's write it out to a temporary file and include the path in the error message
|
||||
if (logs.trim() !== "") {
|
||||
const logPath = await saveLogs(deployment.shortCode, logs);
|
||||
const doOutputLogs = async (prefix: string = "Error") => {
|
||||
if (logs.trim() !== "") {
|
||||
const logPath = await saveLogs(deployment.shortCode, logs);
|
||||
|
||||
printWarnings(warnings);
|
||||
printErrors(errors);
|
||||
printWarnings(warnings);
|
||||
printErrors(errors);
|
||||
|
||||
checkLogsForErrors(logs);
|
||||
checkLogsForErrors(logs);
|
||||
|
||||
outro(
|
||||
`${chalkError("Error:")} ${error.message}. Full build logs have been saved to ${logPath}`
|
||||
);
|
||||
outro(
|
||||
`${chalkError(`${prefix}:`)} ${
|
||||
error.message
|
||||
}. Full build logs have been saved to ${logPath}`
|
||||
);
|
||||
} else {
|
||||
outro(`${chalkError(`${prefix}:`)} ${error.message}.`);
|
||||
}
|
||||
};
|
||||
|
||||
const exitCommand = (message: string) => {
|
||||
throw new SkipLoggingError(message);
|
||||
};
|
||||
|
||||
const deploymentResponse = await client.getDeployment(deployment.id);
|
||||
|
||||
if (!deploymentResponse.success) {
|
||||
logger.debug(`Failed to get deployment with worker: ${deploymentResponse.error}`);
|
||||
} else {
|
||||
outro(`${chalkError("Error:")} ${error.message}.`);
|
||||
const serverDeployment = deploymentResponse.data;
|
||||
|
||||
switch (serverDeployment.status) {
|
||||
case "PENDING":
|
||||
case "DEPLOYING":
|
||||
case "BUILDING": {
|
||||
await doOutputLogs();
|
||||
|
||||
await client.failDeployment(deployment.id, {
|
||||
error,
|
||||
});
|
||||
|
||||
exitCommand("Failed to deploy project");
|
||||
|
||||
break;
|
||||
}
|
||||
case "CANCELED": {
|
||||
await doOutputLogs("Canceled");
|
||||
|
||||
exitCommand("Failed to deploy project");
|
||||
|
||||
break;
|
||||
}
|
||||
case "FAILED": {
|
||||
const errorData = serverDeployment.errorData
|
||||
? prepareDeploymentError(serverDeployment.errorData)
|
||||
: undefined;
|
||||
|
||||
if (errorData) {
|
||||
prettyError(errorData.name, errorData.stack, errorData.stderr);
|
||||
|
||||
if (logs.trim() !== "") {
|
||||
const logPath = await saveLogs(deployment.shortCode, logs);
|
||||
|
||||
outro(`Aborting deployment. Full build logs have been saved to ${logPath}`);
|
||||
} else {
|
||||
outro(`Aborting deployment`);
|
||||
}
|
||||
} else {
|
||||
await doOutputLogs("Failed");
|
||||
}
|
||||
|
||||
exitCommand("Failed to deploy project");
|
||||
|
||||
break;
|
||||
}
|
||||
case "DEPLOYED": {
|
||||
await doOutputLogs("Deployed with errors");
|
||||
|
||||
exitCommand("Deployed with errors");
|
||||
|
||||
break;
|
||||
}
|
||||
case "TIMED_OUT": {
|
||||
await doOutputLogs("TimedOut");
|
||||
|
||||
exitCommand("Timed out");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await client.failDeployment(deployment.id, {
|
||||
error,
|
||||
});
|
||||
|
||||
throw new SkipLoggingError(`Failed to deploy: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { logger } from "../utilities/logger.js";
|
||||
import { runtimeChecks } from "../utilities/runtimeCheck.js";
|
||||
import { getProjectClient, isLoggedIn, LoginResultOk } from "../utilities/session.js";
|
||||
import { updateTriggerPackages } from "./update.js";
|
||||
import { login } from "./login.js";
|
||||
|
||||
const DevCommandOptions = CommonCommandOptions.extend({
|
||||
debugOtel: z.boolean().default(false),
|
||||
@@ -45,7 +46,11 @@ export function configureDevCommand(program: Command) {
|
||||
export async function devCommand(options: DevCommandOptions) {
|
||||
runtimeChecks();
|
||||
|
||||
const authorization = await isLoggedIn(options.profile);
|
||||
const authorization = await login({
|
||||
embedded: true,
|
||||
defaultApiUrl: options.apiUrl,
|
||||
profile: options.profile,
|
||||
});
|
||||
|
||||
if (!authorization.ok) {
|
||||
if (authorization.error === "fetch failed") {
|
||||
|
||||
@@ -22,6 +22,7 @@ import { logger } from "../utilities/logger.js";
|
||||
import { spinner } from "../utilities/windows.js";
|
||||
import { isLinuxServer } from "../utilities/linux.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { env } from "std-env";
|
||||
|
||||
export const LoginCommandOptions = CommonCommandOptions.extend({
|
||||
apiUrl: z.string(),
|
||||
@@ -74,12 +75,12 @@ export async function login(options?: LoginOptions): Promise<LoginResult> {
|
||||
intro("Logging in to Trigger.dev");
|
||||
}
|
||||
|
||||
const accessTokenFromEnv = process.env.TRIGGER_ACCESS_TOKEN;
|
||||
const accessTokenFromEnv = env.TRIGGER_ACCESS_TOKEN;
|
||||
|
||||
if (accessTokenFromEnv) {
|
||||
const auth = {
|
||||
accessToken: accessTokenFromEnv,
|
||||
apiUrl: process.env.TRIGGER_API_URL ?? opts.defaultApiUrl ?? "https://api.trigger.dev",
|
||||
apiUrl: env.TRIGGER_API_URL ?? opts.defaultApiUrl ?? "https://api.trigger.dev",
|
||||
};
|
||||
const apiClient = new CliApiClient(auth.apiUrl, auth.accessToken);
|
||||
const userData = await apiClient.whoAmI();
|
||||
|
||||
@@ -11,6 +11,7 @@ import { printStandloneInitialBanner, updateCheck } from "../utilities/initialBa
|
||||
import { logger } from "../utilities/logger.js";
|
||||
import { spinner } from "../utilities/windows.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { hasTTY } from "std-env";
|
||||
|
||||
export const UpdateCommandOptions = CommonCommandOptions.pick({
|
||||
logLevel: true,
|
||||
@@ -142,7 +143,7 @@ export async function updateTriggerPackages(
|
||||
);
|
||||
}
|
||||
|
||||
if (!process.stdout.isTTY) {
|
||||
if (!hasTTY) {
|
||||
// Running in CI with version mismatch detected
|
||||
outro("Deploy failed");
|
||||
|
||||
|
||||
@@ -408,8 +408,8 @@ function extractImageDigest(outputs: string[]) {
|
||||
export type GenerateContainerfileOptions = {
|
||||
runtime: BuildRuntime;
|
||||
build: BuildManifest["build"];
|
||||
indexerEntryPoint: string;
|
||||
workerEntryPoint: string;
|
||||
indexScript: string;
|
||||
entrypoint: string;
|
||||
};
|
||||
|
||||
export async function generateContainerfile(options: GenerateContainerfileOptions) {
|
||||
@@ -481,7 +481,7 @@ ENV TRIGGER_PROJECT_ID=\${TRIGGER_PROJECT_ID} \
|
||||
NODE_ENV=production
|
||||
|
||||
# Run the indexer
|
||||
RUN bun run ${options.indexerEntryPoint}
|
||||
RUN bun run ${options.indexScript}
|
||||
|
||||
# Development or production stage builds upon the base stage
|
||||
FROM base AS final
|
||||
@@ -510,7 +510,7 @@ COPY --from=install --chown=bun:bun /app ./
|
||||
# Copy the index.json file from the indexer stage
|
||||
COPY --from=indexer --chown=bun:bun /app/index.json ./
|
||||
|
||||
ENTRYPOINT [ "dumb-init", "node", "${options.workerEntryPoint}" ]
|
||||
ENTRYPOINT [ "dumb-init", "node", "${options.entrypoint}" ]
|
||||
CMD []
|
||||
`;
|
||||
}
|
||||
@@ -579,7 +579,7 @@ ENV TRIGGER_PROJECT_ID=\${TRIGGER_PROJECT_ID} \
|
||||
NODE_OPTIONS="--max_old_space_size=8192"
|
||||
|
||||
# Run the indexer
|
||||
RUN node ${options.indexerEntryPoint}
|
||||
RUN node ${options.indexScript}
|
||||
|
||||
# Development or production stage builds upon the base stage
|
||||
FROM base AS final
|
||||
@@ -609,7 +609,7 @@ COPY --from=install --chown=node:node /app ./
|
||||
# Copy the index.json file from the indexer stage
|
||||
COPY --from=indexer --chown=node:node /app/index.json ./
|
||||
|
||||
ENTRYPOINT [ "dumb-init", "node", "${options.workerEntryPoint}" ]
|
||||
ENTRYPOINT [ "dumb-init", "node", "${options.entrypoint}" ]
|
||||
CMD []
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -10,27 +10,23 @@ import {
|
||||
TaskRunFailedExecutionResult,
|
||||
WorkerManifest,
|
||||
correctErrorStackTrace,
|
||||
indexerToWorkerMessages,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import { parseMessageFromCatalog } from "@trigger.dev/core/v3/zodMessageHandler";
|
||||
import { Evt } from "evt";
|
||||
import { fork } from "node:child_process";
|
||||
|
||||
import { execPathForRuntime } from "@trigger.dev/core/v3/build";
|
||||
import { join } from "node:path";
|
||||
import { TaskRunProcess, TaskRunProcessOptions } from "../executions/taskRunProcess.js";
|
||||
import { eventBus } from "../utilities/eventBus.js";
|
||||
import { writeJSONFile } from "../utilities/fileSystem.js";
|
||||
import { logger } from "../utilities/logger.js";
|
||||
import {
|
||||
CancelledProcessError,
|
||||
CleanupProcessError,
|
||||
SigKillTimeoutProcessError,
|
||||
TaskMetadataParseError,
|
||||
UncaughtExceptionError,
|
||||
UnexpectedExitError,
|
||||
getFriendlyErrorMessage,
|
||||
} from "../executions/errors.js";
|
||||
} from "@trigger.dev/core/v3/errors";
|
||||
import { TaskRunProcess, TaskRunProcessOptions } from "../executions/taskRunProcess.js";
|
||||
import { indexWorkerManifest } from "../indexing/indexWorkerManifest.js";
|
||||
import { prettyError } from "../utilities/cliOutput.js";
|
||||
import { eventBus } from "../utilities/eventBus.js";
|
||||
import { writeJSONFile } from "../utilities/fileSystem.js";
|
||||
import { logger } from "../utilities/logger.js";
|
||||
|
||||
export type CurrentWorkers = BackgroundWorkerCoordinator["currentWorkers"];
|
||||
export class BackgroundWorkerCoordinator {
|
||||
@@ -254,87 +250,36 @@ export class BackgroundWorker {
|
||||
throw new Error("Worker already initialized");
|
||||
}
|
||||
|
||||
let resolved = false;
|
||||
|
||||
// Write the build manifest to this.build.outputPath/build.json
|
||||
await writeJSONFile(this.buildManifestPath, this.build, true);
|
||||
|
||||
logger.debug("Initializing worker", { build: this.build, params: this.params });
|
||||
logger.debug("indexing worker manifest", { build: this.build, params: this.params });
|
||||
|
||||
this.manifest = await new Promise<WorkerManifest>((resolve, reject) => {
|
||||
const child = fork(this.build.indexerEntryPoint, {
|
||||
stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"],
|
||||
cwd: this.params.cwd,
|
||||
env: {
|
||||
...this.params.env,
|
||||
TRIGGER_BUILD_MANIFEST_PATH: this.buildManifestPath,
|
||||
NODE_OPTIONS: this.build.loaderEntryPoint
|
||||
? `--import=${this.build.loaderEntryPoint} ${process.env.NODE_OPTIONS ?? ""}`
|
||||
: process.env.NODE_OPTIONS,
|
||||
},
|
||||
execPath: execPathForRuntime(this.build.runtime),
|
||||
});
|
||||
|
||||
// Set a timeout to kill the child process if it doesn't respond
|
||||
const timeout = setTimeout(() => {
|
||||
if (resolved) {
|
||||
return;
|
||||
this.manifest = await indexWorkerManifest({
|
||||
runtime: this.build.runtime,
|
||||
indexWorkerPath: this.build.indexWorkerEntryPoint,
|
||||
buildManifestPath: this.buildManifestPath,
|
||||
nodeOptions: this.build.loaderEntryPoint
|
||||
? `--import=${this.build.loaderEntryPoint}`
|
||||
: undefined,
|
||||
env: this.params.env,
|
||||
cwd: this.params.cwd,
|
||||
otelHookInclude: this.build.otelImportHook?.include,
|
||||
otelHookExclude: this.build.otelImportHook?.exclude,
|
||||
handleStdout(data) {
|
||||
logger.debug(data);
|
||||
},
|
||||
handleStderr(data) {
|
||||
if (!data.includes("Debugger attached")) {
|
||||
prettyError(data.toString());
|
||||
}
|
||||
|
||||
resolved = true;
|
||||
child.kill();
|
||||
reject(new Error("Worker timed out"));
|
||||
}, 20_000);
|
||||
|
||||
child.on("message", async (msg: any) => {
|
||||
const message = parseMessageFromCatalog(msg, indexerToWorkerMessages);
|
||||
|
||||
switch (message.type) {
|
||||
case "INDEX_COMPLETE": {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
resolve(message.payload.manifest);
|
||||
child.kill();
|
||||
break;
|
||||
}
|
||||
case "TASKS_FAILED_TO_PARSE": {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
reject(new TaskMetadataParseError(message.payload.zodIssues, message.payload.tasks));
|
||||
child.kill();
|
||||
break;
|
||||
}
|
||||
case "UNCAUGHT_EXCEPTION": {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
reject(new UncaughtExceptionError(message.payload.error, message.payload.origin));
|
||||
child.kill();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
child.on("exit", (code) => {
|
||||
if (!resolved) {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
reject(new Error(`Worker exited with code ${code}`));
|
||||
}
|
||||
});
|
||||
|
||||
child.stdout?.on("data", (data) => {
|
||||
logger.debug(`indexer: ${data.toString()}`);
|
||||
});
|
||||
|
||||
child.stderr?.on("data", (data) => {
|
||||
logger.debug(`indexer: ${data.toString()}`);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Write the build manifest to this.build.outputPath/worker.json
|
||||
await writeJSONFile(this.workerManifestPath, this.manifest, true);
|
||||
|
||||
logger.debug("Worker initialized", { path: this.build.outputPath });
|
||||
logger.debug("worker manifest indexed", { path: this.build.outputPath });
|
||||
}
|
||||
|
||||
// We need to notify all the task run processes that a task run has completed,
|
||||
|
||||
@@ -11,11 +11,21 @@ import {
|
||||
chalkWarning,
|
||||
chalkWorker,
|
||||
cliLink,
|
||||
prettyError,
|
||||
prettyPrintDate,
|
||||
} from "../utilities/cliOutput.js";
|
||||
import { eventBus, EventBusEventArgs } from "../utilities/eventBus.js";
|
||||
import { TaskRunError, TaskRunErrorCodes } from "@trigger.dev/core/v3/schemas";
|
||||
import {
|
||||
TaskMetadataFailedToParseData,
|
||||
TaskRunError,
|
||||
TaskRunErrorCodes,
|
||||
} from "@trigger.dev/core/v3/schemas";
|
||||
import { formatDurationMilliseconds } from "@trigger.dev/core/v3";
|
||||
import {
|
||||
createTaskMetadataFailedErrorStack,
|
||||
TaskIndexingImportError,
|
||||
TaskMetadataParseError,
|
||||
} from "@trigger.dev/core/v3/errors";
|
||||
|
||||
export type DevOutputOptions = {
|
||||
name: string | undefined;
|
||||
@@ -63,6 +73,29 @@ export function startDevOutput(options: DevOutputOptions) {
|
||||
);
|
||||
};
|
||||
|
||||
const backgroundWorkerIndexingError = (
|
||||
...[buildManifest, error]: EventBusEventArgs<"backgroundWorkerIndexingError">
|
||||
) => {
|
||||
if (error instanceof TaskIndexingImportError) {
|
||||
for (const importError of error.importErrors) {
|
||||
prettyError(`Could not import ${importError.file}`, importError.stack);
|
||||
}
|
||||
} else if (error instanceof TaskMetadataParseError) {
|
||||
const errorStack = createTaskMetadataFailedErrorStack({
|
||||
version: "v1",
|
||||
zodIssues: error.zodIssues,
|
||||
tasks: error.tasks,
|
||||
});
|
||||
|
||||
prettyError(`Could not parse task metadata`, errorStack);
|
||||
} else {
|
||||
const errorText = error instanceof Error ? error.message : "Unknown error";
|
||||
const stack = error instanceof Error ? error.stack : undefined;
|
||||
|
||||
prettyError(`Build failed: ${errorText}`, stack);
|
||||
}
|
||||
};
|
||||
|
||||
const runStarted = (...[worker, payload]: EventBusEventArgs<"runStarted">) => {
|
||||
if (!worker.serverWorker) {
|
||||
return;
|
||||
@@ -142,6 +175,7 @@ export function startDevOutput(options: DevOutputOptions) {
|
||||
eventBus.on("backgroundWorkerInitialized", backgroundWorkerInitialized);
|
||||
eventBus.on("runStarted", runStarted);
|
||||
eventBus.on("runCompleted", runCompleted);
|
||||
eventBus.on("backgroundWorkerIndexingError", backgroundWorkerIndexingError);
|
||||
|
||||
return () => {
|
||||
eventBus.off("rebuildStarted", rebuildStarted);
|
||||
@@ -150,6 +184,7 @@ export function startDevOutput(options: DevOutputOptions) {
|
||||
eventBus.off("backgroundWorkerInitialized", backgroundWorkerInitialized);
|
||||
eventBus.off("runStarted", runStarted);
|
||||
eventBus.off("runCompleted", runCompleted);
|
||||
eventBus.off("backgroundWorkerIndexingError", backgroundWorkerIndexingError);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,7 @@ import {
|
||||
} from "../build/extensions.js";
|
||||
import { createExternalsBuildExtension } from "../build/externals.js";
|
||||
import { copyManifestToDir } from "../build/manifests.js";
|
||||
import {
|
||||
devExecutorEntryPoint,
|
||||
devIndexerEntryPoint,
|
||||
telemetryEntryPoint,
|
||||
} from "../build/packageModules.js";
|
||||
import { devIndexWorker, devRunWorker, telemetryEntryPoint } from "../build/packageModules.js";
|
||||
import { type DevCommandOptions } from "../commands/dev.js";
|
||||
import { eventBus } from "../utilities/eventBus.js";
|
||||
import { logger } from "../utilities/logger.js";
|
||||
@@ -31,6 +27,7 @@ import { EphemeralDirectory, getTmpDir } from "../utilities/tempDirectories.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { startDevOutput } from "./devOutput.js";
|
||||
import { startWorkerRuntime } from "./workerRuntime.js";
|
||||
import { getInstrumentedPackageNames } from "../build/instrumentation.js";
|
||||
|
||||
export type DevSessionOptions = {
|
||||
name: string | undefined;
|
||||
@@ -75,21 +72,25 @@ export async function startDevSession({
|
||||
const pluginsFromExtensions = resolvePluginsForContext(buildContext);
|
||||
|
||||
async function updateBundle(bundle: BundleResult, workerDir?: EphemeralDirectory) {
|
||||
let buildManifest = await createBuildManifestFromBundle(
|
||||
bundle,
|
||||
destination.path,
|
||||
rawConfig,
|
||||
workerDir?.path
|
||||
);
|
||||
|
||||
buildManifest = await notifyExtensionOnBuildComplete(buildContext, buildManifest);
|
||||
|
||||
try {
|
||||
let buildManifest = await createBuildManifestFromBundle(
|
||||
bundle,
|
||||
destination.path,
|
||||
rawConfig,
|
||||
workerDir?.path
|
||||
);
|
||||
|
||||
buildManifest = await notifyExtensionOnBuildComplete(buildContext, buildManifest);
|
||||
|
||||
logger.debug("Updated bundle", { bundle, buildManifest });
|
||||
|
||||
await runtime.initializeWorker(buildManifest);
|
||||
} catch (error) {
|
||||
logger.error("Error updating bundle", { error });
|
||||
if (error instanceof Error) {
|
||||
eventBus.emit("backgroundWorkerIndexingError", buildManifest, error);
|
||||
} else {
|
||||
logger.error("Error updating bundle", { error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,14 +192,17 @@ async function createBuildManifestFromBundle(
|
||||
dirs: resolvedConfig.dirs,
|
||||
},
|
||||
outputPath: destination,
|
||||
executorEntryPoint: bundle.executorEntryPoint ?? devExecutorEntryPoint,
|
||||
runWorkerEntryPoint: bundle.runWorkerEntryPoint ?? devRunWorker,
|
||||
indexWorkerEntryPoint: bundle.indexWorkerEntryPoint ?? devIndexWorker,
|
||||
loaderEntryPoint: bundle.loaderEntryPoint ?? telemetryEntryPoint,
|
||||
indexerEntryPoint: bundle.indexerEntryPoint ?? devIndexerEntryPoint,
|
||||
configPath: bundle.configPath,
|
||||
deploy: {
|
||||
env: {},
|
||||
},
|
||||
build: {},
|
||||
otelImportHook: {
|
||||
include: getInstrumentedPackageNames(resolvedConfig),
|
||||
},
|
||||
};
|
||||
|
||||
if (!workerDir) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import { eventBus } from "../utilities/eventBus.js";
|
||||
import { logger } from "../utilities/logger.js";
|
||||
import { resolveTaskSourceFiles } from "../utilities/sourceFiles.js";
|
||||
import { BackgroundWorker, BackgroundWorkerCoordinator } from "./backgroundWorker.js";
|
||||
import { env } from "std-env";
|
||||
|
||||
export interface WorkerRuntime {
|
||||
shutdown(): Promise<void>;
|
||||
@@ -316,13 +317,13 @@ function WebsocketFactory(apiKey: string) {
|
||||
}
|
||||
|
||||
function gatherProcessEnv() {
|
||||
const env = {
|
||||
...process.env,
|
||||
const $env = {
|
||||
...env,
|
||||
NODE_ENV: "development",
|
||||
};
|
||||
|
||||
// Filter out undefined values
|
||||
return Object.fromEntries(Object.entries(env).filter(([key, value]) => value !== undefined));
|
||||
return Object.fromEntries(Object.entries($env).filter(([key, value]) => value !== undefined));
|
||||
}
|
||||
|
||||
function validateWorkerManifest(manifest: WorkerManifest): string[] {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
BuildManifest,
|
||||
CreateBackgroundWorkerRequestBody,
|
||||
serializeIndexingError,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { env } from "std-env";
|
||||
import { CliApiClient } from "../apiClient.js";
|
||||
import { indexWorkerManifest } from "../indexing/indexWorkerManifest.js";
|
||||
import { resolveTaskSourceFiles } from "../utilities/sourceFiles.js";
|
||||
|
||||
async function loadBuildManifest() {
|
||||
const manifestContents = await readFile("./build.json", "utf-8");
|
||||
const raw = JSON.parse(manifestContents);
|
||||
|
||||
return BuildManifest.parse(raw);
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
const buildManifest = await loadBuildManifest();
|
||||
|
||||
if (typeof env.TRIGGER_API_URL !== "string") {
|
||||
console.error("TRIGGER_API_URL is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cliApiClient = new CliApiClient(env.TRIGGER_API_URL, env.TRIGGER_SECRET_KEY);
|
||||
|
||||
if (!env.TRIGGER_PROJECT_REF) {
|
||||
console.error("TRIGGER_PROJECT_REF is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!env.TRIGGER_DEPLOYMENT_ID) {
|
||||
console.error("TRIGGER_DEPLOYMENT_ID is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return {
|
||||
buildManifest,
|
||||
cliApiClient,
|
||||
projectRef: env.TRIGGER_PROJECT_REF,
|
||||
deploymentId: env.TRIGGER_DEPLOYMENT_ID,
|
||||
};
|
||||
}
|
||||
|
||||
type BootstrapResult = Awaited<ReturnType<typeof bootstrap>>;
|
||||
|
||||
async function indexDeployment({
|
||||
cliApiClient,
|
||||
projectRef,
|
||||
deploymentId,
|
||||
buildManifest,
|
||||
}: BootstrapResult) {
|
||||
const stdout: string[] = [];
|
||||
const stderr: string[] = [];
|
||||
|
||||
try {
|
||||
const $env = await cliApiClient.getEnvironmentVariables(projectRef);
|
||||
|
||||
if (!$env.success) {
|
||||
throw new Error(`Failed to fetch environment variables: ${env.error}`);
|
||||
}
|
||||
|
||||
const workerManifest = await indexWorkerManifest({
|
||||
runtime: buildManifest.runtime,
|
||||
indexWorkerPath: buildManifest.indexWorkerEntryPoint,
|
||||
buildManifestPath: "./build.json",
|
||||
nodeOptions: buildManifest.loaderEntryPoint
|
||||
? `--import=${buildManifest.loaderEntryPoint}`
|
||||
: undefined,
|
||||
env: $env.data.variables,
|
||||
otelHookExclude: buildManifest.otelImportHook?.exclude,
|
||||
otelHookInclude: buildManifest.otelImportHook?.include,
|
||||
handleStdout(data) {
|
||||
stdout.push(data);
|
||||
},
|
||||
handleStderr(data) {
|
||||
if (!data.includes("DeprecationWarning")) {
|
||||
stderr.push(data);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
console.log("Writing index.json", process.cwd());
|
||||
|
||||
await writeFile(join(process.cwd(), "index.json"), JSON.stringify(workerManifest, null, 2));
|
||||
|
||||
const sourceFiles = resolveTaskSourceFiles(buildManifest.sources, workerManifest.tasks);
|
||||
|
||||
const backgroundWorkerBody: CreateBackgroundWorkerRequestBody = {
|
||||
localOnly: true,
|
||||
metadata: {
|
||||
contentHash: buildManifest.contentHash,
|
||||
packageVersion: buildManifest.packageVersion,
|
||||
cliPackageVersion: buildManifest.cliPackageVersion,
|
||||
tasks: workerManifest.tasks,
|
||||
sourceFiles,
|
||||
},
|
||||
supportsLazyAttempts: true,
|
||||
};
|
||||
|
||||
await cliApiClient.createDeploymentBackgroundWorker(deploymentId, backgroundWorkerBody);
|
||||
} catch (error) {
|
||||
const serialiedIndexError = serializeIndexingError(error, stderr.join("\n"));
|
||||
|
||||
await cliApiClient.failDeployment(deploymentId, { error: serialiedIndexError });
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const results = await bootstrap();
|
||||
|
||||
await indexDeployment(results);
|
||||
+11
-43
@@ -13,6 +13,8 @@ import {
|
||||
import { sendMessageInCatalog, ZodSchemaParsedError } from "@trigger.dev/core/v3/zodMessageHandler";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import sourceMapSupport from "source-map-support";
|
||||
import { registerTasks } from "../indexing/registerTasks.js";
|
||||
import { env } from "std-env";
|
||||
|
||||
sourceMapSupport.install({
|
||||
handleUncaughtExceptions: false,
|
||||
@@ -63,7 +65,7 @@ async function importConfig(
|
||||
}
|
||||
|
||||
async function loadBuildManifest() {
|
||||
const manifestContents = await readFile(process.env.TRIGGER_BUILD_MANIFEST_PATH!, "utf-8");
|
||||
const manifestContents = await readFile(env.TRIGGER_BUILD_MANIFEST_PATH!, "utf-8");
|
||||
const raw = JSON.parse(manifestContents);
|
||||
|
||||
return BuildManifest.parse(raw);
|
||||
@@ -76,42 +78,23 @@ async function bootstrap() {
|
||||
|
||||
// This needs to run or the PrismaInstrumentation will throw an error
|
||||
const tracingSDK = new TracingSDK({
|
||||
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
|
||||
url: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
|
||||
instrumentations: config.instrumentations ?? [],
|
||||
diagLogLevel: (process.env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none",
|
||||
diagLogLevel: (env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none",
|
||||
forceFlushTimeoutMillis: 30_000,
|
||||
});
|
||||
|
||||
for (const file of buildManifest.files) {
|
||||
const module = await import(file.out);
|
||||
|
||||
for (const exportName of getExportNames(module)) {
|
||||
const task = module[exportName] ?? module.default?.[exportName];
|
||||
|
||||
if (!task) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (task[Symbol.for("trigger.dev/task")]) {
|
||||
if (taskCatalog.taskExists(task.id)) {
|
||||
taskCatalog.registerTaskFileMetadata(task.id, {
|
||||
exportName,
|
||||
filePath: file.entry,
|
||||
entryPoint: file.out,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const importErrors = await registerTasks(buildManifest);
|
||||
|
||||
return {
|
||||
tracingSDK,
|
||||
config,
|
||||
buildManifest,
|
||||
importErrors,
|
||||
};
|
||||
}
|
||||
|
||||
const { buildManifest } = await bootstrap();
|
||||
const { buildManifest, importErrors } = await bootstrap();
|
||||
|
||||
const tasks = taskCatalog.listTaskManifests();
|
||||
|
||||
@@ -123,10 +106,11 @@ await sendMessageInCatalog(
|
||||
tasks,
|
||||
configPath: buildManifest.configPath,
|
||||
runtime: buildManifest.runtime,
|
||||
executorEntryPoint: buildManifest.executorEntryPoint,
|
||||
workerEntryPoint: buildManifest.workerEntryPoint,
|
||||
workerEntryPoint: buildManifest.runWorkerEntryPoint,
|
||||
controllerEntryPoint: buildManifest.runControllerEntryPoint,
|
||||
loaderEntryPoint: buildManifest.loaderEntryPoint,
|
||||
},
|
||||
importErrors,
|
||||
},
|
||||
async (msg) => {
|
||||
process.send?.(msg);
|
||||
@@ -153,19 +137,3 @@ await new Promise<void>((resolve) => {
|
||||
resolve();
|
||||
}, 10);
|
||||
});
|
||||
|
||||
function getExportNames(module: any) {
|
||||
const exports: string[] = [];
|
||||
|
||||
const exportKeys = Object.keys(module);
|
||||
|
||||
if (exportKeys.length === 0) {
|
||||
return exports;
|
||||
}
|
||||
|
||||
if (exportKeys.length === 1 && exportKeys[0] === "default") {
|
||||
return Object.keys(module.default);
|
||||
}
|
||||
|
||||
return exportKeys;
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
import {
|
||||
BuildManifest,
|
||||
CreateBackgroundWorkerRequestBody,
|
||||
type HandleErrorFunction,
|
||||
taskCatalog,
|
||||
TriggerConfig,
|
||||
WorkerManifest,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import {
|
||||
StandardTaskCatalog,
|
||||
TracingDiagnosticLogLevel,
|
||||
TracingSDK,
|
||||
} from "@trigger.dev/core/v3/workers";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import sourceMapSupport from "source-map-support";
|
||||
import { CliApiClient } from "../apiClient.js";
|
||||
import { resolveTaskSourceFiles } from "../utilities/sourceFiles.js";
|
||||
import { join } from "node:path";
|
||||
|
||||
sourceMapSupport.install({
|
||||
handleUncaughtExceptions: false,
|
||||
environment: "node",
|
||||
hookRequire: false,
|
||||
});
|
||||
|
||||
taskCatalog.setGlobalTaskCatalog(new StandardTaskCatalog());
|
||||
|
||||
async function importConfig(configPath: string): Promise<{
|
||||
config: TriggerConfig;
|
||||
handleError?: HandleErrorFunction;
|
||||
}> {
|
||||
const configModule = await import(configPath);
|
||||
|
||||
const config = configModule?.default ?? configModule?.config;
|
||||
|
||||
return {
|
||||
config,
|
||||
handleError: configModule?.handleError,
|
||||
};
|
||||
}
|
||||
|
||||
async function loadBuildManifest() {
|
||||
const manifestContents = await readFile("./build.json", "utf-8");
|
||||
const raw = JSON.parse(manifestContents);
|
||||
|
||||
return BuildManifest.parse(raw);
|
||||
}
|
||||
|
||||
// We need to make sure, that if any errors are thrown, that we fail the deployment
|
||||
|
||||
// 1. Fetch the build manifest
|
||||
// 2. Fetch the environment variables from the server
|
||||
// 3. Import the config
|
||||
// 5. Inject the env vars into process.env
|
||||
// 6. Configure the tracing SDK
|
||||
// 7. Load all the tasks from the build manifest and create the index.json
|
||||
// 8. Write the index.json to the file system
|
||||
// 9. Update the deployment with the worker index.json
|
||||
// 10. Exit the process
|
||||
async function bootstrap() {
|
||||
const buildManifest = await loadBuildManifest();
|
||||
|
||||
if (typeof process.env.TRIGGER_API_URL !== "string") {
|
||||
console.error("TRIGGER_API_URL is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cliApiClient = new CliApiClient(
|
||||
process.env.TRIGGER_API_URL,
|
||||
process.env.TRIGGER_SECRET_KEY
|
||||
);
|
||||
|
||||
if (!process.env.TRIGGER_PROJECT_REF) {
|
||||
console.error("TRIGGER_PROJECT_REF is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!process.env.TRIGGER_DEPLOYMENT_ID) {
|
||||
console.error("TRIGGER_DEPLOYMENT_ID is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return {
|
||||
buildManifest,
|
||||
cliApiClient,
|
||||
projectRef: process.env.TRIGGER_PROJECT_REF,
|
||||
deploymentId: process.env.TRIGGER_DEPLOYMENT_ID,
|
||||
};
|
||||
}
|
||||
|
||||
type BootstrapResult = Awaited<ReturnType<typeof bootstrap>>;
|
||||
|
||||
async function indexDeployment({
|
||||
cliApiClient,
|
||||
projectRef,
|
||||
deploymentId,
|
||||
buildManifest,
|
||||
}: BootstrapResult) {
|
||||
try {
|
||||
const env = await cliApiClient.getEnvironmentVariables(projectRef);
|
||||
|
||||
if (!env.success) {
|
||||
throw new Error(`Failed to fetch environment variables: ${env.error}`);
|
||||
}
|
||||
|
||||
injectEnvVars(env.data.variables);
|
||||
|
||||
const { config } = await importConfig(buildManifest.configPath);
|
||||
|
||||
// This needs to run or the PrismaInstrumentation will throw an error
|
||||
new TracingSDK({
|
||||
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
|
||||
instrumentations: config.instrumentations ?? [],
|
||||
diagLogLevel: (process.env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none",
|
||||
forceFlushTimeoutMillis: 30_000,
|
||||
});
|
||||
|
||||
const importErrors: Array<{ error: Error; file: string }> = [];
|
||||
|
||||
for (const file of buildManifest.files) {
|
||||
const [error, module] = await $import(file.out);
|
||||
|
||||
if (error) {
|
||||
importErrors.push({ error, file: file.entry });
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const exportName of getExportNames(module)) {
|
||||
const task = module[exportName] ?? module.default?.[exportName];
|
||||
|
||||
if (!task) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (task[Symbol.for("trigger.dev/task")]) {
|
||||
if (taskCatalog.taskExists(task.id)) {
|
||||
taskCatalog.registerTaskFileMetadata(task.id, {
|
||||
exportName,
|
||||
filePath: file.entry,
|
||||
entryPoint: file.out,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Import errors", importErrors);
|
||||
|
||||
if (importErrors.length > 0) {
|
||||
const errorMessages = importErrors.map((error) => {
|
||||
return `${error.file}: ${error.error.message}`;
|
||||
});
|
||||
|
||||
throw new Error(`Failed to index task files:\n${errorMessages.join("\n")}`);
|
||||
}
|
||||
|
||||
let tasks = taskCatalog.listTaskManifests();
|
||||
|
||||
if (typeof config.machine === "string") {
|
||||
// Set the machine preset on all tasks that don't have it
|
||||
tasks = tasks.map((task) => {
|
||||
if (typeof task.machine?.preset !== "string") {
|
||||
return {
|
||||
...task,
|
||||
machine: {
|
||||
preset: config.machine,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return task;
|
||||
});
|
||||
}
|
||||
|
||||
const workerManifest: WorkerManifest = {
|
||||
tasks,
|
||||
configPath: buildManifest.configPath,
|
||||
runtime: buildManifest.runtime,
|
||||
executorEntryPoint: buildManifest.executorEntryPoint,
|
||||
workerEntryPoint: buildManifest.workerEntryPoint,
|
||||
loaderEntryPoint: buildManifest.loaderEntryPoint,
|
||||
otelImportHook: buildManifest.otelImportHook,
|
||||
};
|
||||
|
||||
console.log("Writing index.json", process.cwd());
|
||||
|
||||
await writeFile(join(process.cwd(), "index.json"), JSON.stringify(workerManifest, null, 2));
|
||||
|
||||
const sourceFiles = resolveTaskSourceFiles(buildManifest.sources, workerManifest.tasks);
|
||||
|
||||
const backgroundWorkerBody: CreateBackgroundWorkerRequestBody = {
|
||||
localOnly: true,
|
||||
metadata: {
|
||||
contentHash: buildManifest.contentHash,
|
||||
packageVersion: buildManifest.packageVersion,
|
||||
cliPackageVersion: buildManifest.cliPackageVersion,
|
||||
tasks: workerManifest.tasks,
|
||||
sourceFiles,
|
||||
},
|
||||
supportsLazyAttempts: true,
|
||||
};
|
||||
|
||||
await cliApiClient.createDeploymentBackgroundWorker(deploymentId, backgroundWorkerBody);
|
||||
} catch (error) {
|
||||
// If we have an error, we need to fail the deployment
|
||||
await cliApiClient.failDeployment(deploymentId, {
|
||||
error:
|
||||
error instanceof Error
|
||||
? {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
}
|
||||
: {
|
||||
name: "Error",
|
||||
message: String(error),
|
||||
},
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const results = await bootstrap();
|
||||
|
||||
await indexDeployment(results);
|
||||
|
||||
function getExportNames(module: any) {
|
||||
const exports: string[] = [];
|
||||
|
||||
const exportKeys = Object.keys(module);
|
||||
|
||||
if (exportKeys.length === 0) {
|
||||
return exports;
|
||||
}
|
||||
|
||||
if (exportKeys.length === 1 && exportKeys[0] === "default") {
|
||||
return Object.keys(module.default);
|
||||
}
|
||||
|
||||
return exportKeys;
|
||||
}
|
||||
|
||||
type Result<T> = [Error | null, T | null];
|
||||
|
||||
async function $import(path: string): Promise<Result<any>> {
|
||||
try {
|
||||
const module = await import(path);
|
||||
|
||||
return [null, module];
|
||||
} catch (error) {
|
||||
return [error as Error, null];
|
||||
}
|
||||
}
|
||||
|
||||
function injectEnvVars(env: Record<string, string>) {
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
+20
-19
@@ -31,13 +31,14 @@ import {
|
||||
TaskRunProcess,
|
||||
} from "../executions/taskRunProcess.js";
|
||||
import { checkpointSafeTimeout, unboundedTimeout } from "@trigger.dev/core/v3/utils/timers";
|
||||
import { env } from "std-env";
|
||||
|
||||
const HTTP_SERVER_PORT = Number(process.env.HTTP_SERVER_PORT || getRandomPortNumber());
|
||||
const COORDINATOR_HOST = process.env.COORDINATOR_HOST || "127.0.0.1";
|
||||
const COORDINATOR_PORT = Number(process.env.COORDINATOR_PORT || 50080);
|
||||
const MACHINE_NAME = process.env.MACHINE_NAME || "local";
|
||||
const POD_NAME = process.env.POD_NAME || "some-pod";
|
||||
const SHORT_HASH = process.env.TRIGGER_CONTENT_HASH!.slice(0, 9);
|
||||
const HTTP_SERVER_PORT = Number(env.HTTP_SERVER_PORT || getRandomPortNumber());
|
||||
const COORDINATOR_HOST = env.COORDINATOR_HOST || "127.0.0.1";
|
||||
const COORDINATOR_PORT = Number(env.COORDINATOR_PORT || 50080);
|
||||
const MACHINE_NAME = env.MACHINE_NAME || "local";
|
||||
const POD_NAME = env.POD_NAME || "some-pod";
|
||||
const SHORT_HASH = env.TRIGGER_CONTENT_HASH!.slice(0, 9);
|
||||
|
||||
const logger = new SimpleLogger(`[${MACHINE_NAME}][${SHORT_HASH}]`);
|
||||
|
||||
@@ -48,17 +49,17 @@ const defaultBackoff = new ExponentialBackoff("FullJitter", {
|
||||
cliLogger.loggerLevel = "debug";
|
||||
|
||||
cliLogger.debug("Starting prod worker", {
|
||||
env: process.env,
|
||||
env,
|
||||
});
|
||||
|
||||
class ProdWorker {
|
||||
private contentHash = process.env.TRIGGER_CONTENT_HASH!;
|
||||
private projectRef = process.env.TRIGGER_PROJECT_REF!;
|
||||
private envId = process.env.TRIGGER_ENV_ID!;
|
||||
private runId = process.env.TRIGGER_RUN_ID!;
|
||||
private deploymentId = process.env.TRIGGER_DEPLOYMENT_ID!;
|
||||
private deploymentVersion = process.env.TRIGGER_DEPLOYMENT_VERSION!;
|
||||
private runningInKubernetes = !!process.env.KUBERNETES_PORT;
|
||||
private contentHash = env.TRIGGER_CONTENT_HASH!;
|
||||
private projectRef = env.TRIGGER_PROJECT_REF!;
|
||||
private envId = env.TRIGGER_ENV_ID!;
|
||||
private runId = env.TRIGGER_RUN_ID!;
|
||||
private deploymentId = env.TRIGGER_DEPLOYMENT_ID!;
|
||||
private deploymentVersion = env.TRIGGER_DEPLOYMENT_VERSION!;
|
||||
private runningInKubernetes = !!env.KUBERNETES_PORT;
|
||||
|
||||
private executing = false;
|
||||
private completed = new Set<string>();
|
||||
@@ -1291,15 +1292,15 @@ const prodWorker = new ProdWorker(HTTP_SERVER_PORT, workerManifest);
|
||||
await prodWorker.start();
|
||||
|
||||
function gatherProcessEnv(): Record<string, string> {
|
||||
const env = {
|
||||
NODE_ENV: process.env.NODE_ENV ?? "production",
|
||||
NODE_EXTRA_CA_CERTS: process.env.NODE_EXTRA_CA_CERTS,
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
|
||||
const $env = {
|
||||
NODE_ENV: env.NODE_ENV ?? "production",
|
||||
NODE_EXTRA_CA_CERTS: env.NODE_EXTRA_CA_CERTS,
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
|
||||
};
|
||||
|
||||
// Filter out undefined values
|
||||
return Object.fromEntries(
|
||||
Object.entries(env).filter(([key, value]) => value !== undefined)
|
||||
Object.entries($env).filter(([key, value]) => value !== undefined)
|
||||
) as Record<string, string>;
|
||||
}
|
||||
|
||||
+4
-3
@@ -35,6 +35,7 @@ import { readFile } from "node:fs/promises";
|
||||
import sourceMapSupport from "source-map-support";
|
||||
import { VERSION } from "../version.js";
|
||||
import { setTimeout, setInterval } from "node:timers/promises";
|
||||
import { env } from "std-env";
|
||||
|
||||
sourceMapSupport.install({
|
||||
handleUncaughtExceptions: false,
|
||||
@@ -119,9 +120,9 @@ async function bootstrap() {
|
||||
const { config, handleError } = await importConfig(workerManifest.configPath);
|
||||
|
||||
const tracingSDK = new TracingSDK({
|
||||
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
|
||||
url: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
|
||||
instrumentations: config.instrumentations ?? [],
|
||||
diagLogLevel: (process.env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none",
|
||||
diagLogLevel: (env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none",
|
||||
forceFlushTimeoutMillis: 30_000,
|
||||
});
|
||||
|
||||
@@ -353,7 +354,7 @@ async function flushTracingSDK(timeoutInMs: number = 10_000) {
|
||||
}
|
||||
|
||||
const prodRuntimeManager = new ProdRuntimeManager(zodIpc, {
|
||||
waitThresholdInMs: parseInt(process.env.TRIGGER_RUNTIME_WAIT_THRESHOLD_IN_MS ?? "30000", 10),
|
||||
waitThresholdInMs: parseInt(env.TRIGGER_RUNTIME_WAIT_THRESHOLD_IN_MS ?? "30000", 10),
|
||||
});
|
||||
|
||||
runtime.setGlobalRuntimeManager(prodRuntimeManager);
|
||||
@@ -0,0 +1,139 @@
|
||||
import {
|
||||
BuildManifest,
|
||||
type HandleErrorFunction,
|
||||
indexerToWorkerMessages,
|
||||
taskCatalog,
|
||||
TriggerConfig,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import {
|
||||
StandardTaskCatalog,
|
||||
TracingDiagnosticLogLevel,
|
||||
TracingSDK,
|
||||
} from "@trigger.dev/core/v3/workers";
|
||||
import { sendMessageInCatalog, ZodSchemaParsedError } from "@trigger.dev/core/v3/zodMessageHandler";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import sourceMapSupport from "source-map-support";
|
||||
import { registerTasks } from "../indexing/registerTasks.js";
|
||||
import { env } from "std-env";
|
||||
|
||||
sourceMapSupport.install({
|
||||
handleUncaughtExceptions: false,
|
||||
environment: "node",
|
||||
hookRequire: false,
|
||||
});
|
||||
|
||||
process.on("uncaughtException", function (error, origin) {
|
||||
if (error instanceof Error) {
|
||||
process.send &&
|
||||
process.send({
|
||||
type: "UNCAUGHT_EXCEPTION",
|
||||
payload: {
|
||||
error: { name: error.name, message: error.message, stack: error.stack },
|
||||
origin,
|
||||
},
|
||||
version: "v1",
|
||||
});
|
||||
} else {
|
||||
process.send &&
|
||||
process.send({
|
||||
type: "UNCAUGHT_EXCEPTION",
|
||||
payload: {
|
||||
error: {
|
||||
name: "Error",
|
||||
message: typeof error === "string" ? error : JSON.stringify(error),
|
||||
},
|
||||
origin,
|
||||
},
|
||||
version: "v1",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
taskCatalog.setGlobalTaskCatalog(new StandardTaskCatalog());
|
||||
|
||||
async function importConfig(
|
||||
configPath: string
|
||||
): Promise<{ config: TriggerConfig; handleError?: HandleErrorFunction }> {
|
||||
const configModule = await import(configPath);
|
||||
|
||||
const config = configModule?.default ?? configModule?.config;
|
||||
|
||||
return {
|
||||
config,
|
||||
handleError: configModule?.handleError,
|
||||
};
|
||||
}
|
||||
|
||||
async function loadBuildManifest() {
|
||||
const manifestContents = await readFile(env.TRIGGER_BUILD_MANIFEST_PATH!, "utf-8");
|
||||
const raw = JSON.parse(manifestContents);
|
||||
|
||||
return BuildManifest.parse(raw);
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
const buildManifest = await loadBuildManifest();
|
||||
|
||||
const { config } = await importConfig(buildManifest.configPath);
|
||||
|
||||
// This needs to run or the PrismaInstrumentation will throw an error
|
||||
const tracingSDK = new TracingSDK({
|
||||
url: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
|
||||
instrumentations: config.instrumentations ?? [],
|
||||
diagLogLevel: (env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none",
|
||||
forceFlushTimeoutMillis: 30_000,
|
||||
});
|
||||
|
||||
const importErrors = await registerTasks(buildManifest);
|
||||
|
||||
return {
|
||||
tracingSDK,
|
||||
config,
|
||||
buildManifest,
|
||||
importErrors,
|
||||
};
|
||||
}
|
||||
|
||||
const { buildManifest, importErrors } = await bootstrap();
|
||||
|
||||
const tasks = taskCatalog.listTaskManifests();
|
||||
|
||||
await sendMessageInCatalog(
|
||||
indexerToWorkerMessages,
|
||||
"INDEX_COMPLETE",
|
||||
{
|
||||
manifest: {
|
||||
tasks,
|
||||
configPath: buildManifest.configPath,
|
||||
runtime: buildManifest.runtime,
|
||||
workerEntryPoint: buildManifest.runWorkerEntryPoint,
|
||||
controllerEntryPoint: buildManifest.runControllerEntryPoint,
|
||||
loaderEntryPoint: buildManifest.loaderEntryPoint,
|
||||
},
|
||||
importErrors,
|
||||
},
|
||||
async (msg) => {
|
||||
process.send?.(msg);
|
||||
}
|
||||
).catch((err) => {
|
||||
if (err instanceof ZodSchemaParsedError) {
|
||||
return sendMessageInCatalog(
|
||||
indexerToWorkerMessages,
|
||||
"TASKS_FAILED_TO_PARSE",
|
||||
{ zodIssues: err.error.issues, tasks },
|
||||
async (msg) => {
|
||||
await process.send?.(msg);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.error("Failed to send TASKS_READY message", err);
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 10);
|
||||
});
|
||||
+4
-3
@@ -33,6 +33,7 @@ import { ZodIpcConnection } from "@trigger.dev/core/v3/zodIpc";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import sourceMapSupport from "source-map-support";
|
||||
import { VERSION } from "../version.js";
|
||||
import { env } from "std-env";
|
||||
|
||||
sourceMapSupport.install({
|
||||
handleUncaughtExceptions: false,
|
||||
@@ -90,7 +91,7 @@ async function importConfig(
|
||||
}
|
||||
|
||||
async function loadWorkerManifest() {
|
||||
const manifestContents = await readFile(process.env.TRIGGER_WORKER_MANIFEST_PATH!, "utf-8");
|
||||
const manifestContents = await readFile(env.TRIGGER_WORKER_MANIFEST_PATH!, "utf-8");
|
||||
const raw = JSON.parse(manifestContents);
|
||||
|
||||
return WorkerManifest.parse(raw);
|
||||
@@ -102,9 +103,9 @@ async function bootstrap() {
|
||||
const { config, handleError } = await importConfig(workerManifest.configPath);
|
||||
|
||||
const tracingSDK = new TracingSDK({
|
||||
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
|
||||
url: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
|
||||
instrumentations: config.instrumentations ?? [],
|
||||
diagLogLevel: (process.env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none",
|
||||
diagLogLevel: (env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none",
|
||||
forceFlushTimeoutMillis: 30_000,
|
||||
});
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export class UncaughtExceptionError extends Error {
|
||||
constructor(
|
||||
public readonly originalError: { name: string; message: string; stack?: string },
|
||||
public readonly origin: "uncaughtException" | "unhandledRejection"
|
||||
) {
|
||||
super(`Uncaught exception: ${originalError.message}`);
|
||||
|
||||
this.name = "UncaughtExceptionError";
|
||||
}
|
||||
}
|
||||
|
||||
export class TaskMetadataParseError extends Error {
|
||||
constructor(
|
||||
public readonly zodIssues: z.ZodIssue[],
|
||||
public readonly tasks: any
|
||||
) {
|
||||
super(`Failed to parse task metadata`);
|
||||
|
||||
this.name = "TaskMetadataParseError";
|
||||
}
|
||||
}
|
||||
|
||||
export class UnexpectedExitError extends Error {
|
||||
constructor(
|
||||
public code: number,
|
||||
public signal: NodeJS.Signals | null,
|
||||
public stderr: string | undefined
|
||||
) {
|
||||
super(`Unexpected exit with code ${code}`);
|
||||
|
||||
this.name = "UnexpectedExitError";
|
||||
}
|
||||
}
|
||||
|
||||
export class CleanupProcessError extends Error {
|
||||
constructor() {
|
||||
super("Cancelled");
|
||||
|
||||
this.name = "CleanupProcessError";
|
||||
}
|
||||
}
|
||||
|
||||
export class CancelledProcessError extends Error {
|
||||
constructor() {
|
||||
super("Cancelled");
|
||||
|
||||
this.name = "CancelledProcessError";
|
||||
}
|
||||
}
|
||||
|
||||
export class SigKillTimeoutProcessError extends Error {
|
||||
constructor() {
|
||||
super("Process kill timeout");
|
||||
|
||||
this.name = "SigKillTimeoutProcessError";
|
||||
}
|
||||
}
|
||||
|
||||
export class GracefulExitTimeoutError extends Error {
|
||||
constructor() {
|
||||
super("Graceful exit timeout");
|
||||
|
||||
this.name = "GracefulExitTimeoutError";
|
||||
}
|
||||
}
|
||||
|
||||
export function getFriendlyErrorMessage(
|
||||
code: number,
|
||||
signal: NodeJS.Signals | null,
|
||||
stderr: string | undefined,
|
||||
dockerMode = true
|
||||
) {
|
||||
const message = (text: string) => {
|
||||
if (signal) {
|
||||
return `[${signal}] ${text}`;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
if (code === 137) {
|
||||
if (dockerMode) {
|
||||
return message(
|
||||
"Process ran out of memory! Try choosing a machine preset with more memory for this task."
|
||||
);
|
||||
} else {
|
||||
// Note: containerState reason and message should be checked to clarify the error
|
||||
return message(
|
||||
"Process most likely ran out of memory, but we can't be certain. Try choosing a machine preset with more memory for this task."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (stderr?.includes("OOMErrorHandler")) {
|
||||
return message(
|
||||
"Process ran out of memory! Try choosing a machine preset with more memory for this task."
|
||||
);
|
||||
}
|
||||
|
||||
return message(`Process exited with code ${code}.`);
|
||||
}
|
||||
@@ -22,9 +22,9 @@ import {
|
||||
CancelledProcessError,
|
||||
CleanupProcessError,
|
||||
GracefulExitTimeoutError,
|
||||
SigKillTimeoutProcessError,
|
||||
UnexpectedExitError,
|
||||
} from "./errors.js";
|
||||
} from "@trigger.dev/core/v3/errors";
|
||||
import { env } from "std-env";
|
||||
|
||||
export type OnWaitForDurationMessage = InferSocketMessageSchema<
|
||||
typeof ExecutorToWorkerMessageCatalog,
|
||||
@@ -105,18 +105,18 @@ export class TaskRunProcess {
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
const { env, workerManifest, cwd, messageId } = this.options;
|
||||
const { env: $env, workerManifest, cwd, messageId } = this.options;
|
||||
|
||||
const fullEnv = {
|
||||
...(this.isTest ? { TRIGGER_LOG_LEVEL: "debug" } : {}),
|
||||
...env,
|
||||
...$env,
|
||||
OTEL_IMPORT_HOOK_INCLUDES: workerManifest.otelImportHook?.include?.join(","),
|
||||
// TODO: this will probably need to use something different for bun (maybe --preload?)
|
||||
NODE_OPTIONS: workerManifest.loaderEntryPoint
|
||||
? `--import=${workerManifest.loaderEntryPoint} ${
|
||||
env.NODE_OPTIONS ?? process.env.NODE_OPTIONS ?? ""
|
||||
env.NODE_OPTIONS ?? env.NODE_OPTIONS ?? ""
|
||||
}`
|
||||
: env.NODE_OPTIONS ?? process.env.NODE_OPTIONS ?? "",
|
||||
: env.NODE_OPTIONS ?? env.NODE_OPTIONS ?? "",
|
||||
};
|
||||
|
||||
logger.debug(`[${this.runId}] initializing task run process`, {
|
||||
@@ -125,7 +125,7 @@ export class TaskRunProcess {
|
||||
cwd,
|
||||
});
|
||||
|
||||
this._child = fork(workerManifest.executorEntryPoint, executorArgs(workerManifest), {
|
||||
this._child = fork(workerManifest.workerEntryPoint, executorArgs(workerManifest), {
|
||||
stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"],
|
||||
cwd,
|
||||
env: fullEnv,
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { execPathForRuntime } from "@trigger.dev/core/v3/build";
|
||||
import {
|
||||
BuildRuntime,
|
||||
indexerToWorkerMessages,
|
||||
WorkerManifest,
|
||||
} from "@trigger.dev/core/v3/schemas";
|
||||
import { parseMessageFromCatalog } from "@trigger.dev/core/v3/zodMessageHandler";
|
||||
import { fork } from "node:child_process";
|
||||
import { env } from "std-env";
|
||||
import {
|
||||
TaskIndexingImportError,
|
||||
TaskMetadataParseError,
|
||||
UncaughtExceptionError,
|
||||
} from "@trigger.dev/core/v3/errors";
|
||||
|
||||
export type IndexWorkerManifestOptions = {
|
||||
runtime: BuildRuntime;
|
||||
indexWorkerPath: string;
|
||||
buildManifestPath: string;
|
||||
nodeOptions?: string;
|
||||
env: Record<string, string | undefined>;
|
||||
cwd?: string;
|
||||
otelHookInclude?: string[];
|
||||
otelHookExclude?: string[];
|
||||
handleStdout?: (data: string) => void;
|
||||
handleStderr?: (data: string) => void;
|
||||
};
|
||||
|
||||
export async function indexWorkerManifest({
|
||||
runtime,
|
||||
indexWorkerPath,
|
||||
buildManifestPath,
|
||||
nodeOptions,
|
||||
env: $env,
|
||||
cwd,
|
||||
otelHookInclude,
|
||||
otelHookExclude,
|
||||
handleStderr,
|
||||
handleStdout,
|
||||
}: IndexWorkerManifestOptions) {
|
||||
return await new Promise<WorkerManifest>((resolve, reject) => {
|
||||
let resolved = false;
|
||||
|
||||
const child = fork(indexWorkerPath, {
|
||||
stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"],
|
||||
cwd,
|
||||
env: {
|
||||
...$env,
|
||||
OTEL_IMPORT_HOOK_INCLUDES: otelHookInclude?.join(","),
|
||||
OTEL_IMPORT_HOOK_EXCLUDES: otelHookExclude?.join(","),
|
||||
TRIGGER_BUILD_MANIFEST_PATH: buildManifestPath,
|
||||
NODE_OPTIONS: nodeOptions ? `${nodeOptions} ${env.NODE_OPTIONS ?? ""}` : env.NODE_OPTIONS,
|
||||
},
|
||||
execPath: execPathForRuntime(runtime),
|
||||
});
|
||||
|
||||
// Set a timeout to kill the child process if it doesn't respond
|
||||
const timeout = setTimeout(() => {
|
||||
if (resolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolved = true;
|
||||
child.kill();
|
||||
reject(new Error("Worker timed out"));
|
||||
}, 20_000);
|
||||
|
||||
child.on("message", async (msg: any) => {
|
||||
const message = parseMessageFromCatalog(msg, indexerToWorkerMessages);
|
||||
|
||||
switch (message.type) {
|
||||
case "INDEX_COMPLETE": {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
if (message.payload.importErrors.length > 0) {
|
||||
reject(
|
||||
new TaskIndexingImportError(message.payload.importErrors, message.payload.manifest)
|
||||
);
|
||||
} else {
|
||||
resolve(message.payload.manifest);
|
||||
}
|
||||
child.kill();
|
||||
break;
|
||||
}
|
||||
case "TASKS_FAILED_TO_PARSE": {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
reject(new TaskMetadataParseError(message.payload.zodIssues, message.payload.tasks));
|
||||
child.kill();
|
||||
break;
|
||||
}
|
||||
case "UNCAUGHT_EXCEPTION": {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
reject(new UncaughtExceptionError(message.payload.error, message.payload.origin));
|
||||
child.kill();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
child.on("exit", (code) => {
|
||||
if (!resolved) {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
reject(new Error(`Worker exited with code ${code}`));
|
||||
}
|
||||
});
|
||||
|
||||
child.stdout?.on("data", (data) => {
|
||||
handleStdout?.(data.toString());
|
||||
});
|
||||
|
||||
child.stderr?.on("data", (data) => {
|
||||
handleStderr?.(data.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { BuildManifest, ImportTaskFileErrors, taskCatalog } from "@trigger.dev/core/v3";
|
||||
|
||||
export async function registerTasks(buildManifest: BuildManifest): Promise<ImportTaskFileErrors> {
|
||||
const importErrors: ImportTaskFileErrors = [];
|
||||
|
||||
for (const file of buildManifest.files) {
|
||||
const [error, module] = await tryImport(file.out);
|
||||
|
||||
if (error) {
|
||||
importErrors.push({
|
||||
file: file.entry,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name,
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const exportName of getExportNames(module)) {
|
||||
const task = module[exportName] ?? module.default?.[exportName];
|
||||
|
||||
if (!task) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (task[Symbol.for("trigger.dev/task")]) {
|
||||
if (taskCatalog.taskExists(task.id)) {
|
||||
taskCatalog.registerTaskFileMetadata(task.id, {
|
||||
exportName,
|
||||
filePath: file.entry,
|
||||
entryPoint: file.out,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return importErrors;
|
||||
}
|
||||
|
||||
type Result<T> = [Error | null, T | null];
|
||||
|
||||
async function tryImport(path: string): Promise<Result<any>> {
|
||||
try {
|
||||
const module = await import(path);
|
||||
|
||||
return [null, module];
|
||||
} catch (error) {
|
||||
return [error as Error, null];
|
||||
}
|
||||
}
|
||||
|
||||
function getExportNames(module: any) {
|
||||
const exports: string[] = [];
|
||||
|
||||
const exportKeys = Object.keys(module);
|
||||
|
||||
if (exportKeys.length === 0) {
|
||||
return exports;
|
||||
}
|
||||
|
||||
if (exportKeys.length === 1 && exportKeys[0] === "default") {
|
||||
return Object.keys(module.default);
|
||||
}
|
||||
|
||||
return exportKeys;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
@@ -10,18 +10,19 @@ import {
|
||||
} from "@opentelemetry/semantic-conventions";
|
||||
import { logger } from "../utilities/logger.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { env } from "std-env";
|
||||
|
||||
function initializeTracing(): NodeTracerProvider | undefined {
|
||||
if (
|
||||
process.argv.includes("--skip-telemetry") ||
|
||||
process.env.TRIGGER_DEV_SKIP_TELEMETRY || // only for backwards compat
|
||||
process.env.TRIGGER_TELEMETRY_DISABLED
|
||||
env.TRIGGER_DEV_SKIP_TELEMETRY || // only for backwards compat
|
||||
env.TRIGGER_TELEMETRY_DISABLED
|
||||
) {
|
||||
logger.debug("📉 Telemetry disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.OTEL_INTERNAL_DIAG_DEBUG) {
|
||||
if (env.OTEL_INTERNAL_DIAG_DEBUG) {
|
||||
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { log } from "@clack/prompts";
|
||||
import chalk from "chalk";
|
||||
import terminalLink, { Options as TerminalLinkOptions } from "terminal-link";
|
||||
import { hasTTY } from "std-env";
|
||||
|
||||
export const isInteractive = process.stdin.isTTY;
|
||||
export const isInteractive = hasTTY;
|
||||
|
||||
export const green = "#4FFF54";
|
||||
export const purple = "#735BF3";
|
||||
@@ -81,7 +82,7 @@ export function prettyError(header: string, body?: string, footer?: string) {
|
||||
.map((line) => `${indent}${line}`)
|
||||
.join("\n");
|
||||
|
||||
const prettyBody = withIndents(body);
|
||||
const prettyBody = withIndents(body?.trim());
|
||||
const prettyFooter = withIndents(footer);
|
||||
|
||||
log.error(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import dotenv from "dotenv";
|
||||
import { resolve } from "node:path";
|
||||
import { env } from "std-env";
|
||||
|
||||
export function resolveDotEnvVars(cwd?: string) {
|
||||
const result: { [key: string]: string } = {};
|
||||
@@ -11,7 +12,7 @@ export function resolveDotEnvVars(cwd?: string) {
|
||||
),
|
||||
});
|
||||
|
||||
process.env.TRIGGER_API_URL && (result.TRIGGER_API_URL = process.env.TRIGGER_API_URL);
|
||||
env.TRIGGER_API_URL && (result.TRIGGER_API_URL = env.TRIGGER_API_URL);
|
||||
|
||||
// remove TRIGGER_API_URL and TRIGGER_SECRET_KEY, since those should be coming from the worker
|
||||
delete result.TRIGGER_API_URL;
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { BuildTarget, TaskRunExecutionPayload, TaskRunExecutionResult } from "@trigger.dev/core/v3";
|
||||
import {
|
||||
BuildManifest,
|
||||
BuildTarget,
|
||||
TaskRunExecutionPayload,
|
||||
TaskRunExecutionResult,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { BackgroundWorker } from "../dev/backgroundWorker.js";
|
||||
|
||||
@@ -7,6 +12,7 @@ export type EventBusEvents = {
|
||||
buildStarted: [BuildTarget];
|
||||
workerSkipped: [];
|
||||
backgroundWorkerInitialized: [BackgroundWorker];
|
||||
backgroundWorkerIndexingError: [BuildManifest, Error];
|
||||
runStarted: [BackgroundWorker, TaskRunExecutionPayload];
|
||||
runCompleted: [BackgroundWorker, TaskRunExecutionPayload, TaskRunExecutionResult, number];
|
||||
};
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import { logger } from "./logger.js";
|
||||
|
||||
type VariableNames = "TRIGGER_API_URL" | "TRIGGER_SECRET_KEY" | "TRIGGER_LOG_LEVEL";
|
||||
|
||||
type DeprecatedNames = "";
|
||||
|
||||
/**
|
||||
* Create a function used to access an environment variable.
|
||||
*
|
||||
* This is not memoized to allow us to change the value at runtime, such as in testing.
|
||||
* A warning is shown if the client is using a deprecated version - but only once.
|
||||
*/
|
||||
export function getEnvironmentVariableFactory({
|
||||
variableName,
|
||||
deprecatedName,
|
||||
}: {
|
||||
variableName: VariableNames;
|
||||
deprecatedName?: DeprecatedNames;
|
||||
}): () => string | undefined;
|
||||
|
||||
/**
|
||||
* Create a function used to access an environment variable, with a default value.
|
||||
*
|
||||
* This is not memoized to allow us to change the value at runtime, such as in testing.
|
||||
* A warning is shown if the client is using a deprecated version - but only once.
|
||||
*/
|
||||
export function getEnvironmentVariableFactory({
|
||||
variableName,
|
||||
deprecatedName,
|
||||
defaultValue,
|
||||
}: {
|
||||
variableName: VariableNames;
|
||||
deprecatedName?: DeprecatedNames;
|
||||
defaultValue: () => string;
|
||||
}): () => string;
|
||||
|
||||
/**
|
||||
* Create a function used to access an environment variable.
|
||||
*
|
||||
* This is not memoized to allow us to change the value at runtime, such as in testing.
|
||||
* A warning is shown if the client is using a deprecated version - but only once.
|
||||
*/
|
||||
export function getEnvironmentVariableFactory({
|
||||
variableName,
|
||||
deprecatedName,
|
||||
defaultValue,
|
||||
}: {
|
||||
variableName: VariableNames;
|
||||
deprecatedName?: DeprecatedNames;
|
||||
defaultValue?: () => string;
|
||||
}): () => string | undefined {
|
||||
let hasWarned = false;
|
||||
return () => {
|
||||
if (process.env[variableName]) {
|
||||
return process.env[variableName];
|
||||
} else if (deprecatedName && process.env[deprecatedName]) {
|
||||
if (!hasWarned) {
|
||||
// Only show the warning once.
|
||||
hasWarned = true;
|
||||
logger.warn(
|
||||
`Using "${deprecatedName}" environment variable. This is deprecated. Please use "${variableName}", instead.`
|
||||
);
|
||||
}
|
||||
return process.env[deprecatedName];
|
||||
} else {
|
||||
return defaultValue?.();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import chalk from "chalk";
|
||||
import CLITable from "cli-table3";
|
||||
import { formatMessagesSync } from "esbuild";
|
||||
import type { Message } from "esbuild";
|
||||
import { getEnvironmentVariableFactory } from "./getEnvironmentVariableFactory.js";
|
||||
import { env } from "std-env";
|
||||
|
||||
export const LOGGER_LEVELS = {
|
||||
none: -1,
|
||||
error: 0,
|
||||
@@ -26,12 +27,8 @@ const LOGGER_LEVEL_FORMAT_TYPE_MAP = {
|
||||
debug: undefined,
|
||||
} as const;
|
||||
|
||||
const getLogLevelFromEnv = getEnvironmentVariableFactory({
|
||||
variableName: "TRIGGER_LOG_LEVEL",
|
||||
});
|
||||
|
||||
function getLoggerLevel(): LoggerLevel {
|
||||
const fromEnv = getLogLevelFromEnv()?.toLowerCase();
|
||||
const fromEnv = env.TRIGGER_LOG_LEVEL?.toLowerCase();
|
||||
if (fromEnv !== undefined) {
|
||||
if (fromEnv in LOGGER_LEVELS) return fromEnv as LoggerLevel;
|
||||
const expected = Object.keys(LOGGER_LEVELS)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { log, spinner as clackSpinner } from "@clack/prompts";
|
||||
import { isWindows as stdEnvIsWindows } from "std-env";
|
||||
|
||||
export const isWindows = process.platform === "win32";
|
||||
export const isWindows = stdEnvIsWindows;
|
||||
|
||||
export function escapeImportPath(path: string) {
|
||||
return isWindows ? path.replaceAll("\\", "\\\\") : path;
|
||||
|
||||
+275
-11
@@ -1,5 +1,8 @@
|
||||
import { z } from "zod";
|
||||
import { TaskRunError } from "./schemas/common.js";
|
||||
import { DeploymentErrorData } from "./schemas/api.js";
|
||||
import { ImportTaskFileErrors, WorkerManifest } from "./schemas/build.js";
|
||||
import { SerializedError, TaskRunError } from "./schemas/common.js";
|
||||
import { TaskMetadataFailedToParseData } from "./schemas/messages.js";
|
||||
|
||||
export class AbortTaskRunError extends Error {
|
||||
constructor(message: string) {
|
||||
@@ -60,14 +63,6 @@ export function createErrorTaskError(error: TaskRunError): any {
|
||||
}
|
||||
}
|
||||
|
||||
export const SerializedError = z.object({
|
||||
message: z.string(),
|
||||
name: z.string().optional(),
|
||||
stackTrace: z.string().optional(),
|
||||
});
|
||||
|
||||
export type SerializedError = z.infer<typeof SerializedError>;
|
||||
|
||||
export function createJsonErrorObject(error: TaskRunError): SerializedError {
|
||||
switch (error.type) {
|
||||
case "BUILT_IN_ERROR": {
|
||||
@@ -178,7 +173,7 @@ export function groupTaskMetadataIssuesByTask(tasks: any, issues: z.ZodIssue[])
|
||||
return acc;
|
||||
}
|
||||
|
||||
const taskIndex = issue.path[1];
|
||||
const taskIndex = issue.path[2];
|
||||
|
||||
if (typeof taskIndex !== "number") {
|
||||
return acc;
|
||||
@@ -190,7 +185,7 @@ export function groupTaskMetadataIssuesByTask(tasks: any, issues: z.ZodIssue[])
|
||||
return acc;
|
||||
}
|
||||
|
||||
const restOfPath = issue.path.slice(2);
|
||||
const restOfPath = issue.path.slice(3);
|
||||
|
||||
const taskId = task.id;
|
||||
const taskName = task.exportName;
|
||||
@@ -226,3 +221,272 @@ export function groupTaskMetadataIssuesByTask(tasks: any, issues: z.ZodIssue[])
|
||||
>
|
||||
);
|
||||
}
|
||||
|
||||
export class UncaughtExceptionError extends Error {
|
||||
constructor(
|
||||
public readonly originalError: { name: string; message: string; stack?: string },
|
||||
public readonly origin: "uncaughtException" | "unhandledRejection"
|
||||
) {
|
||||
super(`Uncaught exception: ${originalError.message}`);
|
||||
|
||||
this.name = "UncaughtExceptionError";
|
||||
}
|
||||
}
|
||||
|
||||
export class TaskMetadataParseError extends Error {
|
||||
constructor(
|
||||
public readonly zodIssues: z.ZodIssue[],
|
||||
public readonly tasks: any
|
||||
) {
|
||||
super(`Failed to parse task metadata`);
|
||||
|
||||
this.name = "TaskMetadataParseError";
|
||||
}
|
||||
}
|
||||
|
||||
export class TaskIndexingImportError extends Error {
|
||||
constructor(
|
||||
public readonly importErrors: ImportTaskFileErrors,
|
||||
public readonly manifest: WorkerManifest
|
||||
) {
|
||||
super(`Failed to import some task files`);
|
||||
|
||||
this.name = "TaskIndexingImportError";
|
||||
}
|
||||
}
|
||||
|
||||
export class UnexpectedExitError extends Error {
|
||||
constructor(
|
||||
public code: number,
|
||||
public signal: NodeJS.Signals | null,
|
||||
public stderr: string | undefined
|
||||
) {
|
||||
super(`Unexpected exit with code ${code}`);
|
||||
|
||||
this.name = "UnexpectedExitError";
|
||||
}
|
||||
}
|
||||
|
||||
export class CleanupProcessError extends Error {
|
||||
constructor() {
|
||||
super("Cancelled");
|
||||
|
||||
this.name = "CleanupProcessError";
|
||||
}
|
||||
}
|
||||
|
||||
export class CancelledProcessError extends Error {
|
||||
constructor() {
|
||||
super("Cancelled");
|
||||
|
||||
this.name = "CancelledProcessError";
|
||||
}
|
||||
}
|
||||
|
||||
export class SigKillTimeoutProcessError extends Error {
|
||||
constructor() {
|
||||
super("Process kill timeout");
|
||||
|
||||
this.name = "SigKillTimeoutProcessError";
|
||||
}
|
||||
}
|
||||
|
||||
export class GracefulExitTimeoutError extends Error {
|
||||
constructor() {
|
||||
super("Graceful exit timeout");
|
||||
|
||||
this.name = "GracefulExitTimeoutError";
|
||||
}
|
||||
}
|
||||
|
||||
export function getFriendlyErrorMessage(
|
||||
code: number,
|
||||
signal: NodeJS.Signals | null,
|
||||
stderr: string | undefined,
|
||||
dockerMode = true
|
||||
) {
|
||||
const message = (text: string) => {
|
||||
if (signal) {
|
||||
return `[${signal}] ${text}`;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
if (code === 137) {
|
||||
if (dockerMode) {
|
||||
return message(
|
||||
"Process ran out of memory! Try choosing a machine preset with more memory for this task."
|
||||
);
|
||||
} else {
|
||||
// Note: containerState reason and message should be checked to clarify the error
|
||||
return message(
|
||||
"Process most likely ran out of memory, but we can't be certain. Try choosing a machine preset with more memory for this task."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (stderr?.includes("OOMErrorHandler")) {
|
||||
return message(
|
||||
"Process ran out of memory! Try choosing a machine preset with more memory for this task."
|
||||
);
|
||||
}
|
||||
|
||||
return message(`Process exited with code ${code}.`);
|
||||
}
|
||||
|
||||
export function serializeIndexingError(error: unknown, stderr?: string): DeploymentErrorData {
|
||||
if (error instanceof TaskMetadataParseError) {
|
||||
return {
|
||||
name: "TaskMetadataParseError",
|
||||
message: "There was an error parsing the task metadata",
|
||||
stack: JSON.stringify({ zodIssues: error.zodIssues, tasks: error.tasks }),
|
||||
stderr,
|
||||
};
|
||||
} else if (error instanceof TaskIndexingImportError) {
|
||||
return {
|
||||
name: "TaskIndexingImportError",
|
||||
message: "There was an error importing task files",
|
||||
stack: JSON.stringify(error.importErrors),
|
||||
stderr,
|
||||
};
|
||||
} else if (error instanceof UncaughtExceptionError) {
|
||||
const originalError = error.originalError;
|
||||
|
||||
return {
|
||||
name: originalError.name,
|
||||
message: originalError.message,
|
||||
stack: originalError.stack,
|
||||
stderr,
|
||||
};
|
||||
} else if (error instanceof Error) {
|
||||
return {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
stderr,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: "UnknownError",
|
||||
message: String(error),
|
||||
stderr,
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareDeploymentError(
|
||||
errorData: DeploymentErrorData
|
||||
): DeploymentErrorData | undefined {
|
||||
if (!errorData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorData.name === "TaskMetadataParseError") {
|
||||
const errorJson = tryJsonParse(errorData.stack);
|
||||
|
||||
if (errorJson) {
|
||||
const parsedError = TaskMetadataFailedToParseData.safeParse(errorJson);
|
||||
|
||||
if (parsedError.success) {
|
||||
return {
|
||||
name: errorData.name,
|
||||
message: errorData.message,
|
||||
stack: createTaskMetadataFailedErrorStack(parsedError.data),
|
||||
stderr: errorData.stderr,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: errorData.name,
|
||||
message: errorData.message,
|
||||
stderr: errorData.stderr,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
name: errorData.name,
|
||||
message: errorData.message,
|
||||
stderr: errorData.stderr,
|
||||
};
|
||||
}
|
||||
} else if (errorData.name === "TaskIndexingImportError") {
|
||||
const errorJson = tryJsonParse(errorData.stack);
|
||||
|
||||
if (errorJson) {
|
||||
const parsedError = ImportTaskFileErrors.safeParse(errorJson);
|
||||
|
||||
if (parsedError.success) {
|
||||
return {
|
||||
name: errorData.name,
|
||||
message: errorData.message,
|
||||
stack: parsedError.data
|
||||
.map((error) => {
|
||||
return `x ${error.message} in ${error.file}`;
|
||||
})
|
||||
.join("\n"),
|
||||
stderr: errorData.stderr,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: errorData.name,
|
||||
message: errorData.message,
|
||||
stderr: errorData.stderr,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
name: errorData.name,
|
||||
message: errorData.message,
|
||||
stderr: errorData.stderr,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: errorData.name,
|
||||
message: errorData.message,
|
||||
stack: errorData.stack,
|
||||
stderr: errorData.stderr,
|
||||
};
|
||||
}
|
||||
|
||||
export function createTaskMetadataFailedErrorStack(
|
||||
data: z.infer<typeof TaskMetadataFailedToParseData>
|
||||
): string {
|
||||
const stack = [];
|
||||
|
||||
const groupedIssues = groupTaskMetadataIssuesByTask(data.tasks, data.zodIssues);
|
||||
|
||||
for (const key in groupedIssues) {
|
||||
const taskWithIssues = groupedIssues[key];
|
||||
|
||||
if (!taskWithIssues) {
|
||||
continue;
|
||||
}
|
||||
|
||||
stack.push("\n");
|
||||
stack.push(` ❯ ${taskWithIssues.exportName} in ${taskWithIssues.filePath}`);
|
||||
|
||||
for (const issue of taskWithIssues.issues) {
|
||||
if (issue.path) {
|
||||
stack.push(` x ${issue.path} ${issue.message}`);
|
||||
} else {
|
||||
stack.push(` x ${issue.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stack.join("\n");
|
||||
}
|
||||
|
||||
function tryJsonParse(data: string | undefined): any {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { BackgroundWorkerMetadata, ImageDetailsMetadata } from "./resources.js";
|
||||
import { BackgroundWorkerMetadata } from "./resources.js";
|
||||
import { QueueOptions } from "./schemas.js";
|
||||
import { SerializedError } from "../errors.js";
|
||||
import { SerializedError } from "./common.js";
|
||||
|
||||
export const WhoAmIResponseSchema = z.object({
|
||||
userId: z.string(),
|
||||
@@ -195,6 +195,8 @@ export const DeploymentErrorData = z.object({
|
||||
stderr: z.string().optional(),
|
||||
});
|
||||
|
||||
export type DeploymentErrorData = z.infer<typeof DeploymentErrorData>;
|
||||
|
||||
export const FailDeploymentRequestBody = z.object({
|
||||
error: DeploymentErrorData,
|
||||
});
|
||||
|
||||
@@ -33,9 +33,10 @@ export const BuildManifest = z.object({
|
||||
})
|
||||
),
|
||||
outputPath: z.string(),
|
||||
indexerEntryPoint: z.string(),
|
||||
executorEntryPoint: z.string(),
|
||||
workerEntryPoint: z.string().optional(),
|
||||
runWorkerEntryPoint: z.string(), // Dev & Deploy has a runWorkerEntryPoint
|
||||
runControllerEntryPoint: z.string().optional(), // Only deploy has a runControllerEntryPoint
|
||||
indexWorkerEntryPoint: z.string(), // Dev & Deploy has a indexWorkerEntryPoint
|
||||
indexControllerEntryPoint: z.string().optional(), // Only deploy has a indexControllerEntryPoint
|
||||
loaderEntryPoint: z.string().optional(),
|
||||
configPath: z.string(),
|
||||
externals: BuildExternal.array().optional(),
|
||||
@@ -73,8 +74,8 @@ export type IndexMessage = z.infer<typeof IndexMessage>;
|
||||
export const WorkerManifest = z.object({
|
||||
configPath: z.string(),
|
||||
tasks: TaskManifest.array(),
|
||||
executorEntryPoint: z.string(),
|
||||
workerEntryPoint: z.string().optional(),
|
||||
workerEntryPoint: z.string(),
|
||||
controllerEntryPoint: z.string().optional(),
|
||||
loaderEntryPoint: z.string().optional(),
|
||||
runtime: BuildRuntime,
|
||||
otelImportHook: z
|
||||
@@ -95,3 +96,16 @@ export const WorkerManifestMessage = z.object({
|
||||
});
|
||||
|
||||
export type WorkerManifestMessage = z.infer<typeof WorkerManifestMessage>;
|
||||
|
||||
export const ImportError = z.object({
|
||||
message: z.string(),
|
||||
file: z.string(),
|
||||
stack: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
});
|
||||
|
||||
export type ImportError = z.infer<typeof ImportError>;
|
||||
|
||||
export const ImportTaskFileErrors = z.array(ImportError);
|
||||
|
||||
export type ImportTaskFileErrors = z.infer<typeof ImportTaskFileErrors>;
|
||||
|
||||
@@ -280,3 +280,11 @@ export const BatchTaskRunExecutionResult = z.object({
|
||||
});
|
||||
|
||||
export type BatchTaskRunExecutionResult = z.infer<typeof BatchTaskRunExecutionResult>;
|
||||
|
||||
export const SerializedError = z.object({
|
||||
message: z.string(),
|
||||
name: z.string().optional(),
|
||||
stackTrace: z.string().optional(),
|
||||
});
|
||||
|
||||
export type SerializedError = z.infer<typeof SerializedError>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { WorkerManifest } from "./build.js";
|
||||
import { ImportTaskFileErrors, WorkerManifest } from "./build.js";
|
||||
import {
|
||||
MachinePreset,
|
||||
TaskRunExecution,
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
EnvironmentType,
|
||||
ProdTaskRunExecution,
|
||||
ProdTaskRunExecutionPayload,
|
||||
TaskManifest,
|
||||
TaskRunExecutionLazyAttemptPayload,
|
||||
WaitReason,
|
||||
} from "./schemas.js";
|
||||
@@ -129,6 +128,7 @@ export const indexerToWorkerMessages = {
|
||||
INDEX_COMPLETE: z.object({
|
||||
version: z.literal("v1").default("v1"),
|
||||
manifest: WorkerManifest,
|
||||
importErrors: ImportTaskFileErrors,
|
||||
}),
|
||||
TASKS_FAILED_TO_PARSE: TaskMetadataFailedToParseData,
|
||||
UNCAUGHT_EXCEPTION: UncaughtExceptionMessage,
|
||||
|
||||
Generated
+20
-6
@@ -978,6 +978,9 @@ importers:
|
||||
source-map-support:
|
||||
specifier: 0.5.21
|
||||
version: 0.5.21
|
||||
std-env:
|
||||
specifier: ^3.7.0
|
||||
version: 3.7.0
|
||||
terminal-link:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@@ -1360,6 +1363,9 @@ importers:
|
||||
'@sindresorhus/slugify':
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
'@t3-oss/env-core':
|
||||
specifier: ^0.11.0
|
||||
version: 0.11.0(typescript@5.5.4)(zod@3.23.8)
|
||||
'@t3-oss/env-nextjs':
|
||||
specifier: ^0.10.1
|
||||
version: 0.10.1(typescript@5.5.4)(zod@3.23.8)
|
||||
@@ -12287,6 +12293,19 @@ packages:
|
||||
zod: 3.23.8
|
||||
dev: false
|
||||
|
||||
/@t3-oss/env-core@0.11.0(typescript@5.5.4)(zod@3.23.8):
|
||||
resolution: {integrity: sha512-PSalC5bG0a7XbyoLydiQdAnx3gICX6IQNctvh+TyLrdFxsxgocdj9Ui7sd061UlBzi+z4aIGjnem1kZx9QtUgQ==}
|
||||
peerDependencies:
|
||||
typescript: '>=5.0.0'
|
||||
zod: ^3.0.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
typescript: 5.5.4
|
||||
zod: 3.23.8
|
||||
dev: false
|
||||
|
||||
/@t3-oss/env-nextjs@0.10.1(typescript@5.5.4)(zod@3.23.8):
|
||||
resolution: {integrity: sha512-iy2qqJLnFh1RjEWno2ZeyTu0ufomkXruUsOZludzDIroUabVvHsrSjtkHqwHp1/pgPUzN3yBRHMILW162X7x2Q==}
|
||||
peerDependencies:
|
||||
@@ -25489,13 +25508,8 @@ packages:
|
||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
/std-env@3.3.2:
|
||||
resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==}
|
||||
dev: true
|
||||
|
||||
/std-env@3.7.0:
|
||||
resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==}
|
||||
dev: true
|
||||
|
||||
/stoppable@1.1.0:
|
||||
resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==}
|
||||
@@ -27761,7 +27775,7 @@ packages:
|
||||
pathe: 1.1.0
|
||||
picocolors: 1.0.0
|
||||
source-map: 0.6.1
|
||||
std-env: 3.3.2
|
||||
std-env: 3.7.0
|
||||
strip-literal: 1.0.1
|
||||
tinybench: 2.3.1
|
||||
tinypool: 0.3.1
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"@react-email/components": "^0.0.17",
|
||||
"@react-email/render": "^0.0.7",
|
||||
"@sindresorhus/slugify": "^2.2.1",
|
||||
"@t3-oss/env-core": "^0.11.0",
|
||||
"@t3-oss/env-nextjs": "^0.10.1",
|
||||
"@traceloop/instrumentation-openai": "^0.10.0",
|
||||
"@trigger.dev/sdk": "workspace:*",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createEnv } from "@t3-oss/env-core";
|
||||
import { z } from "zod";
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
UPLOADTHING_SECRET: z.string(),
|
||||
UPLOADTHING_APP_ID: z.string(),
|
||||
OPENAI_API_KEY: z.string(),
|
||||
},
|
||||
|
||||
runtimeEnv: process.env,
|
||||
emptyStringAsUndefined: true,
|
||||
});
|
||||
@@ -1,9 +1,10 @@
|
||||
import { env } from "@/env.js";
|
||||
import { logger, task } from "@trigger.dev/sdk/v3";
|
||||
|
||||
import OpenAI from "openai";
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
apiKey: env.OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
export const openaiTask = task({
|
||||
|
||||
Reference in New Issue
Block a user