v3: better task metadata errors (#991)
* v3: better handle task metadata parse errors, and display nicely formatted errors (dev, deploy, UI) * Add changeset
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"trigger.dev": patch
|
||||
"@trigger.dev/core": patch
|
||||
---
|
||||
|
||||
better handle task metadata parse errors, and display nicely formatted errors
|
||||
Vendored
+1
-1
@@ -41,7 +41,7 @@
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"name": "Debug V3 Deploy CLI",
|
||||
"command": "pnpm exec trigger.dev deploy --skip-deploy",
|
||||
"command": "pnpm exec trigger.dev deploy",
|
||||
"cwd": "${workspaceFolder}/references/v3-catalog",
|
||||
"sourceMaps": true
|
||||
},
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import {
|
||||
DeploymentErrorData,
|
||||
TaskMetadataFailedToParseData,
|
||||
groupTaskMetadataIssuesByTask,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import { WorkerDeployment, WorkerDeploymentStatus } from "@trigger.dev/database";
|
||||
import { z } from "zod";
|
||||
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 class DeploymentPresenter {
|
||||
@@ -51,6 +58,7 @@ export class DeploymentPresenter {
|
||||
id: true,
|
||||
shortCode: true,
|
||||
version: true,
|
||||
errorData: true,
|
||||
environment: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -120,7 +128,81 @@ export class DeploymentPresenter {
|
||||
userName: getUsername(deployment.environment.orgMember?.user),
|
||||
},
|
||||
deployedBy: deployment.triggeredBy,
|
||||
errorData: this.#prepareErrorData(deployment.errorData),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#prepareErrorData(errorData: WorkerDeployment["errorData"]) {
|
||||
if (!errorData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedErrorData = DeploymentErrorData.safeParse(errorData);
|
||||
|
||||
if (!parsedErrorData.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),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
name: parsedErrorData.data.name,
|
||||
message: parsedErrorData.data.message,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
name: parsedErrorData.data.name,
|
||||
message: parsedErrorData.data.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: parsedErrorData.data.name,
|
||||
message: parsedErrorData.data.message,
|
||||
stack: parsedErrorData.data.stack,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
+21
@@ -3,6 +3,7 @@ import { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { ExitIcon } from "~/assets/icons/ExitIcon";
|
||||
import { UserAvatar } from "~/components/UserProfilePhoto";
|
||||
import { CodeBlock } from "~/components/code/CodeBlock";
|
||||
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
|
||||
import { Badge } from "~/components/primitives/Badge";
|
||||
import { LinkButton } from "~/components/primitives/Buttons";
|
||||
@@ -156,6 +157,26 @@ export default function Page() {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
) : deployment.errorData ? (
|
||||
<div className="flex flex-col">
|
||||
{deployment.errorData.stack ? (
|
||||
<CodeBlock
|
||||
language="markdown"
|
||||
rowTitle={deployment.errorData.message}
|
||||
code={deployment.errorData.stack}
|
||||
maxLines={20}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
<Paragraph
|
||||
variant="base/bright"
|
||||
className="w-full border-b border-grid-dimmed py-2.5"
|
||||
>
|
||||
{deployment.errorData.message}
|
||||
</Paragraph>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export function safeJsonParse(json: string): unknown {
|
||||
export function safeJsonParse(json?: string): unknown {
|
||||
if (!json) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (e) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { marqs, sanitizeQueueName } from "~/v3/marqs/index.server";
|
||||
import { calculateNextBuildVersion } from "../utils/calculateNextBuildVersion";
|
||||
import { BaseService } from "./baseService.server";
|
||||
import { projectPubSub } from "./projectPubSub.server";
|
||||
import { env } from "~/env.server";
|
||||
|
||||
export class CreateBackgroundWorkerService extends BaseService {
|
||||
public async call(
|
||||
@@ -110,7 +111,7 @@ export class CreateBackgroundWorkerService extends BaseService {
|
||||
export async function createBackgroundTasks(
|
||||
tasks: TaskResource[],
|
||||
worker: BackgroundWorker,
|
||||
env: AuthenticatedEnvironment,
|
||||
environment: AuthenticatedEnvironment,
|
||||
prisma: PrismaClientOrTransaction
|
||||
) {
|
||||
for (const task of tasks) {
|
||||
@@ -137,6 +138,18 @@ export async function createBackgroundTasks(
|
||||
queueName = sanitizeQueueName(`task/${task.id}`);
|
||||
}
|
||||
|
||||
const concurrencyLimit =
|
||||
typeof task.queue?.concurrencyLimit === "number"
|
||||
? Math.max(
|
||||
Math.min(
|
||||
task.queue.concurrencyLimit,
|
||||
environment.maximumConcurrencyLimit,
|
||||
environment.organization.maximumConcurrencyLimit
|
||||
),
|
||||
0
|
||||
)
|
||||
: null;
|
||||
|
||||
const taskQueue = await prisma.taskQueue.upsert({
|
||||
where: {
|
||||
runtimeEnvironmentId_name: {
|
||||
@@ -145,13 +158,13 @@ export async function createBackgroundTasks(
|
||||
},
|
||||
},
|
||||
update: {
|
||||
concurrencyLimit: task.queue?.concurrencyLimit,
|
||||
concurrencyLimit,
|
||||
rateLimit: task.queue?.rateLimit,
|
||||
},
|
||||
create: {
|
||||
friendlyId: generateFriendlyId("queue"),
|
||||
name: queueName,
|
||||
concurrencyLimit: task.queue?.concurrencyLimit,
|
||||
concurrencyLimit,
|
||||
runtimeEnvironmentId: worker.runtimeEnvironmentId,
|
||||
projectId: worker.projectId,
|
||||
rateLimit: task.queue?.rateLimit,
|
||||
@@ -160,7 +173,11 @@ export async function createBackgroundTasks(
|
||||
});
|
||||
|
||||
if (taskQueue.concurrencyLimit) {
|
||||
await marqs?.updateQueueConcurrencyLimits(env, taskQueue.name, taskQueue.concurrencyLimit);
|
||||
await marqs?.updateQueueConcurrencyLimits(
|
||||
environment,
|
||||
taskQueue.name,
|
||||
taskQueue.concurrencyLimit
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { depot } from "@depot/cli";
|
||||
import { context, trace } from "@opentelemetry/api";
|
||||
import {
|
||||
ResolvedConfig,
|
||||
TaskMetadataFailedToParseData,
|
||||
detectDependencyVersion,
|
||||
flattenAttributes,
|
||||
recordSpanException,
|
||||
@@ -49,9 +50,11 @@ import { bundleDependenciesPlugin, workerSetupImportConfigPlugin } from "../util
|
||||
import { chalkError, chalkPurple, chalkWarning } from "../utilities/cliOutput";
|
||||
import {
|
||||
logESMRequireError,
|
||||
logTaskMetadataParseError,
|
||||
parseBuildErrorStack,
|
||||
parseNpmInstallError,
|
||||
} from "../utilities/deployErrors";
|
||||
import { safeJsonParse } from "../utilities/safeJsonParse";
|
||||
|
||||
const DeployCommandOptions = CommonCommandOptions.extend({
|
||||
skipTypecheck: z.boolean().default(false),
|
||||
@@ -401,6 +404,24 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
|
||||
}
|
||||
case "FAILED": {
|
||||
if (finishedDeployment.errorData) {
|
||||
if (finishedDeployment.errorData.name === "TaskMetadataParseError") {
|
||||
const errorJson = safeJsonParse(finishedDeployment.errorData.stack);
|
||||
|
||||
if (errorJson) {
|
||||
const parsedError = TaskMetadataFailedToParseData.safeParse(errorJson);
|
||||
|
||||
if (parsedError.success) {
|
||||
deploymentSpinner.stop(`Deployment encountered an error. ${deploymentLink}`);
|
||||
|
||||
logTaskMetadataParseError(parsedError.data.zodIssues, parsedError.data.tasks);
|
||||
|
||||
throw new SkipLoggingError(
|
||||
`Deployment encountered an error: ${finishedDeployment.errorData.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const parsedError = finishedDeployment.errorData.stack
|
||||
? parseBuildErrorStack(finishedDeployment.errorData)
|
||||
: finishedDeployment.errorData.message;
|
||||
|
||||
@@ -38,11 +38,12 @@ import {
|
||||
import { logger } from "../utilities/logger.js";
|
||||
import { isLoggedIn } from "../utilities/session.js";
|
||||
import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles";
|
||||
import { UncaughtExceptionError } from "../workers/common/errors";
|
||||
import { TaskMetadataParseError, UncaughtExceptionError } from "../workers/common/errors";
|
||||
import { BackgroundWorker, BackgroundWorkerCoordinator } from "../workers/dev/backgroundWorker.js";
|
||||
import { runtimeCheck } from "../utilities/runtimeCheck";
|
||||
import {
|
||||
logESMRequireError,
|
||||
logTaskMetadataParseError,
|
||||
parseBuildErrorStack,
|
||||
parseNpmInstallError,
|
||||
} from "../utilities/deployErrors";
|
||||
@@ -548,7 +549,10 @@ function useDev({
|
||||
backgroundWorker
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof UncaughtExceptionError) {
|
||||
if (e instanceof TaskMetadataParseError) {
|
||||
logTaskMetadataParseError(e.zodIssues, e.tasks);
|
||||
return;
|
||||
} else if (e instanceof UncaughtExceptionError) {
|
||||
const parsedBuildError = parseBuildErrorStack(e.originalError);
|
||||
|
||||
if (typeof parsedBuildError !== "string") {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import chalk from "chalk";
|
||||
import { relative } from "node:path";
|
||||
import { chalkError, chalkPurple, chalkGrey, chalkGreen } from "./cliOutput";
|
||||
import { chalkError, chalkPurple, chalkGrey, chalkGreen, chalkWarning } from "./cliOutput";
|
||||
import { logger } from "./logger";
|
||||
import { ReadConfigResult } from "./configFiles";
|
||||
import { TaskMetadataParseError } from "../workers/common/errors";
|
||||
import { z } from "zod";
|
||||
import { groupTaskMetadataIssuesByTask } from "@trigger.dev/core/v3";
|
||||
|
||||
export type ESMRequireError = {
|
||||
type: "esm-require-error";
|
||||
@@ -144,3 +147,35 @@ export function parseNpmInstallError(error: unknown): NpmInstallError {
|
||||
|
||||
return "Unknown error";
|
||||
}
|
||||
|
||||
export function logTaskMetadataParseError(zodIssues: z.ZodIssue[], tasks: any) {
|
||||
logger.log(
|
||||
`\n${chalkError("X Error:")} Failed to start. The following ${
|
||||
zodIssues.length === 1 ? "task issue was" : "task issues were"
|
||||
} found:`
|
||||
);
|
||||
|
||||
const groupedIssues = groupTaskMetadataIssuesByTask(tasks, zodIssues);
|
||||
|
||||
for (const key in groupedIssues) {
|
||||
const taskWithIssues = groupedIssues[key];
|
||||
|
||||
if (!taskWithIssues) {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.log(
|
||||
`\n ${chalkWarning("❯")} ${taskWithIssues.exportName} ${chalkGrey("in")} ${
|
||||
taskWithIssues.filePath
|
||||
}`
|
||||
);
|
||||
|
||||
for (const issue of taskWithIssues.issues) {
|
||||
if (issue.path) {
|
||||
logger.log(` ${chalkError("x")} ${issue.path} ${chalkGrey(issue.message)}`);
|
||||
} else {
|
||||
logger.log(` ${chalkError("x")} ${chalkGrey(issue.message)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export function safeJsonParse(json?: string): unknown {
|
||||
if (!json) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export class UncaughtExceptionError extends Error {
|
||||
constructor(
|
||||
public readonly originalError: { name: string; message: string; stack?: string },
|
||||
@@ -8,3 +10,14 @@ export class UncaughtExceptionError extends Error {
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
import { safeDeleteFileSync } from "../../utilities/fileSystem.js";
|
||||
import { installPackages } from "../../utilities/installPackages.js";
|
||||
import { logger } from "../../utilities/logger.js";
|
||||
import { UncaughtExceptionError } from "../common/errors.js";
|
||||
import { TaskMetadataParseError, UncaughtExceptionError } from "../common/errors.js";
|
||||
|
||||
export type CurrentWorkers = BackgroundWorkerCoordinator["currentWorkers"];
|
||||
export class BackgroundWorkerCoordinator {
|
||||
@@ -352,6 +352,11 @@ export class BackgroundWorker {
|
||||
resolved = true;
|
||||
reject(new UncaughtExceptionError(message.payload.error, message.payload.origin));
|
||||
child.kill();
|
||||
} else if (message.type === "TASKS_FAILED_TO_PARSE") {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
reject(new TaskMetadataParseError(message.payload.zodIssues, message.payload.tasks));
|
||||
child.kill();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
logLevels,
|
||||
LogLevel,
|
||||
getEnvVar,
|
||||
ZodSchemaParsedError,
|
||||
} from "@trigger.dev/core/v3";
|
||||
|
||||
__WORKER_SETUP__;
|
||||
@@ -219,8 +220,14 @@ process.on("message", async (msg: any) => {
|
||||
await handler.handleMessage(msg);
|
||||
});
|
||||
|
||||
sender.send("TASKS_READY", { tasks: getTaskMetadata() }).catch((err) => {
|
||||
console.error("Failed to send TASKS_READY message", err);
|
||||
const TASK_METADATA = getTaskMetadata();
|
||||
|
||||
sender.send("TASKS_READY", { tasks: TASK_METADATA }).catch((err) => {
|
||||
if (err instanceof ZodSchemaParsedError) {
|
||||
sender.send("TASKS_FAILED_TO_PARSE", { zodIssues: err.error.issues, tasks: TASK_METADATA });
|
||||
} else {
|
||||
console.error("Failed to send TASKS_READY message", err);
|
||||
}
|
||||
});
|
||||
|
||||
process.title = "trigger-dev-worker";
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "@trigger.dev/core/v3";
|
||||
import { Evt } from "evt";
|
||||
import { ChildProcess, fork } from "node:child_process";
|
||||
import { UncaughtExceptionError } from "../common/errors";
|
||||
import { TaskMetadataParseError, UncaughtExceptionError } from "../common/errors";
|
||||
|
||||
class UnexpectedExitError extends Error {
|
||||
constructor(public code: number) {
|
||||
@@ -149,6 +149,14 @@ export class ProdBackgroundWorker {
|
||||
child.kill();
|
||||
}
|
||||
},
|
||||
TASKS_FAILED_TO_PARSE: async (message) => {
|
||||
if (!resolved) {
|
||||
clearTimeout(timeout);
|
||||
resolved = true;
|
||||
reject(new TaskMetadataParseError(message.zodIssues, message.tasks));
|
||||
child.kill();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { readFile } from "node:fs/promises";
|
||||
import { createServer } from "node:http";
|
||||
import { z } from "zod";
|
||||
import { ProdBackgroundWorker } from "./backgroundWorker";
|
||||
import { UncaughtExceptionError } from "../common/errors";
|
||||
import { TaskMetadataParseError, UncaughtExceptionError } from "../common/errors";
|
||||
import { setTimeout } from "node:timers/promises";
|
||||
|
||||
declare const __PROJECT_CONFIG__: Config;
|
||||
@@ -416,7 +416,19 @@ class ProdWorker {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof UncaughtExceptionError) {
|
||||
if (e instanceof TaskMetadataParseError) {
|
||||
logger.error("tasks metadata parse error", { message: e.zodIssues, tasks: e.tasks });
|
||||
|
||||
socket.emit("INDEXING_FAILED", {
|
||||
version: "v1",
|
||||
deploymentId: this.deploymentId,
|
||||
error: {
|
||||
name: "TaskMetadataParseError",
|
||||
message: "There was an error parsing the task metadata",
|
||||
stack: JSON.stringify({ zodIssues: e.zodIssues, tasks: e.tasks }),
|
||||
},
|
||||
});
|
||||
} else if (e instanceof UncaughtExceptionError) {
|
||||
logger.error("uncaught exception", { message: e.originalError.message });
|
||||
|
||||
socket.emit("INDEXING_FAILED", {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getEnvVar,
|
||||
logLevels,
|
||||
LogLevel,
|
||||
ZodSchemaParsedError,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import "source-map-support/register.js";
|
||||
|
||||
@@ -219,8 +220,14 @@ const prodRuntimeManager = new ProdRuntimeManager(zodIpc, {
|
||||
|
||||
runtime.setGlobalRuntimeManager(prodRuntimeManager);
|
||||
|
||||
zodIpc.send("TASKS_READY", { tasks: getTaskMetadata() }).catch((err) => {
|
||||
console.error("Failed to send TASKS_READY message", err);
|
||||
const TASK_METADATA = getTaskMetadata();
|
||||
|
||||
zodIpc.send("TASKS_READY", { tasks: TASK_METADATA }).catch((err) => {
|
||||
if (err instanceof ZodSchemaParsedError) {
|
||||
zodIpc.send("TASKS_FAILED_TO_PARSE", { zodIssues: err.error.issues, tasks: TASK_METADATA });
|
||||
} else {
|
||||
console.error("Failed to send TASKS_READY message", err);
|
||||
}
|
||||
});
|
||||
|
||||
process.title = "trigger-prod-worker";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { TaskRunError } from "./schemas/common";
|
||||
import nodePath from "node:path";
|
||||
|
||||
@@ -95,3 +96,59 @@ function correctStackTraceLine(line: string, projectDir?: string) {
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
export function groupTaskMetadataIssuesByTask(tasks: any, issues: z.ZodIssue[]) {
|
||||
return issues.reduce(
|
||||
(acc, issue) => {
|
||||
if (issue.path.length === 0) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const taskIndex = issue.path[1];
|
||||
|
||||
if (typeof taskIndex !== "number") {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const task = tasks[taskIndex];
|
||||
|
||||
if (!task) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const restOfPath = issue.path.slice(2);
|
||||
|
||||
const taskId = task.id;
|
||||
const taskName = task.exportName;
|
||||
const filePath = task.filePath;
|
||||
|
||||
const key = taskIndex;
|
||||
|
||||
const existing = acc[key] ?? {
|
||||
id: taskId,
|
||||
exportName: taskName,
|
||||
filePath,
|
||||
issues: [] as Array<{ message: string; path?: string }>,
|
||||
};
|
||||
|
||||
existing.issues.push({
|
||||
message: issue.message,
|
||||
path: restOfPath.length === 0 ? undefined : restOfPath.join("."),
|
||||
});
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[key]: existing,
|
||||
};
|
||||
},
|
||||
{} as Record<
|
||||
number,
|
||||
{
|
||||
id: any;
|
||||
exportName: string;
|
||||
filePath: string;
|
||||
issues: Array<{ message: string; path?: string }>;
|
||||
}
|
||||
>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -153,6 +153,12 @@ export const InitializeDeploymentRequestBody = z.object({
|
||||
|
||||
export type InitializeDeploymentRequestBody = z.infer<typeof InitializeDeploymentRequestBody>;
|
||||
|
||||
export const DeploymentErrorData = z.object({
|
||||
name: z.string(),
|
||||
message: z.string(),
|
||||
stack: z.string().optional(),
|
||||
});
|
||||
|
||||
export const GetDeploymentResponseBody = z.object({
|
||||
id: z.string(),
|
||||
status: z.enum([
|
||||
@@ -168,14 +174,7 @@ export const GetDeploymentResponseBody = z.object({
|
||||
shortCode: z.string(),
|
||||
version: z.string(),
|
||||
imageReference: z.string().optional(),
|
||||
errorData: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
message: z.string(),
|
||||
stack: z.string().optional(),
|
||||
})
|
||||
.optional()
|
||||
.nullable(),
|
||||
errorData: DeploymentErrorData.optional().nullable(),
|
||||
worker: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { TaskRunExecution, TaskRunExecutionResult } from "./common";
|
||||
|
||||
export const EnvironmentType = z.enum(["PRODUCTION", "STAGING", "DEVELOPMENT", "PREVIEW"])
|
||||
export const EnvironmentType = z.enum(["PRODUCTION", "STAGING", "DEVELOPMENT", "PREVIEW"]);
|
||||
export type EnvironmentType = z.infer<typeof EnvironmentType>;
|
||||
|
||||
export const MachineCpu = z
|
||||
@@ -244,7 +244,7 @@ export const QueueOptions = z.object({
|
||||
/** An optional property that specifies the maximum number of concurrent run executions.
|
||||
*
|
||||
* If this property is omitted, the task can potentially use up the full concurrency of an environment. */
|
||||
concurrencyLimit: z.number().int().min(1).max(1000).optional(),
|
||||
concurrencyLimit: z.number().int().min(0).max(1000).optional(),
|
||||
/** @deprecated This feature is coming soon */
|
||||
rateLimit: RateLimitOptions.optional(),
|
||||
});
|
||||
@@ -278,6 +278,14 @@ export const UncaughtExceptionMessage = z.object({
|
||||
origin: z.enum(["uncaughtException", "unhandledRejection"]),
|
||||
});
|
||||
|
||||
export const TaskMetadataFailedToParseData = z.object({
|
||||
version: z.literal("v1").default("v1"),
|
||||
tasks: z.unknown(),
|
||||
zodIssues: z.custom<z.ZodIssue[]>((v) => {
|
||||
return Array.isArray(v) && v.every((issue) => typeof issue === "object" && "message" in issue);
|
||||
}),
|
||||
});
|
||||
|
||||
export const childToWorkerMessages = {
|
||||
TASK_RUN_COMPLETED: z.object({
|
||||
version: z.literal("v1").default("v1"),
|
||||
@@ -288,6 +296,7 @@ export const childToWorkerMessages = {
|
||||
version: z.literal("v1").default("v1"),
|
||||
tasks: TaskMetadataWithFilePath.array(),
|
||||
}),
|
||||
TASKS_FAILED_TO_PARSE: TaskMetadataFailedToParseData,
|
||||
TASK_HEARTBEAT: z.object({
|
||||
version: z.literal("v1").default("v1"),
|
||||
id: z.string(),
|
||||
@@ -323,6 +332,9 @@ export const ProdChildToWorkerMessages = {
|
||||
tasks: TaskMetadataWithFilePath.array(),
|
||||
}),
|
||||
},
|
||||
TASKS_FAILED_TO_PARSE: {
|
||||
message: TaskMetadataFailedToParseData,
|
||||
},
|
||||
TASK_HEARTBEAT: {
|
||||
message: z.object({
|
||||
version: z.literal("v1").default("v1"),
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ZodSocketMessageCatalogSchema,
|
||||
} from "./zodSocket";
|
||||
import { z } from "zod";
|
||||
import { ZodSchemaParsedError } from "./zodMessageHandler";
|
||||
|
||||
interface ZodIpcMessageSender<TEmitCatalog extends ZodSocketMessageCatalogSchema> {
|
||||
send<K extends GetSocketMessagesWithoutCallback<TEmitCatalog>>(
|
||||
@@ -272,7 +273,7 @@ export class ZodIpcConnection<
|
||||
const parsedPayload = schema.safeParse(payload);
|
||||
|
||||
if (!parsedPayload.success) {
|
||||
throw new Error(`Failed to parse message payload: ${JSON.stringify(parsedPayload.error)}`);
|
||||
throw new ZodSchemaParsedError(parsedPayload.error, payload);
|
||||
}
|
||||
|
||||
await this.#sendPacket({
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { z } from "zod";
|
||||
import { StructuredLogger } from "./zodNamespace";
|
||||
|
||||
export class ZodSchemaParsedError extends Error {
|
||||
constructor(
|
||||
public error: z.ZodError,
|
||||
public payload: unknown
|
||||
) {
|
||||
super(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export type ZodMessageValueSchema<TDiscriminatedUnion extends z.ZodDiscriminatedUnion<any, any>> =
|
||||
| z.ZodFirstPartySchemaTypes
|
||||
| TDiscriminatedUnion;
|
||||
@@ -160,7 +169,7 @@ export class ZodMessageSender<TMessageCatalog extends ZodMessageCatalogSchema> {
|
||||
const parsedPayload = schema.safeParse(payload);
|
||||
|
||||
if (!parsedPayload.success) {
|
||||
throw new Error(`Failed to parse message payload: ${JSON.stringify(parsedPayload.error)}`);
|
||||
throw new ZodSchemaParsedError(parsedPayload.error, payload);
|
||||
}
|
||||
|
||||
await this.#sender({ type, payload, version: "v1" });
|
||||
|
||||
Reference in New Issue
Block a user