From 059f1887f73229e131b440c52a2c271c70a4d732 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Sat, 17 Aug 2024 19:36:13 +0100 Subject: [PATCH] deployment WIP --- .vscode/launch.json | 16 + ...yments.$deploymentId.background-workers.ts | 73 ++ .../api.v1.deployments.$deploymentId.fail.ts | 52 ++ ...i.v1.deployments.$deploymentId.finalize.ts | 65 ++ .../api.v1.projects.$projectRef.$env.ts | 1 + .../services/createBackgroundWorker.server.ts | 6 +- ...createDeploymentBackgroundWorker.server.ts | 108 +++ .../app/v3/services/failDeployment.server.ts | 56 ++ .../v3/services/finalizeDeployment.server.ts | 122 +++ packages/cli-v3/package.json | 41 +- packages/cli-v3/src/apiClient.ts | 67 +- packages/cli-v3/src/build/bundle.ts | 13 +- packages/cli-v3/src/build/extensions.ts | 24 +- packages/cli-v3/src/build/packageModules.ts | 8 +- packages/cli-v3/src/cli/index.ts | 2 + packages/cli-v3/src/commands/deploy.ts | 593 +++++++++++++++ packages/cli-v3/src/commands/dev.tsx | 13 +- packages/cli-v3/src/commands/init.ts | 65 +- packages/cli-v3/src/commands/list-profiles.ts | 2 +- packages/cli-v3/src/commands/update.ts | 32 +- packages/cli-v3/src/config.ts | 9 + packages/cli-v3/src/deploy/buildImage.ts | 514 +++++++++++++ packages/cli-v3/src/deploy/logs.ts | 147 ++++ packages/cli-v3/src/dev/devSession.ts | 11 +- packages/cli-v3/src/dev/workerRuntime.ts | 37 +- .../cli-v3/src/entryPoints/deploy-indexer.ts | 237 ++++++ .../cli-v3/src/utilities/buildManifest.ts | 11 + packages/cli-v3/src/utilities/cliOutput.ts | 2 + .../src/utilities/getUserPackageManager.ts | 72 -- .../cli-v3/src/utilities/initialBanner.ts | 49 +- .../cli-v3/src/utilities/javascriptProject.ts | 706 ------------------ .../cli-v3/src/utilities/resolveEnvVars.ts | 64 -- packages/cli-v3/src/utilities/runtimeCheck.ts | 27 +- packages/cli-v3/src/utilities/session.ts | 8 +- packages/cli-v3/src/utilities/sourceFiles.ts | 73 ++ packages/core/src/v3/apiClient/core.ts | 17 +- packages/core/src/v3/apiClient/errors.ts | 26 + packages/core/src/v3/build/extensions.ts | 8 + packages/core/src/v3/build/resolvedConfig.ts | 4 +- packages/core/src/v3/extensions/prisma.ts | 8 +- .../core/src/v3/extensions/syncEnvVars.ts | 156 ++++ packages/core/src/v3/index.ts | 4 + packages/core/src/v3/schemas/api.ts | 24 +- packages/core/src/v3/schemas/build.ts | 10 + packages/core/src/v3/schemas/resources.ts | 6 +- packages/core/src/v3/types/index.ts | 2 +- pnpm-lock.yaml | 540 +++----------- references/v3-catalog/package.json | 1 + .../trigger/{javascript.js => javascript.cjs} | 0 references/v3-catalog/trigger.config.ts | 8 +- 50 files changed, 2669 insertions(+), 1471 deletions(-) create mode 100644 apps/webapp/app/routes/api.v1.deployments.$deploymentId.background-workers.ts create mode 100644 apps/webapp/app/routes/api.v1.deployments.$deploymentId.fail.ts create mode 100644 apps/webapp/app/routes/api.v1.deployments.$deploymentId.finalize.ts create mode 100644 apps/webapp/app/v3/services/createDeploymentBackgroundWorker.server.ts create mode 100644 apps/webapp/app/v3/services/failDeployment.server.ts create mode 100644 apps/webapp/app/v3/services/finalizeDeployment.server.ts create mode 100644 packages/cli-v3/src/commands/deploy.ts create mode 100644 packages/cli-v3/src/deploy/buildImage.ts create mode 100644 packages/cli-v3/src/deploy/logs.ts create mode 100644 packages/cli-v3/src/entryPoints/deploy-indexer.ts create mode 100644 packages/cli-v3/src/utilities/buildManifest.ts delete mode 100644 packages/cli-v3/src/utilities/getUserPackageManager.ts delete mode 100644 packages/cli-v3/src/utilities/javascriptProject.ts delete mode 100644 packages/cli-v3/src/utilities/resolveEnvVars.ts create mode 100644 packages/cli-v3/src/utilities/sourceFiles.ts create mode 100644 packages/core/src/v3/extensions/syncEnvVars.ts rename references/v3-catalog/src/trigger/{javascript.js => javascript.cjs} (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 5aafa6173..ea926d23a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -45,6 +45,22 @@ "cwd": "${workspaceFolder}/references/v3-catalog", "sourceMaps": true }, + { + "type": "node-terminal", + "request": "launch", + "name": "Debug V3 list-profiles CLI", + "command": "pnpm exec triggerdev list-profiles --log-level debug", + "cwd": "${workspaceFolder}/references/v3-catalog", + "sourceMaps": true + }, + { + "type": "node-terminal", + "request": "launch", + "name": "Debug V3 update CLI", + "command": "pnpm exec triggerdev update", + "cwd": "${workspaceFolder}/references/v3-catalog", + "sourceMaps": true + }, { "type": "node-terminal", "request": "launch", diff --git a/apps/webapp/app/routes/api.v1.deployments.$deploymentId.background-workers.ts b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.background-workers.ts new file mode 100644 index 000000000..1d8f29758 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.background-workers.ts @@ -0,0 +1,73 @@ +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; +import { CreateBackgroundWorkerRequestBody } from "@trigger.dev/core/v3"; +import { z } from "zod"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; +import { ServiceValidationError } from "~/v3/services/baseService.server"; +import { CreateDeclarativeScheduleError } from "~/v3/services/createBackgroundWorker.server"; +import { CreateDeploymentBackgroundWorkerService } from "~/v3/services/createDeploymentBackgroundWorker.server"; + +const ParamsSchema = z.object({ + deploymentId: z.string(), +}); + +export async function action({ request, params }: ActionFunctionArgs) { + // Ensure this is a POST request + if (request.method.toUpperCase() !== "POST") { + return { status: 405, body: "Method Not Allowed" }; + } + + const parsedParams = ParamsSchema.safeParse(params); + + if (!parsedParams.success) { + return json({ error: "Invalid params" }, { status: 400 }); + } + + // Next authenticate the request + const authenticationResult = await authenticateApiRequest(request); + + if (!authenticationResult) { + logger.info("Invalid or missing api key", { url: request.url }); + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const authenticatedEnv = authenticationResult.environment; + + const { deploymentId } = parsedParams.data; + + const rawBody = await request.json(); + const body = CreateBackgroundWorkerRequestBody.safeParse(rawBody); + + if (!body.success) { + return json({ error: "Invalid body", issues: body.error.issues }, { status: 400 }); + } + + const service = new CreateDeploymentBackgroundWorkerService(); + + try { + const backgroundWorker = await service.call(authenticatedEnv, deploymentId, body.data); + + if (!backgroundWorker) { + return json({ error: "Failed to create background worker" }, { status: 500 }); + } + + return json( + { + id: backgroundWorker.friendlyId, + version: backgroundWorker.version, + contentHash: backgroundWorker.contentHash, + }, + { status: 200 } + ); + } catch (e) { + logger.error("Failed to create background worker", { error: e }); + + if (e instanceof ServiceValidationError) { + return json({ error: e.message }, { status: 400 }); + } else if (e instanceof CreateDeclarativeScheduleError) { + return json({ error: e.message }, { status: 400 }); + } + + return json({ error: "Failed to create background worker" }, { status: 500 }); + } +} diff --git a/apps/webapp/app/routes/api.v1.deployments.$deploymentId.fail.ts b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.fail.ts new file mode 100644 index 000000000..5edea5636 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.fail.ts @@ -0,0 +1,52 @@ +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; +import { FailDeploymentRequestBody } from "@trigger.dev/core/v3"; +import { z } from "zod"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; +import { FailDeploymentService } from "~/v3/services/failDeployment.server"; + +const ParamsSchema = z.object({ + deploymentId: z.string(), +}); + +export async function action({ request, params }: ActionFunctionArgs) { + // Ensure this is a POST request + if (request.method.toUpperCase() !== "POST") { + return { status: 405, body: "Method Not Allowed" }; + } + + const parsedParams = ParamsSchema.safeParse(params); + + if (!parsedParams.success) { + return json({ error: "Invalid params" }, { status: 400 }); + } + + // Next authenticate the request + const authenticationResult = await authenticateApiRequest(request); + + if (!authenticationResult) { + logger.info("Invalid or missing api key", { url: request.url }); + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const authenticatedEnv = authenticationResult.environment; + + const { deploymentId } = parsedParams.data; + + const rawBody = await request.json(); + const body = FailDeploymentRequestBody.safeParse(rawBody); + + if (!body.success) { + return json({ error: "Invalid body", issues: body.error.issues }, { status: 400 }); + } + + const service = new FailDeploymentService(); + await service.call(authenticatedEnv, deploymentId, body.data); + + return json( + { + id: deploymentId, + }, + { status: 200 } + ); +} diff --git a/apps/webapp/app/routes/api.v1.deployments.$deploymentId.finalize.ts b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.finalize.ts new file mode 100644 index 000000000..9bd12e4bd --- /dev/null +++ b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.finalize.ts @@ -0,0 +1,65 @@ +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; +import { FinalizeDeploymentRequestBody } from "@trigger.dev/core/v3"; +import { z } from "zod"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; +import { ServiceValidationError } from "~/v3/services/baseService.server"; +import { FinalizeDeploymentService } from "~/v3/services/finalizeDeployment.server"; + +const ParamsSchema = z.object({ + deploymentId: z.string(), +}); + +export async function action({ request, params }: ActionFunctionArgs) { + // Ensure this is a POST request + if (request.method.toUpperCase() !== "POST") { + return { status: 405, body: "Method Not Allowed" }; + } + + const parsedParams = ParamsSchema.safeParse(params); + + if (!parsedParams.success) { + return json({ error: "Invalid params" }, { status: 400 }); + } + + // Next authenticate the request + const authenticationResult = await authenticateApiRequest(request); + + if (!authenticationResult) { + logger.info("Invalid or missing api key", { url: request.url }); + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const authenticatedEnv = authenticationResult.environment; + + const { deploymentId } = parsedParams.data; + + const rawBody = await request.json(); + const body = FinalizeDeploymentRequestBody.safeParse(rawBody); + + if (!body.success) { + return json({ error: "Invalid body", issues: body.error.issues }, { status: 400 }); + } + + try { + const service = new FinalizeDeploymentService(); + await service.call(authenticatedEnv, deploymentId, body.data); + + return json( + { + id: deploymentId, + }, + { status: 200 } + ); + } catch (error) { + if (error instanceof ServiceValidationError) { + return json({ error: error.message }, { status: 400 }); + } else if (error instanceof Error) { + logger.error("Error finalizing deployment", { error: error.message }); + return json({ error: `Internal server error: ${error.message}` }, { status: 500 }); + } else { + logger.error("Error finalizing deployment", { error: String(error) }); + return json({ error: "Internal server error" }, { status: 500 }); + } + } +} diff --git a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts index 90e41a4f5..bfcf174df 100644 --- a/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts +++ b/apps/webapp/app/routes/api.v1.projects.$projectRef.$env.ts @@ -89,6 +89,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { apiKey: runtimeEnv.apiKey, name: project.name, apiUrl: processEnv.APP_ORIGIN, + projectId: project.id, }; return json(result); diff --git a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts index 5712c81a3..478e82781 100644 --- a/apps/webapp/app/v3/services/createBackgroundWorker.server.ts +++ b/apps/webapp/app/v3/services/createBackgroundWorker.server.ts @@ -1,5 +1,5 @@ import { - BackgroundWorkerFileMetadata, + BackgroundWorkerSourceFileMetadata, CreateBackgroundWorkerRequestBody, TaskResource, } from "@trigger.dev/core/v3"; @@ -75,7 +75,7 @@ export class CreateBackgroundWorkerService extends BaseService { }); const tasksToBackgroundFiles = await createBackgroundFiles( - body.metadata.fileContents, + body.metadata.sourceFiles, backgroundWorker, environment, this._prisma @@ -401,7 +401,7 @@ export async function syncDeclarativeSchedules( } export async function createBackgroundFiles( - files: Array | undefined, + files: Array | undefined, worker: BackgroundWorker, environment: AuthenticatedEnvironment, prisma: PrismaClientOrTransaction diff --git a/apps/webapp/app/v3/services/createDeploymentBackgroundWorker.server.ts b/apps/webapp/app/v3/services/createDeploymentBackgroundWorker.server.ts new file mode 100644 index 000000000..751a79a8d --- /dev/null +++ b/apps/webapp/app/v3/services/createDeploymentBackgroundWorker.server.ts @@ -0,0 +1,108 @@ +import { CreateBackgroundWorkerRequestBody } from "@trigger.dev/core/v3"; +import type { BackgroundWorker } from "@trigger.dev/database"; +import { AuthenticatedEnvironment } from "~/services/apiAuth.server"; +import { generateFriendlyId } from "../friendlyIdentifiers"; +import { BaseService } from "./baseService.server"; +import { + createBackgroundFiles, + createBackgroundTasks, + syncDeclarativeSchedules, +} from "./createBackgroundWorker.server"; +import { TimeoutDeploymentService } from "./timeoutDeployment.server"; + +export class CreateDeploymentBackgroundWorkerService extends BaseService { + public async call( + environment: AuthenticatedEnvironment, + deploymentId: string, + body: CreateBackgroundWorkerRequestBody + ): Promise { + return this.traceWithEnv("call", environment, async (span) => { + span.setAttribute("deploymentId", deploymentId); + + const deployment = await this._prisma.workerDeployment.findUnique({ + where: { + friendlyId: deploymentId, + }, + }); + + if (!deployment) { + return; + } + + if (deployment.status !== "BUILDING") { + return; + } + + const backgroundWorker = await this._prisma.backgroundWorker.create({ + data: { + friendlyId: generateFriendlyId("worker"), + version: deployment.version, + runtimeEnvironmentId: environment.id, + projectId: environment.projectId, + metadata: body.metadata, + contentHash: body.metadata.contentHash, + cliVersion: body.metadata.cliPackageVersion, + sdkVersion: body.metadata.packageVersion, + supportsLazyAttempts: body.supportsLazyAttempts, + }, + }); + + try { + const tasksToBackgroundFiles = await createBackgroundFiles( + body.metadata.sourceFiles, + backgroundWorker, + environment, + this._prisma + ); + await createBackgroundTasks( + body.metadata.tasks, + backgroundWorker, + environment, + this._prisma, + tasksToBackgroundFiles + ); + await syncDeclarativeSchedules( + body.metadata.tasks, + backgroundWorker, + environment, + this._prisma + ); + } catch (error) { + const name = error instanceof Error ? error.name : "UnknownError"; + const message = error instanceof Error ? error.message : JSON.stringify(error); + + await this._prisma.workerDeployment.update({ + where: { + id: deployment.id, + }, + data: { + status: "FAILED", + failedAt: new Date(), + errorData: { + name, + message, + }, + }, + }); + + throw error; + } + + // Link the deployment with the background worker + await this._prisma.workerDeployment.update({ + where: { + id: deployment.id, + }, + data: { + status: "DEPLOYING", + workerId: backgroundWorker.id, + deployedAt: new Date(), + }, + }); + + await TimeoutDeploymentService.dequeue(deployment.id, this._prisma); + + return backgroundWorker; + }); + } +} diff --git a/apps/webapp/app/v3/services/failDeployment.server.ts b/apps/webapp/app/v3/services/failDeployment.server.ts new file mode 100644 index 000000000..a5d7fc9e8 --- /dev/null +++ b/apps/webapp/app/v3/services/failDeployment.server.ts @@ -0,0 +1,56 @@ +import { PerformDeploymentAlertsService } from "./alerts/performDeploymentAlerts.server"; +import { BaseService } from "./baseService.server"; +import { logger } from "~/services/logger.server"; +import { WorkerDeploymentStatus } from "@trigger.dev/database"; +import { FailDeploymentRequestBody } from "@trigger.dev/core/v3/schemas"; +import { AuthenticatedEnvironment } from "~/services/apiAuth.server"; + +const FINAL_DEPLOYMENT_STATUSES: WorkerDeploymentStatus[] = [ + "CANCELED", + "DEPLOYED", + "FAILED", + "TIMED_OUT", +]; + +export class FailDeploymentService extends BaseService { + public async call( + authenticatedEnv: AuthenticatedEnvironment, + id: string, + params: FailDeploymentRequestBody + ) { + const deployment = await this._prisma.workerDeployment.findUnique({ + where: { + friendlyId: id, + environmentId: authenticatedEnv.id, + }, + }); + + if (!deployment) { + logger.error("Worker deployment not found", { id }); + return; + } + + if (FINAL_DEPLOYMENT_STATUSES.includes(deployment.status)) { + logger.error("Worker deployment already in final state", { + id: deployment.id, + status: deployment.status, + }); + return; + } + + const failedDeployment = await this._prisma.workerDeployment.update({ + where: { + id: deployment.id, + }, + data: { + status: "FAILED", + failedAt: new Date(), + errorData: params.error, + }, + }); + + await PerformDeploymentAlertsService.enqueue(failedDeployment.id, this._prisma); + + return failedDeployment; + } +} diff --git a/apps/webapp/app/v3/services/finalizeDeployment.server.ts b/apps/webapp/app/v3/services/finalizeDeployment.server.ts new file mode 100644 index 000000000..c610f9122 --- /dev/null +++ b/apps/webapp/app/v3/services/finalizeDeployment.server.ts @@ -0,0 +1,122 @@ +import { FinalizeDeploymentRequestBody } from "@trigger.dev/core/v3/schemas"; +import { CURRENT_DEPLOYMENT_LABEL } from "~/consts"; +import { AuthenticatedEnvironment } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; +import { socketIo } from "../handleSocketIo.server"; +import { marqs } from "../marqs/index.server"; +import { registryProxy } from "../registryProxy.server"; +import { PerformDeploymentAlertsService } from "./alerts/performDeploymentAlerts.server"; +import { BaseService, ServiceValidationError } from "./baseService.server"; +import { ExecuteTasksWaitingForDeployService } from "./executeTasksWaitingForDeploy"; +import { projectPubSub } from "./projectPubSub.server"; + +export class FinalizeDeploymentService extends BaseService { + public async call( + authenticatedEnv: AuthenticatedEnvironment, + id: string, + body: FinalizeDeploymentRequestBody + ) { + const deployment = await this._prisma.workerDeployment.findUnique({ + where: { + friendlyId: id, + environmentId: authenticatedEnv.id, + }, + include: { + worker: { + include: { + tasks: true, + }, + }, + }, + }); + + if (!deployment) { + logger.error("Worker deployment not found", { id }); + return; + } + + if (!deployment.worker) { + logger.error("Worker deployment does not have a worker", { id }); + + // TODO: We need to fail the deployment here because it's not possible to deploy a worker without a worker + + throw new ServiceValidationError("Worker deployment does not have a worker"); + } + + if (deployment.status !== "DEPLOYING") { + logger.error("Worker deployment is not in DEPLOYING status", { id }); + throw new ServiceValidationError("Worker deployment is not in DEPLOYING status"); + } + + // Link the deployment with the background worker + const finalizedDeployment = await this._prisma.workerDeployment.update({ + where: { + id: deployment.id, + }, + data: { + status: "DEPLOYED", + deployedAt: new Date(), + imageReference: + registryProxy && body.selfHosted !== true + ? registryProxy.rewriteImageReference(body.imageReference) + : body.imageReference, + }, + }); + + //set this deployment as the current deployment for this environment + await this._prisma.workerDeploymentPromotion.upsert({ + where: { + environmentId_label: { + environmentId: authenticatedEnv.id, + label: CURRENT_DEPLOYMENT_LABEL, + }, + }, + create: { + deploymentId: finalizedDeployment.id, + environmentId: authenticatedEnv.id, + label: CURRENT_DEPLOYMENT_LABEL, + }, + update: { + deploymentId: finalizedDeployment.id, + }, + }); + + try { + //send a notification that a new worker has been created + await projectPubSub.publish( + `project:${authenticatedEnv.projectId}:env:${authenticatedEnv.id}`, + "WORKER_CREATED", + { + environmentId: authenticatedEnv.id, + environmentType: authenticatedEnv.type, + createdAt: authenticatedEnv.createdAt, + taskCount: deployment.worker.tasks.length, + type: "deployed", + } + ); + + await marqs?.updateEnvConcurrencyLimits(authenticatedEnv); + } catch (err) { + logger.error("Failed to publish WORKER_CREATED event", { err }); + } + + if (deployment.imageReference) { + socketIo.providerNamespace.emit("PRE_PULL_DEPLOYMENT", { + version: "v1", + imageRef: deployment.imageReference, + shortCode: deployment.shortCode, + // identifiers + deploymentId: deployment.id, + envId: authenticatedEnv.id, + envType: authenticatedEnv.type, + orgId: authenticatedEnv.organizationId, + projectId: deployment.projectId, + }); + } + + await ExecuteTasksWaitingForDeployService.enqueue(deployment.worker.id, this._prisma); + await PerformDeploymentAlertsService.enqueue(deployment.id, this._prisma); + + return finalizedDeployment; + } +} diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index 03f5589e3..ce3eabc33 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -46,27 +46,21 @@ }, "devDependencies": { "@types/gradient-string": "^1.1.2", - "@types/mock-fs": "^4.13.1", "@types/node": "20.14.14", - "@types/object-hash": "^3.0.6", + "@types/object-hash": "3.0.6", "@types/react": "^18.2.48", "@types/resolve": "^1.20.6", "@types/semver": "^7.3.13", "@types/source-map-support": "0.5.10", "@types/ws": "^8.5.3", "cpy-cli": "^5.0.0", - "nodemon": "^3.0.1", - "npm-run-all": "^4.1.5", - "open": "^10.0.3", "p-retry": "^6.1.0", "rimraf": "^5.0.7", "ts-essentials": "10.0.1", "tshy": "^3.0.2", "tsx": "4.17.0", - "type-fest": "^3.6.0", "typescript": "^5.5.4", - "vitest": "^1.6.0", - "xdg-app-paths": "^8.3.0" + "vitest": "^1.6.0" }, "scripts": { "typecheck": "tsc", @@ -78,9 +72,8 @@ "update-version": "tsx ../../scripts/updateVersion.ts" }, "dependencies": { - "@anatine/esbuild-decorators": "^0.2.19", "@clack/prompts": "^0.7.0", - "@depot/cli": "0.0.1-cli.2.71.0", + "@depot/cli": "0.0.1-cli.2.73.0", "@opentelemetry/api": "1.9.0", "@opentelemetry/api-logs": "0.52.1", "@opentelemetry/exporter-logs-otlp-http": "0.52.1", @@ -94,56 +87,38 @@ "@opentelemetry/sdk-trace-node": "1.25.1", "@opentelemetry/semantic-conventions": "1.25.1", "@trigger.dev/core": "workspace:3.0.0-beta.52", - "@types/degit": "^2.8.3", - "async-sema": "^3.1.1", "c12": "^1.11.1", "chalk": "^5.2.0", - "chokidar": "^3.5.3", "cli-table3": "^0.6.3", "commander": "^9.4.1", "defu": "^6.1.4", - "degit": "^2.8.4", "dotenv": "^16.4.5", "esbuild": "^0.23.0", "evt": "^2.4.13", - "execa": "^9.1.0", - "find-up": "^7.0.0", - "glob": "^11.0.0", - "glob-to-regexp": "^0.4.1", + "fast-npm-meta": "^0.2.2", "gradient-string": "^2.0.2", - "hono": "^4.4.13", "import-in-the-middle": "1.9.1", "import-meta-resolve": "^4.1.0", "ink": "^4.4.1", "jsonc-parser": "3.2.1", - "liquidjs": "^10.9.2", "magicast": "^0.3.4", "minimatch": "^10.0.1", "mlly": "^1.7.1", - "mock-fs": "^5.2.0", - "nanoid": "^4.0.2", - "node-fetch": "^3.3.0", + "nypm": "^0.3.9", "object-hash": "^3.0.0", - "p-debounce": "^4.0.0", - "p-throttle": "^6.1.0", + "open": "^10.0.3", "partysocket": "^0.0.17", "pkg-types": "^1.1.3", - "proxy-agent": "^6.3.0", "react": "^18.2.0", "react-error-boundary": "^3.1.4", "resolve": "^1.22.8", - "semver": "^7.5.0", "signal-exit": "^4.1.0", - "simple-git": "^3.19.0", "source-map-support": "0.5.21", "terminal-link": "^3.0.0", "tiny-invariant": "^1.2.0", - "tsconfig-paths": "^4.2.0", - "typescript": "^5.4.0", - "unplugin": "^1.12.0", - "update-check": "^1.5.4", - "url": "^0.11.1", + "tinyexec": "^0.1.4", "ws": "^8.12.0", + "xdg-app-paths": "^8.3.0", "zod": "3.23.8", "zod-validation-error": "^1.5.0" }, diff --git a/packages/cli-v3/src/apiClient.ts b/packages/cli-v3/src/apiClient.ts index 5f9eb62b8..ad2c7624b 100644 --- a/packages/cli-v3/src/apiClient.ts +++ b/packages/cli-v3/src/apiClient.ts @@ -17,6 +17,9 @@ import { ImportEnvironmentVariablesRequestBody, EnvironmentVariableResponseBody, TaskRunExecution, + FailDeploymentRequestBody, + FailDeploymentResponseBody, + FinalizeDeploymentRequestBody, } from "@trigger.dev/core/v3"; import { zodfetch, ApiError } from "@trigger.dev/core/v3/zodfetch"; @@ -155,7 +158,7 @@ export class CliApiClient { async importEnvVars( projectRef: string, - slug: "dev" | "prod" | "staging", + slug: string, params: ImportEnvironmentVariablesRequestBody ) { if (!this.accessToken) { @@ -191,6 +194,66 @@ export class CliApiClient { }); } + async createDeploymentBackgroundWorker( + deploymentId: string, + body: CreateBackgroundWorkerRequestBody + ) { + if (!this.accessToken) { + throw new Error("createDeploymentBackgroundWorker: No access token"); + } + + return wrapZodFetch( + CreateBackgroundWorkerResponse, + `${this.apiURL}/api/v1/deployments/${deploymentId}/background-workers`, + { + method: "POST", + headers: { + Authorization: `Bearer ${this.accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + } + ); + } + + async failDeployment(id: string, body: FailDeploymentRequestBody) { + if (!this.accessToken) { + throw new Error("failDeployment: No access token"); + } + + return wrapZodFetch( + FailDeploymentResponseBody, + `${this.apiURL}/api/v1/deployments/${id}/fail`, + { + method: "POST", + headers: { + Authorization: `Bearer ${this.accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + } + ); + } + + async finalizeDeployment(id: string, body: FinalizeDeploymentRequestBody) { + if (!this.accessToken) { + throw new Error("finalizeDeployment: No access token"); + } + + return wrapZodFetch( + FailDeploymentResponseBody, + `${this.apiURL}/api/v1/deployments/${id}/finalize`, + { + method: "POST", + headers: { + Authorization: `Bearer ${this.accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + } + ); + } + async startDeploymentIndexing(deploymentId: string, body: StartDeploymentIndexingRequestBody) { if (!this.accessToken) { throw new Error("startDeploymentIndexing: No access token"); @@ -245,7 +308,7 @@ async function wrapZodFetch( retry: { minTimeoutInMs: 500, maxTimeoutInMs: 5000, - maxAttempts: 3, + maxAttempts: 5, factor: 2, randomize: false, }, diff --git a/packages/cli-v3/src/build/bundle.ts b/packages/cli-v3/src/build/bundle.ts index 1254f06ca..23218db71 100644 --- a/packages/cli-v3/src/build/bundle.ts +++ b/packages/cli-v3/src/build/bundle.ts @@ -9,6 +9,7 @@ import { deployEntryPoints, devEntryPoints, isDeployEntryPoint, + isDeployIndexerEntryPoint, isDevEntryPoint, isIndexerEntryPoint, isLoaderEntryPoint, @@ -72,7 +73,7 @@ export async function bundleWorker(options: BundleOptions): Promise dirToEntryPointGlob(dir)); diff --git a/packages/cli-v3/src/build/extensions.ts b/packages/cli-v3/src/build/extensions.ts index 31da16e9f..e03952282 100644 --- a/packages/cli-v3/src/build/extensions.ts +++ b/packages/cli-v3/src/build/extensions.ts @@ -94,6 +94,11 @@ export function createBuildContext( prependExtension(extension) { extensions.unshift(extension); }, + logger: { + debug: (...args) => logger.debug(...args), + log: (...args) => logger.log(...args), + warn: (...args) => logger.warn(...args), + }, }; } @@ -125,7 +130,24 @@ function applyLayerToManifest(layer: BuildLayer, manifest: BuildManifest): Build if (layer.deploy?.env) { manifest.deploy.env ??= {}; - Object.assign(manifest.deploy.env, layer.deploy.env); + + for (const [key, value] of Object.entries(layer.deploy.env)) { + if (!value) { + continue; + } + + if (layer.deploy.override || manifest.deploy.env[key] === undefined) { + let needsSyncing = manifest.deploy.needsSyncing; + const existingValue = manifest.deploy.env[key]; + + if (existingValue !== value) { + needsSyncing = true; + } + + manifest.deploy.env[key] = value; + manifest.deploy.needsSyncing = needsSyncing; + } + } } if (layer.dependencies) { diff --git a/packages/cli-v3/src/build/packageModules.ts b/packages/cli-v3/src/build/packageModules.ts index 26596ffae..c5887a95e 100644 --- a/packages/cli-v3/src/build/packageModules.ts +++ b/packages/cli-v3/src/build/packageModules.ts @@ -5,10 +5,10 @@ export const devEntryPoint = join(sourceDir, "entryPoints", "dev.js"); export const deployEntryPoint = join(sourceDir, "entryPoints", "deploy.js"); export const telemetryEntryPoint = join(sourceDir, "entryPoints", "loader.js"); export const indexerEntryPoint = join(sourceDir, "entryPoints", "indexer.js"); +export const deployIndexerEntryPoint = join(sourceDir, "entryPoints", "deploy-indexer.js"); export const devEntryPoints = [devEntryPoint, indexerEntryPoint, telemetryEntryPoint]; - -export const deployEntryPoints = [devEntryPoint, deployEntryPoint, telemetryEntryPoint]; +export const deployEntryPoints = [deployIndexerEntryPoint, deployEntryPoint, telemetryEntryPoint]; export const esmShimPath = join(sourceDir, "shims", "esm.js"); @@ -22,6 +22,10 @@ export function isIndexerEntryPoint(entryPoint: string) { return entryPoint.includes(join("dist", "esm", "entryPoints", "indexer.js")); } +export function isDeployIndexerEntryPoint(entryPoint: string) { + return entryPoint.includes(join("dist", "esm", "entryPoints", "deploy-indexer.js")); +} + export function isDeployEntryPoint(entryPoint: string) { return entryPoint.includes(join("dist", "esm", "entryPoints", "deploy.js")); } diff --git a/packages/cli-v3/src/cli/index.ts b/packages/cli-v3/src/cli/index.ts index 22e179c52..e64ba0db6 100644 --- a/packages/cli-v3/src/cli/index.ts +++ b/packages/cli-v3/src/cli/index.ts @@ -8,6 +8,7 @@ import { COMMAND_NAME } from "../consts.js"; import { configureListProfilesCommand } from "../commands/list-profiles.js"; import { configureUpdateCommand } from "../commands/update.js"; import { VERSION } from "../version.js"; +import { configureDeployCommand } from "../commands/deploy.js"; export const program = new Command(); @@ -19,6 +20,7 @@ program configureLoginCommand(program); configureInitCommand(program); configureDevCommand(program); +configureDeployCommand(program); configureWhoamiCommand(program); configureLogoutCommand(program); configureListProfilesCommand(program); diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts new file mode 100644 index 000000000..fd342ee6c --- /dev/null +++ b/packages/cli-v3/src/commands/deploy.ts @@ -0,0 +1,593 @@ +import { intro, log, outro } from "@clack/prompts"; +import { DEFAULT_RUNTIME, ResolvedConfig } from "@trigger.dev/core/v3/build"; +import { + BuildManifest, + InitializeDeploymentResponseBody, + TaskFile, +} from "@trigger.dev/core/v3/schemas"; +import { CORE_VERSION } from "@trigger.dev/core/v3"; +import { Command, Option as CommandOption } from "commander"; +import { join, relative, resolve } from "node:path"; +import { readPackageJSON, writePackageJSON } from "pkg-types"; +import { z } from "zod"; +import { bundleWorker } from "../build/bundle.js"; +import { + createBuildContext, + notifyExtensionOnBuildComplete, + notifyExtensionOnBuildStart, + resolvePluginsForContext, +} from "../build/extensions.js"; +import { createExternalsBuildExtension } from "../build/externals.js"; +import { + deployEntryPoint, + deployIndexerEntryPoint, + telemetryEntryPoint, +} from "../build/packageModules.js"; +import { + CommonCommandOptions, + commonOptions, + handleTelemetry, + SkipLoggingError, + wrapCommandAction, +} from "../cli/common.js"; +import { loadConfig } from "../config.js"; +import { createTempDir, writeJSONFile } from "../utilities/fileSystem.js"; +import { printStandloneInitialBanner } from "../utilities/initialBanner.js"; +import { logger } from "../utilities/logger.js"; +import { getProjectClient } from "../utilities/session.js"; +import { getTmpDir } from "../utilities/tempDirectories.js"; +import { login } from "./login.js"; +import { updateTriggerPackages } from "./update.js"; +import { spinner } from "../utilities/windows.js"; +import { readFile, writeFile } from "node:fs/promises"; +import { VERSION } from "../version.js"; +import { resolveFileSources } from "../utilities/sourceFiles.js"; +import { buildImage, generateContainerfile } from "../deploy/buildImage.js"; +import { buildManifestToJSON } from "../utilities/buildManifest.js"; +import { CliApiClient } from "../apiClient.js"; +import { chalkError, chalkWarning, cliLink } from "../utilities/cliOutput.js"; +import { docs, getInTouch } from "../utilities/links.js"; +import { + checkLogsForErrors, + checkLogsForWarnings, + printErrors, + printWarnings, + saveLogs, +} from "../deploy/logs.js"; + +const DeployCommandOptions = CommonCommandOptions.extend({ + dryRun: z.boolean().default(false), + skipSyncEnvVars: z.boolean().default(false), + env: z.enum(["prod", "staging"]), + loadImage: z.boolean().default(false), + buildPlatform: z.enum(["linux/amd64", "linux/arm64"]).default("linux/amd64"), + selfHosted: z.boolean().default(false), + registry: z.string().optional(), + push: z.boolean().default(false), + config: z.string().optional(), + projectRef: z.string().optional(), + apiUrl: z.string().optional(), + saveLogs: z.boolean().default(false), + skipUpdateCheck: z.boolean().default(false), + noCache: z.boolean().default(false), +}); + +type DeployCommandOptions = z.infer; + +type Deployment = InitializeDeploymentResponseBody; + +export function configureDeployCommand(program: Command) { + return commonOptions( + program + .command("deploy") + .description("Deploy your Trigger.dev v3 project to the cloud.") + .argument("[path]", "The path to the project", ".") + .option( + "-e, --env ", + "Deploy to a specific environment (currently only prod and staging are supported)", + "prod" + ) + .option("--skip-update-check", "Skip checking for @trigger.dev package updates") + .option("-c, --config ", "The name of the config file, found at [path]") + .option( + "-p, --project-ref ", + "The project ref. Required if there is no config file. This will override the project specified in the config file." + ) + .option( + "--dry-run", + "Do a dry run of the deployment. This will not actually deploy the project, but will show you what would be deployed." + ) + .option( + "--skip-sync-env-vars", + "Skip syncing environment variables when using the syncEnvVars extension." + ) + ) + .addOption( + new CommandOption( + "--self-hosted", + "Build and load the image using your local Docker. Use the --registry option to specify the registry to push the image to when using --self-hosted, or just use --push to push to the default registry." + ).hideHelp() + ) + .addOption( + new CommandOption( + "--no-cache", + "Do not use the cache when building the image. This will slow down the build process but can be useful if you are experiencing issues with the cache." + ).hideHelp() + ) + .addOption( + new CommandOption( + "--push", + "When using the --self-hosted flag, push the image to the default registry. (defaults to false when not using --registry)" + ).hideHelp() + ) + .addOption( + new CommandOption( + "--registry ", + "The registry to push the image to when using --self-hosted" + ).hideHelp() + ) + .addOption( + new CommandOption( + "--tag ", + "(Coming soon) Specify the tag to use when pushing the image to the registry" + ).hideHelp() + ) + .addOption( + new CommandOption("--load-image", "Load the built image into your local docker").hideHelp() + ) + .addOption( + new CommandOption( + "--build-platform ", + "The platform to build the deployment image for" + ) + .default("linux/amd64") + .hideHelp() + ) + .addOption( + new CommandOption( + "--save-logs", + "If provided, will save logs even for successful builds" + ).hideHelp() + ) + .action(async (path, options) => { + await handleTelemetry(async () => { + await printStandloneInitialBanner(true); + await deployCommand(path, options); + }); + }); +} + +export async function deployCommand(dir: string, options: unknown) { + return await wrapCommandAction("deployCommand", DeployCommandOptions, options, async (opts) => { + return await _deployCommand(dir, opts); + }); +} + +async function _deployCommand(dir: string, options: DeployCommandOptions) { + intro("Deploying project"); + + if (!options.skipUpdateCheck) { + await updateTriggerPackages(dir, { ...options }, true, true); + } + + const projectPath = resolve(process.cwd(), dir); + + const authorization = await login({ + embedded: true, + defaultApiUrl: options.apiUrl, + profile: options.profile, + }); + + if (!authorization.ok) { + if (authorization.error === "fetch failed") { + throw new Error( + `Failed to connect to ${authorization.auth?.apiUrl}. Are you sure it's the correct URL?` + ); + } else { + throw new Error( + `You must login first. Use the \`login\` CLI command.\n\n${authorization.error}` + ); + } + } + + const resolvedConfig = await loadConfig({ + cwd: projectPath, + overrides: { project: options.projectRef }, + }); + + logger.debug("Resolved config", resolvedConfig); + + const projectClient = await getProjectClient({ + accessToken: authorization.auth.accessToken, + apiUrl: authorization.auth.apiUrl, + projectRef: resolvedConfig.project, + env: options.env, + profile: options.profile, + }); + + if (!projectClient) { + throw new Error("Failed to get project client"); + } + + const serverEnvVars = await projectClient.client.getEnvironmentVariables(resolvedConfig.project); + + const destination = getTmpDir(resolvedConfig.workingDir, "build", options.dryRun); + const externalsExtension = createExternalsBuildExtension("deploy", resolvedConfig); + const buildContext = createBuildContext("deploy", resolvedConfig); + buildContext.prependExtension(externalsExtension); + await notifyExtensionOnBuildStart(buildContext); + const pluginsFromExtensions = resolvePluginsForContext(buildContext); + + const $buildSpinner = spinner(); + $buildSpinner.start("Building project"); + + const bundleResult = await bundleWorker({ + target: "deploy", + cwd: resolvedConfig.workingDir, + destination: destination.path, + watch: false, + resolvedConfig, + plugins: [...pluginsFromExtensions], + jsxFactory: resolvedConfig.build.jsx.factory, + jsxFragment: resolvedConfig.build.jsx.fragment, + jsxAutomatic: resolvedConfig.build.jsx.automatic, + }); + + $buildSpinner.stop("Successfully built project"); + + logger.debug("Bundle result", bundleResult); + + let buildManifest: BuildManifest = { + contentHash: bundleResult.contentHash, + runtime: resolvedConfig.runtime ?? DEFAULT_RUNTIME, + environment: options.env, + packageVersion: CORE_VERSION, + cliPackageVersion: VERSION, + target: "deploy", + files: bundleResult.files, + sources: await resolveFileSources(bundleResult.files, resolvedConfig.workingDir), + config: { + project: resolvedConfig.project, + dirs: resolvedConfig.dirs, + }, + outputPath: destination.path, + workerEntryPoint: bundleResult.workerEntryPoint ?? deployEntryPoint, + indexerEntryPoint: bundleResult.indexerEntryPoint ?? deployIndexerEntryPoint, + loaderEntryPoint: bundleResult.loaderEntryPoint ?? telemetryEntryPoint, + configPath: bundleResult.configPath, + deploy: { + env: serverEnvVars.success ? serverEnvVars.data.variables : {}, + }, + build: {}, + }; + + buildManifest = await notifyExtensionOnBuildComplete(buildContext, buildManifest); + buildManifest = rewriteBuildManifestPaths(buildManifest, destination.path); + + await writeProjectFiles(buildManifest, resolvedConfig, destination.path); + + logger.debug("Successfully built project to", destination.path); + + if (options.dryRun) { + logger.info(`Dry run complete. View the built project at ${destination.path}`); + return; + } + + const deploymentResponse = await projectClient.client.initializeDeployment({ + contentHash: buildManifest.contentHash, + userId: authorization.userId, + }); + + if (!deploymentResponse.success) { + throw new Error(`Failed to start deployment: ${deploymentResponse.error}`); + } + + const deployment = deploymentResponse.data; + + // If the deployment doesn't have any externalBuildData, then we can't use the remote image builder + // TODO: handle this and allow the user to the build and push the image themselves + if (!deployment.externalBuildData && !options.selfHosted) { + throw new Error( + `Failed to start deployment, as your instance of trigger.dev does not support hosting. To deploy this project, you must use the --self-hosted flag to build and push the image yourself.` + ); + } + + if ( + buildManifest.deploy.env && + Object.keys(buildManifest.deploy.env).length > 0 && + buildManifest.deploy.needsSyncing + ) { + if (!options.skipSyncEnvVars) { + const $spinner = spinner(); + $spinner.start("Syncing environment variables with the server"); + const success = await syncEnvVarsWithServer( + projectClient.client, + resolvedConfig.project, + options.env, + buildManifest.deploy.env + ); + + if (!success) { + await failDeploy( + projectClient.client, + deployment, + { + name: "SyncEnvVarsError", + message: "Failed to sync environment variables with the server", + }, + "", + $spinner + ); + } else { + $spinner.stop("Successfully synced environment variables with the server"); + } + } + + logger.log( + "Skipping syncing environment variables. The environment variables in your project have changed, but the --skip-sync-env-vars flag was provided." + ); + } + + const version = deployment.version; + + const deploymentLink = cliLink( + "View deployment", + `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project}/deployments/${deployment.shortCode}` + ); + + const testLink = cliLink( + "Test tasks", + `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project}/test?environment=${ + options.env === "prod" ? "prod" : "stg" + }` + ); + + const $spinner = spinner(); + + $spinner.start(`Deploying version ${version} ${deploymentLink}`); + + const selfHostedRegistryHost = deployment.registryHost ?? options.registry; + const registryHost = selfHostedRegistryHost ?? "registry.trigger.dev"; + + const buildResult = await buildImage({ + selfHosted: options.selfHosted, + buildPlatform: options.buildPlatform, + noCache: options.noCache, + push: options.push, + registryHost, + deploymentId: deployment.id, + deploymentVersion: deployment.version, + imageTag: deployment.imageTag, + contentHash: deployment.contentHash, + externalBuildId: deployment.externalBuildData?.buildId, + externalBuildToken: deployment.externalBuildData?.buildToken, + externalBuildProjectId: deployment.externalBuildData?.projectId, + projectId: projectClient.id, + projectRef: resolvedConfig.project, + apiUrl: projectClient.client.apiURL, + apiKey: projectClient.client.accessToken!, + authAccessToken: authorization.auth.accessToken, + compilationPath: destination.path, + }); + + logger.debug("Build result", buildResult); + + const warnings = checkLogsForWarnings(buildResult.logs); + + if (!warnings.ok) { + await failDeploy( + projectClient.client, + deployment, + { name: "BuildError", message: warnings.summary }, + buildResult.logs, + $spinner, + warnings.warnings, + warnings.errors + ); + + throw new SkipLoggingError("Failed to build image"); + } + + if (!buildResult.ok) { + await failDeploy( + projectClient.client, + deployment, + { name: "BuildError", message: buildResult.error }, + buildResult.logs, + $spinner, + warnings.warnings + ); + + throw new SkipLoggingError("Failed to build image"); + } + + const getDeploymentResponse = await projectClient.client.getDeployment(deployment.id); + + if (!getDeploymentResponse.success) { + await failDeploy( + projectClient.client, + deployment, + { name: "DeploymentError", message: getDeploymentResponse.error }, + buildResult.logs, + $spinner + ); + + throw new SkipLoggingError("Failed to get deployment with worker"); + } + + const deploymentWithWorker = getDeploymentResponse.data; + + if (!deploymentWithWorker.worker) { + await failDeploy( + projectClient.client, + deployment, + { name: "DeploymentError", message: "Failed to get deployment with worker" }, + buildResult.logs, + $spinner + ); + + throw new SkipLoggingError("Failed to get deployment with worker"); + } + + const imageReference = options.selfHosted + ? `${selfHostedRegistryHost ? `${selfHostedRegistryHost}/` : ""}${buildResult.image}${ + buildResult.digest ? `@${buildResult.digest}` : "" + }` + : `${registryHost}/${buildResult.image}${buildResult.digest ? `@${buildResult.digest}` : ""}`; + + const finalizeResponse = await projectClient.client.finalizeDeployment(deployment.id, { + imageReference, + selfHosted: options.selfHosted, + }); + + if (!finalizeResponse.success) { + await failDeploy( + projectClient.client, + deployment, + { name: "FinalizeError", message: finalizeResponse.error }, + buildResult.logs, + $spinner + ); + + throw new SkipLoggingError("Failed to finalize deployment"); + } + + $spinner.stop(`Successfully deployed version ${version}`); + + const taskCount = deploymentWithWorker.worker?.tasks.length ?? 0; + + outro( + `Version ${version} deployed with ${taskCount} detected task${ + taskCount === 1 ? "" : "s" + } | ${deploymentLink} | ${testLink}` + ); +} + +function rewriteBuildManifestPaths( + buildManifest: BuildManifest, + destinationDir: string +): BuildManifest { + return { + ...buildManifest, + files: buildManifest.files.map((file) => ({ + ...file, + entry: cleanEntryPath(file.entry), + out: rewriteOutputPath(destinationDir, file.out), + })), + outputPath: rewriteOutputPath(destinationDir, buildManifest.outputPath), + configPath: rewriteOutputPath(destinationDir, buildManifest.configPath), + workerEntryPoint: rewriteOutputPath(destinationDir, buildManifest.workerEntryPoint), + indexerEntryPoint: rewriteOutputPath(destinationDir, buildManifest.indexerEntryPoint), + loaderEntryPoint: buildManifest.loaderEntryPoint + ? rewriteOutputPath(destinationDir, buildManifest.loaderEntryPoint) + : undefined, + }; +} + +async function writeProjectFiles( + buildManifest: BuildManifest, + resolvedConfig: ResolvedConfig, + outputPath: string +) { + // Step 1. Read the package.json file + const packageJson = await readProjectPackageJson(resolvedConfig.packageJsonPath); + + if (!packageJson) { + throw new Error("Could not read the package.json file"); + } + + const dependencies = + buildManifest.externals?.reduce( + (acc, external) => { + acc[external.name] = external.version; + + return acc; + }, + {} as Record + ) ?? {}; + + // Step 3: Write the resolved dependencies to the package.json file + await writePackageJSON(join(outputPath, "package.json"), { + ...packageJson, + name: packageJson.name ?? "trigger-project", + dependencies: { + ...dependencies, + }, + devDependencies: {}, + peerDependencies: {}, + scripts: {}, + }); + + await writeJSONFile(join(outputPath, "build.json"), buildManifestToJSON(buildManifest)); + await writeContainerfile(outputPath, buildManifest); +} + +async function readProjectPackageJson(packageJsonPath: string) { + const packageJson = await readPackageJSON(packageJsonPath); + + return packageJson; +} + +// Remove any query parameters from the entry path +// For example, src/trigger/ai.ts?sentryProxyModule=true -> src/trigger/ai.ts +function cleanEntryPath(entry: string): string { + return entry.split("?")[0]!; +} + +function rewriteOutputPath(destinationDir: string, filePath: string) { + return `/app/${relative(destinationDir, filePath)}`; +} + +async function writeContainerfile(outputPath: string, buildManifest: BuildManifest) { + const containerfile = await generateContainerfile(buildManifest); + + await writeFile(join(outputPath, "Containerfile"), containerfile); +} + +export async function syncEnvVarsWithServer( + apiClient: CliApiClient, + projectRef: string, + environmentSlug: string, + envVars: Record +) { + const uploadResult = await apiClient.importEnvVars(projectRef, environmentSlug, { + variables: envVars, + override: true, + }); + + return uploadResult.success; +} + +async function failDeploy( + client: CliApiClient, + deployment: Deployment, + error: { name: string; message: string }, + logs: string, + $spinner: ReturnType, + warnings?: string[], + errors?: string[] +) { + $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); + + printWarnings(warnings); + printErrors(errors); + + checkLogsForErrors(logs); + + outro( + `${chalkError("Error:")} ${error.message}. Full build logs have been saved to ${logPath}` + ); + } else { + outro(`${chalkError("Error:")} ${error.message}.`); + } + + await client.failDeployment(deployment.id, { + error, + }); + + throw new SkipLoggingError(`Failed to deploy: ${error.message}`); +} diff --git a/packages/cli-v3/src/commands/dev.tsx b/packages/cli-v3/src/commands/dev.tsx index 3a9c4378f..084644f98 100644 --- a/packages/cli-v3/src/commands/dev.tsx +++ b/packages/cli-v3/src/commands/dev.tsx @@ -9,7 +9,7 @@ import Dev from "../dev/dev.js"; import { chalkError } from "../utilities/cliOutput.js"; import { printDevBanner, printStandloneInitialBanner } from "../utilities/initialBanner.js"; import { logger } from "../utilities/logger.js"; -import { runtimeCheck } from "../utilities/runtimeCheck.js"; +import { runtimeChecks } from "../utilities/runtimeCheck.js"; import { getProjectClient, isLoggedIn, LoginResultOk } from "../utilities/session.js"; import { updateTriggerPackages } from "./update.js"; @@ -42,17 +42,8 @@ export function configureDevCommand(program: Command) { }); } -const MINIMUM_NODE_MAJOR = 18; -const MINIMUM_NODE_MINOR = 20; - export async function devCommand(options: DevCommandOptions) { - try { - runtimeCheck(MINIMUM_NODE_MAJOR, MINIMUM_NODE_MINOR); - } catch (e) { - logger.log(`${chalkError("X Error:")} ${e}`); - process.exitCode = 1; - return; - } + runtimeChecks(); const authorization = await isLoggedIn(options.profile); diff --git a/packages/cli-v3/src/commands/init.ts b/packages/cli-v3/src/commands/init.ts index 830938a4e..1fc48968c 100644 --- a/packages/cli-v3/src/commands/init.ts +++ b/packages/cli-v3/src/commands/init.ts @@ -4,10 +4,10 @@ import { GetProjectResponseBody, flattenAttributes } from "@trigger.dev/core/v3" import { recordSpanException } from "@trigger.dev/core/v3/workers"; import chalk from "chalk"; import { Command } from "commander"; -import { ExecaError, Options as ExecaOptions, ResultPromise as ExecaResult, execa } from "execa"; -import { applyEdits, modify, findNodeAtLocation, parseTree, getNodeValue } from "jsonc-parser"; +import { applyEdits, findNodeAtLocation, getNodeValue, modify, parseTree } from "jsonc-parser"; import { writeFile } from "node:fs/promises"; import { join, relative, resolve } from "node:path"; +import { addDependency, detectPackageManager } from "nypm"; import { z } from "zod"; import { CliApiClient } from "../apiClient.js"; import { @@ -20,17 +20,16 @@ import { tracer, wrapCommandAction, } from "../cli/common.js"; +import { loadConfig } from "../config.js"; +import { CLOUD_API_URL } from "../consts.js"; +import { cliLink, prettyError } from "../utilities/cliOutput.js"; import { createFileFromTemplate } from "../utilities/createFileFromTemplate.js"; import { createFile, pathExists, readFile } from "../utilities/fileSystem.js"; -import { PackageManager, getUserPackageManager } from "../utilities/getUserPackageManager.js"; import { printStandloneInitialBanner } from "../utilities/initialBanner.js"; import { logger } from "../utilities/logger.js"; import { cliRootPath } from "../utilities/resolveInternalFilePath.js"; -import { login } from "./login.js"; import { spinner } from "../utilities/windows.js"; -import { CLOUD_API_URL } from "../consts.js"; -import { cliLink, prettyError } from "../utilities/cliOutput.js"; -import { loadConfig } from "../config.js"; +import { login } from "./login.js"; const InitCommandOptions = CommonCommandOptions.extend({ projectRef: z.string().optional(), @@ -431,55 +430,21 @@ async function addConfigFileToTsConfig(dir: string, options: InitCommandOptions) async function installPackages(dir: string, options: InitCommandOptions) { return await tracer.startActiveSpan("installPackages", async (span) => { - const installSpinner = spinner(); + const projectDir = resolve(process.cwd(), dir); - let pkgManager: PackageManager | undefined; + const installSpinner = spinner(); + const packageManager = await detectPackageManager(projectDir); try { - const projectDir = resolve(process.cwd(), dir); - - pkgManager = await getUserPackageManager(projectDir); - span.setAttributes({ "cli.projectDir": projectDir, - "cli.packageManager": pkgManager, + "cli.packageManager": packageManager?.name, "cli.tag": options.tag, }); - const userArgs = options.pkgArgs?.split(",") ?? []; - const execaOptions = { cwd: projectDir } satisfies ExecaOptions; + installSpinner.start(`Adding @trigger.dev/sdk@${options.tag}`); - let installProcess: ExecaResult; - let args: string[]; - - switch (pkgManager) { - case "npm": { - // --save-exact: pin version, e.g. 3.0.0-beta.20 instead of ^3.0.0-beta.20 - args = ["install", "--save-exact", ...userArgs, `@trigger.dev/sdk@${options.tag}`]; - - break; - } - case "pnpm": - case "yarn": { - // pins version by default - args = ["add", ...userArgs, `@trigger.dev/sdk@${options.tag}`]; - - break; - } - } - - installSpinner.start(`Running ${pkgManager} ${args.join(" ")}`); - - installProcess = execa(pkgManager, args, execaOptions); - - const handleProcessOutput = (data: Buffer) => { - logger.debug(data.toString()); - }; - - installProcess.stderr?.on("data", handleProcessOutput); - installProcess.stdout?.on("data", handleProcessOutput); - - await installProcess; + await addDependency(`@trigger.dev/sdk@${options.tag}`, { cwd: projectDir }); installSpinner.stop(`@trigger.dev/sdk@${options.tag} installed`); @@ -497,12 +462,6 @@ async function installPackages(dir: string, options: InitCommandOptions) { recordSpanException(span, e); } - if (e instanceof ExecaError) { - if (pkgManager) { - e.message += ` \n\nNote: You can pass additional args to ${pkgManager} by using --pkg-args. For example: trigger.dev init --pkg-args="--workspace-root"`; - } - } - span.end(); throw e; diff --git a/packages/cli-v3/src/commands/list-profiles.ts b/packages/cli-v3/src/commands/list-profiles.ts index 52422db85..0a0a0c34e 100644 --- a/packages/cli-v3/src/commands/list-profiles.ts +++ b/packages/cli-v3/src/commands/list-profiles.ts @@ -23,7 +23,6 @@ export function configureListProfilesCommand(program: Command) { .option("--skip-telemetry", "Opt-out of sending telemetry") .action(async (options) => { await handleTelemetry(async () => { - await printInitialBanner(true); await listProfilesCommand(options); }); }); @@ -31,6 +30,7 @@ export function configureListProfilesCommand(program: Command) { export async function listProfilesCommand(options: unknown) { return await wrapCommandAction("listProfiles", ListProfilesOptions, options, async (opts) => { + await printInitialBanner(false); return await listProfiles(opts); }); } diff --git a/packages/cli-v3/src/commands/update.ts b/packages/cli-v3/src/commands/update.ts index 83cb1a866..5d29b57fc 100644 --- a/packages/cli-v3/src/commands/update.ts +++ b/packages/cli-v3/src/commands/update.ts @@ -1,16 +1,15 @@ import { confirm, intro, isCancel, log, outro } from "@clack/prompts"; -import { z } from "zod"; -import { readJSONFile, removeFile, writeJSONFile } from "../utilities/fileSystem.js"; -import { spinner } from "../utilities/windows.js"; -import { CommonCommandOptions, OutroCommandError, wrapCommandAction } from "../cli/common.js"; import { Command } from "commander"; -import { logger } from "../utilities/logger.js"; -import { PackageJson } from "type-fest"; -import { printStandloneInitialBanner, updateCheck } from "../utilities/initialBanner.js"; -import { join, resolve } from "path"; -import { JavascriptProject } from "../utilities/javascriptProject.js"; -import { PackageManager } from "../utilities/getUserPackageManager.js"; +import { detectPackageManager, installDependencies } from "nypm"; +import { resolve } from "path"; +import { PackageJson, readPackageJSON, resolvePackageJSON } from "pkg-types"; +import { z } from "zod"; +import { CommonCommandOptions, OutroCommandError, wrapCommandAction } from "../cli/common.js"; import { chalkError, prettyError, prettyWarning } from "../utilities/cliOutput.js"; +import { removeFile, writeJSONFile } from "../utilities/fileSystem.js"; +import { printStandloneInitialBanner, updateCheck } from "../utilities/initialBanner.js"; +import { logger } from "../utilities/logger.js"; +import { spinner } from "../utilities/windows.js"; import { VERSION } from "../version.js"; export const UpdateCommandOptions = CommonCommandOptions.pick({ @@ -233,16 +232,12 @@ export async function updateTriggerPackages( installSpinner.message("Installing new package versions"); - const jsProject = new JavascriptProject(projectPath); - - let packageManager: PackageManager | undefined; + const packageManager = await detectPackageManager(projectPath); try { - packageManager = await jsProject.getPackageManager(); - installSpinner.message(`Installing new package versions with ${packageManager}`); - await jsProject.install(); + await installDependencies({ cwd: projectPath }); } catch (error) { installSpinner.stop( `Failed to install new package versions${packageManager ? ` with ${packageManager}` : ""}` @@ -351,9 +346,8 @@ async function updateConfirmation(depsToUpdate: Dependency[], targetVersion: str } export async function getPackageJson(absoluteProjectPath: string) { - const packageJsonPath = join(absoluteProjectPath, "package.json"); - - const readonlyPackageJson = Object.freeze((await readJSONFile(packageJsonPath)) as PackageJson); + const packageJsonPath = await resolvePackageJSON(absoluteProjectPath); + const readonlyPackageJson = await readPackageJSON(packageJsonPath); const packageJson = structuredClone(readonlyPackageJson); diff --git a/packages/cli-v3/src/config.ts b/packages/cli-v3/src/config.ts index 8044d5da6..c762c5eab 100644 --- a/packages/cli-v3/src/config.ts +++ b/packages/cli-v3/src/config.ts @@ -241,6 +241,15 @@ function validateConfig(config: TriggerConfig, warn = true) { config.tsconfig = config.tsconfigPath; } + if ("resolveEnvVars" in config && typeof config.resolveEnvVars === "function") { + warn && + logger.warn( + `The "resolveEnvVars" option is deprecated and will be removed. Use the "syncEnvVars" build extension instead. See https://trigger.dev/docs/trigger-config#syncEnvVars for more information.` + ); + + // + } + if (config.runtime && config.runtime === "bun") { warn && logger.warn(`The "bun" runtime is currently experimental and may not work as expected.`); diff --git a/packages/cli-v3/src/deploy/buildImage.ts b/packages/cli-v3/src/deploy/buildImage.ts new file mode 100644 index 000000000..5668ec0cd --- /dev/null +++ b/packages/cli-v3/src/deploy/buildImage.ts @@ -0,0 +1,514 @@ +import { join } from "node:path"; +import { createTempDir, writeJSONFile } from "../utilities/fileSystem.js"; +import { logger } from "../utilities/logger.js"; +import { depot } from "@depot/cli"; +import { x } from "tinyexec"; +import { BuildManifest } from "@trigger.dev/core/v3/schemas"; + +export interface BuildImageOptions { + // Common options + selfHosted: boolean; + buildPlatform: string; + noCache?: boolean; + + // Self-hosted specific options + push: boolean; + registry?: string; + + // Non-self-hosted specific options + loadImage?: boolean; + + // Flattened properties from nested structures + registryHost: string; + authAccessToken: string; + imageTag: string; + deploymentId: string; + deploymentVersion: string; + contentHash: string; + externalBuildId?: string; + externalBuildToken?: string; + externalBuildProjectId?: string; + compilationPath: string; + projectId: string; + projectRef: string; + extraCACerts?: string; + apiUrl: string; + apiKey: string; + + // Optional deployment spinner + deploymentSpinner?: any; // Replace 'any' with the actual type if known +} + +export async function buildImage(options: BuildImageOptions) { + const { + selfHosted, + buildPlatform, + noCache, + push, + registry, + loadImage, + registryHost, + authAccessToken, + imageTag, + deploymentId, + deploymentVersion, + contentHash, + externalBuildId, + externalBuildToken, + externalBuildProjectId, + compilationPath, + projectId, + projectRef, + extraCACerts, + apiUrl, + apiKey, + } = options; + + if (selfHosted) { + return selfHostedBuildImage({ + registryHost: registryHost, + imageTag: imageTag, + cwd: compilationPath, + projectId: projectId, + deploymentId: deploymentId, + deploymentVersion: deploymentVersion, + contentHash: contentHash, + projectRef: projectRef, + buildPlatform: buildPlatform, + pushImage: push, + selfHostedRegistry: !!registry, + noCache: noCache, + extraCACerts: extraCACerts, + apiUrl, + apiKey, + }); + } + + if (!externalBuildId || !externalBuildToken || !externalBuildProjectId) { + throw new Error( + "Failed to initialize deployment. The deployment does not have any external build data. To deploy this project, you must use the --self-hosted flag to build and push the image yourself." + ); + } + + return depotBuildImage({ + registryHost, + auth: authAccessToken, + imageTag, + buildId: externalBuildId, + buildToken: externalBuildToken, + buildProjectId: externalBuildProjectId, + cwd: compilationPath, + projectId, + deploymentId, + deploymentVersion, + contentHash, + projectRef, + loadImage, + buildPlatform, + noCache, + extraCACerts, + apiUrl, + apiKey, + }); +} + +export interface DepotBuildImageOptions { + registryHost: string; + auth: string; + imageTag: string; + buildId: string; + buildToken: string; + buildProjectId: string; + cwd: string; + projectId: string; + deploymentId: string; + deploymentVersion: string; + contentHash: string; + projectRef: string; + buildPlatform: string; + apiUrl: string; + apiKey: string; + loadImage?: boolean; + noCache?: boolean; + extraCACerts?: string; +} + +type BuildImageSuccess = { + ok: true; + image: string; + logs: string; + digest?: string; +}; + +type BuildImageFailure = { + ok: false; + error: string; + logs: string; +}; + +type BuildImageResults = BuildImageSuccess | BuildImageFailure; + +async function depotBuildImage(options: DepotBuildImageOptions): Promise { + // Step 3: Ensure we are "logged in" to our registry by writing to $HOME/.docker/config.json + // TODO: make sure this works on windows + const dockerConfigDir = await ensureLoggedIntoDockerRegistry(options.registryHost, { + username: "trigger", + password: options.auth, + }); + + const args = [ + "build", + "-f", + "Containerfile", + options.noCache ? "--no-cache" : undefined, + "--platform", + options.buildPlatform, + "--provenance", + "false", + "--build-arg", + `TRIGGER_PROJECT_ID=${options.projectId}`, + "--build-arg", + `TRIGGER_DEPLOYMENT_ID=${options.deploymentId}`, + "--build-arg", + `TRIGGER_DEPLOYMENT_VERSION=${options.deploymentVersion}`, + "--build-arg", + `TRIGGER_CONTENT_HASH=${options.contentHash}`, + "--build-arg", + `TRIGGER_PROJECT_REF=${options.projectRef}`, + "--build-arg", + `TRIGGER_API_URL=${options.apiUrl}`, + "--build-arg", + `TRIGGER_SECRET_KEY=${options.apiKey}`, + ...(options.extraCACerts ? ["--build-arg", `NODE_EXTRA_CA_CERTS=${options.extraCACerts}`] : []), + "--progress", + "plain", + "-t", + `${options.registryHost}/${options.imageTag}`, + ".", + "--push", + options.loadImage ? "--load" : undefined, + ].filter(Boolean) as string[]; + + logger.debug(`depot ${args.join(" ")}`); + + // Step 4: Build and push the image + const childProcess = depot(args, { + cwd: options.cwd, + env: { + DEPOT_BUILD_ID: options.buildId, + DEPOT_TOKEN: options.buildToken, + DEPOT_PROJECT_ID: options.buildProjectId, + DEPOT_NO_SUMMARY_LINK: "1", + DEPOT_NO_UPDATE_NOTIFIER: "1", + DOCKER_CONFIG: dockerConfigDir, + }, + }); + + const errors: string[] = []; + + try { + const processCode = await new Promise((res, rej) => { + // For some reason everything is output on stderr, not stdout + childProcess.stderr?.on("data", (data: Buffer) => { + const text = data.toString(); + + // Emitted data chunks can contain multiple lines. Remove empty lines. + const lines = text.split("\n").filter(Boolean); + + errors.push(...lines); + logger.debug(text); + }); + + childProcess.on("error", (e) => rej(e)); + childProcess.on("close", (code) => res(code)); + }); + + const logs = extractLogs(errors); + + if (processCode !== 0) { + return { + ok: false as const, + error: `Error building image`, + logs, + }; + } + + const digest = extractImageDigest(errors); + + return { + ok: true as const, + image: options.imageTag, + logs, + digest, + }; + } catch (e) { + return { + ok: false as const, + error: e instanceof Error ? e.message : JSON.stringify(e), + logs: extractLogs(errors), + }; + } +} + +interface SelfHostedBuildImageOptions { + registryHost: string; + imageTag: string; + cwd: string; + projectId: string; + deploymentId: string; + deploymentVersion: string; + contentHash: string; + projectRef: string; + buildPlatform: string; + pushImage: boolean; + selfHostedRegistry: boolean; + apiUrl: string; + apiKey: string; + noCache?: boolean; + extraCACerts?: string; +} + +async function selfHostedBuildImage( + options: SelfHostedBuildImageOptions +): Promise { + const imageRef = `${options.registryHost ? `${options.registryHost}/` : ""}${options.imageTag}`; + + const buildArgs = [ + "build", + "-f", + "Containerfile", + options.noCache ? "--no-cache" : undefined, + "--platform", + options.buildPlatform, + "--build-arg", + `TRIGGER_PROJECT_ID=${options.projectId}`, + "--build-arg", + `TRIGGER_DEPLOYMENT_ID=${options.deploymentId}`, + "--build-arg", + `TRIGGER_DEPLOYMENT_VERSION=${options.deploymentVersion}`, + "--build-arg", + `TRIGGER_CONTENT_HASH=${options.contentHash}`, + "--build-arg", + `TRIGGER_PROJECT_REF=${options.projectRef}`, + "--build-arg", + `TRIGGER_API_URL=${options.apiUrl}`, + "--build-arg", + `TRIGGER_SECRET_KEY=${options.apiKey}`, + ...(options.extraCACerts ? ["--build-arg", `NODE_EXTRA_CA_CERTS=${options.extraCACerts}`] : []), + "--progress", + "plain", + "-t", + imageRef, + ".", // The build context + ].filter(Boolean) as string[]; + + logger.debug(`docker ${buildArgs.join(" ")}`, { + cwd: options.cwd, + }); + + const errors: string[] = []; + let digest: string | undefined; + + // Build the image + const buildProcess = x("docker", buildArgs, { + nodeOptions: { cwd: options.cwd }, + }); + + for await (const line of buildProcess) { + // line will be from stderr/stdout in the order you'd see it in a term + errors.push(line); + logger.debug(line); + } + + if (buildProcess.exitCode !== 0) { + return { + ok: false as const, + error: "Error building image", + logs: extractLogs(errors), + }; + } + + digest = extractImageDigest(errors); + + if (options.selfHostedRegistry || options.pushImage) { + const pushArgs = ["push", imageRef].filter(Boolean) as string[]; + + logger.debug(`docker ${pushArgs.join(" ")}`); + + // Push the image + const pushProcess = x("docker", pushArgs, { + nodeOptions: { cwd: options.cwd }, + }); + + for await (const line of pushProcess) { + logger.debug(line); + } + + if (pushProcess.exitCode !== 0) { + return { + ok: false as const, + error: "Error pushing image", + logs: extractLogs(errors), + }; + } + } + + return { + ok: true as const, + image: options.imageTag, + digest, + logs: extractLogs(errors), + }; +} + +async function ensureLoggedIntoDockerRegistry( + registryHost: string, + auth: { username: string; password: string } +) { + const tmpDir = await createTempDir(); + // Read the current docker config + const dockerConfigPath = join(tmpDir, "config.json"); + + await writeJSONFile(dockerConfigPath, { + auths: { + [registryHost]: { + auth: Buffer.from(`${auth.username}:${auth.password}`).toString("base64"), + }, + }, + }); + + logger.debug(`Writing docker config to ${dockerConfigPath}`); + + return tmpDir; +} + +function extractLogs(outputs: string[]) { + // Remove empty lines + const cleanedOutputs = outputs.map((line) => line.trim()).filter((line) => line !== ""); + + return cleanedOutputs.map((line) => line.trim()).join("\n"); +} + +function extractImageDigest(outputs: string[]) { + const imageDigestRegex = /pushing manifest for .+(?sha256:[a-f0-9]{64})/; + + for (const line of outputs) { + const imageDigestMatch = line.match(imageDigestRegex); + + const digest = imageDigestMatch?.groups?.digest; + + if (digest) { + return digest; + } + } + + return; +} + +export async function generateContainerfile(buildManifest: BuildManifest) { + switch (buildManifest.runtime) { + case "node20": { + return await generateNodeContainerfile(buildManifest); + } + case "bun": { + return await generateBunContainerfile(buildManifest); + } + } +} + +async function generateBunContainerfile(buildManifest: BuildManifest) { + return ""; +} + +async function generateNodeContainerfile(buildManifest: BuildManifest) { + const buildArgs = Object.entries(buildManifest.build.env || {}) + .flatMap(([key]) => `ARG ${key}`) + .join("\n"); + + const buildEnvVars = Object.entries(buildManifest.build.env || {}) + .flatMap(([key]) => `ENV ${key}=$${key}`) + .join("\n"); + + const postInstallCommands = (buildManifest.build.commands || []) + .map((cmd) => `RUN ${cmd}`) + .join("\n"); + + return ` +FROM node:21-bookworm-slim@sha256:99afef5df7400a8d118e0504576d32ca700de5034c4f9271d2ff7c91cc12d170 AS base +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get --fix-broken install -y && apt-get install -y --no-install-recommends busybox ca-certificates dumb-init git openssl && apt-get clean && rm -rf /var/lib/apt/lists/* + +FROM base AS install + +USER node +WORKDIR /app + +${buildArgs} + +${buildEnvVars} + +ENV NODE_ENV=production +ENV NPM_CONFIG_UPDATE_NOTIFIER=false + +COPY --chown=node:node package.json ./ +RUN npm i --no-audit --no-fund --no-save --no-package-lock + +# Now copy all the files +# IMPORTANT: Do this after running npm install because npm i will wipe out the node_modules directory +COPY --chown=node:node . . + +${postInstallCommands} + +from install as indexer + +USER node +WORKDIR /app + +ARG TRIGGER_PROJECT_ID +ARG TRIGGER_DEPLOYMENT_ID +ARG TRIGGER_DEPLOYMENT_VERSION +ARG TRIGGER_CONTENT_HASH +ARG TRIGGER_PROJECT_REF +ARG NODE_EXTRA_CA_CERTS +ARG TRIGGER_SECRET_KEY +ARG TRIGGER_API_URL + +ENV TRIGGER_PROJECT_ID=\${TRIGGER_PROJECT_ID} \ + TRIGGER_DEPLOYMENT_ID=\${TRIGGER_DEPLOYMENT_ID} \ + TRIGGER_DEPLOYMENT_VERSION=\${TRIGGER_DEPLOYMENT_VERSION} \ + TRIGGER_PROJECT_REF=\${TRIGGER_PROJECT_REF} \ + TRIGGER_CONTENT_HASH=\${TRIGGER_CONTENT_HASH} \ + TRIGGER_SECRET_KEY=\${TRIGGER_SECRET_KEY} \ + TRIGGER_API_URL=\${TRIGGER_API_URL} \ + NODE_EXTRA_CA_CERTS=\${NODE_EXTRA_CA_CERTS} \ + NODE_ENV=production \ + NODE_OPTIONS="--max_old_space_size=8192" + +# Run the indexer +RUN node ${buildManifest.indexerEntryPoint} + +# Development or production stage builds upon the base stage +FROM base AS final + +USER node +WORKDIR /app + +ENV TRIGGER_PROJECT_ID=\${TRIGGER_PROJECT_ID} \ + TRIGGER_DEPLOYMENT_ID=\${TRIGGER_DEPLOYMENT_ID} \ + TRIGGER_DEPLOYMENT_VERSION=\${TRIGGER_DEPLOYMENT_VERSION} \ + TRIGGER_CONTENT_HASH=\${TRIGGER_CONTENT_HASH} \ + TRIGGER_PROJECT_REF=\${TRIGGER_PROJECT_REF} \ + NODE_EXTRA_CA_CERTS=\${NODE_EXTRA_CA_CERTS} \ + NODE_ENV=production \ + NODE_OPTIONS="--max_old_space_size=8192" + +# Copy the files from the install stage +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", "${buildManifest.workerEntryPoint}" ] +CMD [] + `; +} diff --git a/packages/cli-v3/src/deploy/logs.ts b/packages/cli-v3/src/deploy/logs.ts new file mode 100644 index 000000000..d0ad58ae9 --- /dev/null +++ b/packages/cli-v3/src/deploy/logs.ts @@ -0,0 +1,147 @@ +import { log } from "@clack/prompts"; +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { chalkError, chalkWarning, cliLink } from "../utilities/cliOutput.js"; +import { createTempDir } from "../utilities/fileSystem.js"; +import { docs, getInTouch } from "../utilities/links.js"; + +export type WarningsCheckReturn = + | { + ok: true; + warnings: string[]; + } + | { + ok: false; + summary: string; + errors: string[]; + warnings: string[]; + }; + +export type LogParserOptions = Array<{ + regex: RegExp; + message: string; + shouldFail?: boolean; +}>; + +export async function saveLogs(shortCode: string, logs: string) { + const logPath = join(await createTempDir(), `build-${shortCode}.log`); + await writeFile(logPath, logs); + return logPath; +} + +export function printErrors(errors?: string[]) { + for (const error of errors ?? []) { + log.error(`${chalkError("Error:")} ${error}`); + } +} + +export function printWarnings(warnings?: string[]) { + for (const warning of warnings ?? []) { + log.warn(`${chalkWarning("Warning:")} ${warning}`); + } +} + +// Try to extract useful error messages from the logs +export function checkLogsForErrors(logs: string) { + const errors: LogParserOptions = [ + { + regex: /Error: Provided --schema at (?.*) doesn't exist/, + message: `Prisma generate failed to find the specified schema at "$schema".\nDid you include it in config.additionalFiles? ${cliLink( + "Config docs", + docs.config.prisma + )}`, + }, + { + regex: /@prisma\/client did not initialize yet/, + message: `Prisma client not initialized yet.\nDid you forget to add the postinstall script? ${cliLink( + "Config docs", + docs.config.prisma + )}`, + }, + { + regex: /sh: 1: (?.*): not found/, + message: `$packageOrBinary not found\n\nIf it's a package: Include it in ${cliLink( + "config.additionalPackages", + docs.config.prisma + )}\nIf it's a binary: Please ${cliLink( + "get in touch", + getInTouch + )} and we'll see what we can do!`, + }, + ]; + + for (const error of errors) { + const matches = logs.match(error.regex); + + if (!matches) { + continue; + } + + const message = getMessageFromTemplate(error.message, matches.groups); + + log.error(`${chalkError("Error:")} ${message}`); + break; + } +} + +function getMessageFromTemplate(template: string, replacer: RegExpMatchArray["groups"]) { + let message = template; + + if (replacer) { + for (const [key, value] of Object.entries(replacer)) { + message = message.replaceAll(`$${key}`, value); + } + } + + return message; +} + +// Try to extract useful warnings from logs. Sometimes we may even want to fail the build. This won't work if the step is cached. +export function checkLogsForWarnings(logs: string): WarningsCheckReturn { + const warnings: LogParserOptions = [ + { + regex: /prisma:warn We could not find your Prisma schema/, + message: `Prisma generate failed to find the default schema. Did you include it in config.additionalFiles? ${cliLink( + "Config docs", + docs.config.prisma + )}\nCustom schema paths require a postinstall script like this: \`prisma generate --schema=./custom/path/to/schema.prisma\``, + shouldFail: true, + }, + ]; + + const errorMessages: string[] = []; + const warningMessages: string[] = []; + + let shouldFail = false; + + for (const warning of warnings) { + const matches = logs.match(warning.regex); + + if (!matches) { + continue; + } + + const message = getMessageFromTemplate(warning.message, matches.groups); + + if (warning.shouldFail) { + shouldFail = true; + errorMessages.push(message); + } else { + warningMessages.push(message); + } + } + + if (shouldFail) { + return { + ok: false, + summary: "Build succeeded with critical warnings. Will not proceed", + warnings: warningMessages, + errors: errorMessages, + }; + } + + return { + ok: true, + warnings: warningMessages, + }; +} diff --git a/packages/cli-v3/src/dev/devSession.ts b/packages/cli-v3/src/dev/devSession.ts index e353168f0..4f0e8df9b 100644 --- a/packages/cli-v3/src/dev/devSession.ts +++ b/packages/cli-v3/src/dev/devSession.ts @@ -1,5 +1,5 @@ import { DEFAULT_RUNTIME, ResolvedConfig } from "@trigger.dev/core/v3/build"; -import { BuildManifest } from "@trigger.dev/core/v3/schemas"; +import { BuildManifest, TaskFile } from "@trigger.dev/core/v3/schemas"; import * as esbuild from "esbuild"; import { CliApiClient } from "../apiClient.js"; import { @@ -24,6 +24,11 @@ import { logger } from "../utilities/logger.js"; import { EphemeralDirectory, getTmpDir } from "../utilities/tempDirectories.js"; import { startDevOutput } from "./devOutput.js"; import { startWorkerRuntime } from "./workerRuntime.js"; +import { VERSION } from "../version.js"; +import { CORE_VERSION } from "@trigger.dev/core/v3"; +import { join } from "node:path"; +import { readFile } from "node:fs/promises"; +import { resolveFileSources } from "../utilities/sourceFiles.js"; export type DevSessionOptions = { name: string | undefined; @@ -172,8 +177,12 @@ async function createBuildManifestFromBundle( const buildManifest: BuildManifest = { contentHash: bundle.contentHash, runtime: resolvedConfig.runtime ?? DEFAULT_RUNTIME, + cliPackageVersion: VERSION, + packageVersion: CORE_VERSION, + environment: "dev", target: "dev", files: bundle.files, + sources: await resolveFileSources(bundle.files, resolvedConfig.workingDir), externals: [], config: { project: resolvedConfig.project, diff --git a/packages/cli-v3/src/dev/workerRuntime.ts b/packages/cli-v3/src/dev/workerRuntime.ts index 3b27c162c..2c4f4d906 100644 --- a/packages/cli-v3/src/dev/workerRuntime.ts +++ b/packages/cli-v3/src/dev/workerRuntime.ts @@ -18,17 +18,14 @@ import { ClientRequestArgs } from "node:http"; import { WebSocket } from "partysocket"; import { ClientOptions, WebSocket as wsWebSocket } from "ws"; import { CliApiClient } from "../apiClient.js"; +import { getInstrumentedPackageNames } from "../build/instrumentation.js"; import { DevCommandOptions } from "../commands/dev.js"; import { chalkError, chalkTask } from "../utilities/cliOutput.js"; import { resolveDotEnvVars } from "../utilities/dotEnv.js"; import { eventBus } from "../utilities/eventBus.js"; import { logger } from "../utilities/logger.js"; -import { VERSION } from "../version.js"; import { BackgroundWorker, BackgroundWorkerCoordinator } from "./backgroundWorker.js"; -import { getInstrumentedPackageNames } from "../build/instrumentation.js"; -import { readFile } from "node:fs/promises"; -import { join } from "node:path"; -import { createHash } from "node:crypto"; +import { resolveTaskSourceFiles } from "../utilities/sourceFiles.js"; export interface WorkerRuntime { shutdown(): Promise; @@ -186,19 +183,16 @@ class DevWorkerRuntime implements WorkerRuntime { return; } - const fileContents = await this.#fetchTaskFiles( - backgroundWorker.manifest.tasks, - this.options.config.workingDir - ); + const sourceFiles = resolveTaskSourceFiles(manifest.sources, backgroundWorker.manifest.tasks); const backgroundWorkerBody: CreateBackgroundWorkerRequestBody = { localOnly: true, metadata: { - packageVersion: VERSION, - cliPackageVersion: VERSION, + packageVersion: manifest.packageVersion, + cliPackageVersion: manifest.cliPackageVersion, tasks: backgroundWorker.manifest.tasks, contentHash: manifest.contentHash, - fileContents, + sourceFiles, }, supportsLazyAttempts: true, }; @@ -219,7 +213,10 @@ class DevWorkerRuntime implements WorkerRuntime { eventBus.emit("backgroundWorkerInitialized", backgroundWorker); } - async #fetchTaskFiles(tasks: TaskManifest[], workingDir: string) { + async #fetchTaskFiles( + sources: Record, + tasks: TaskManifest[] + ) { const tasksGroupedByFile: Record = {}; for (const task of tasks) { @@ -238,16 +235,18 @@ class DevWorkerRuntime implements WorkerRuntime { }> = []; for (const [filePath, tasks] of Object.entries(tasksGroupedByFile)) { - const contents = await readFile(join(workingDir, filePath), "utf-8"); + const source = sources[filePath]; + + if (!source) { + continue; + } + const taskIds = tasks.map((task) => task.id); - const hasher = createHash("md5"); - hasher.update(contents); taskFiles.push({ - filePath, + ...source, taskIds, - contents, - contentHash: hasher.digest("hex"), + filePath, }); } diff --git a/packages/cli-v3/src/entryPoints/deploy-indexer.ts b/packages/cli-v3/src/entryPoints/deploy-indexer.ts new file mode 100644 index 000000000..ccd2c3d39 --- /dev/null +++ b/packages/cli-v3/src/entryPoints/deploy-indexer.ts @@ -0,0 +1,237 @@ +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>; + +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")}`); + } + + const tasks = taskCatalog.listTaskManifests(); + + const workerManifest: WorkerManifest = { tasks, configPath: buildManifest.configPath }; + + 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 = [Error | null, T | null]; + +async function $import(path: string): Promise> { + try { + const module = await import(path); + + return [null, module]; + } catch (error) { + return [error as Error, null]; + } +} + +function injectEnvVars(env: Record) { + for (const [key, value] of Object.entries(env)) { + process.env[key] = value; + } +} diff --git a/packages/cli-v3/src/utilities/buildManifest.ts b/packages/cli-v3/src/utilities/buildManifest.ts new file mode 100644 index 000000000..af80e2a02 --- /dev/null +++ b/packages/cli-v3/src/utilities/buildManifest.ts @@ -0,0 +1,11 @@ +import { BuildManifest } from "@trigger.dev/core/v3/schemas"; + +export function buildManifestToJSON(manifest: BuildManifest): BuildManifest { + const { deploy, build, ...rest } = manifest; + + return { + ...rest, + deploy: {}, + build: {}, + }; +} diff --git a/packages/cli-v3/src/utilities/cliOutput.ts b/packages/cli-v3/src/utilities/cliOutput.ts index 2d4a888a6..18069e77d 100644 --- a/packages/cli-v3/src/utilities/cliOutput.ts +++ b/packages/cli-v3/src/utilities/cliOutput.ts @@ -2,6 +2,8 @@ import { log } from "@clack/prompts"; import chalk from "chalk"; import terminalLink, { Options as TerminalLinkOptions } from "terminal-link"; +export const isInteractive = process.stdin.isTTY; + export const green = "#4FFF54"; export const purple = "#735BF3"; diff --git a/packages/cli-v3/src/utilities/getUserPackageManager.ts b/packages/cli-v3/src/utilities/getUserPackageManager.ts deleted file mode 100644 index 5d88ae316..000000000 --- a/packages/cli-v3/src/utilities/getUserPackageManager.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { findUp } from "find-up"; -import { basename } from "path"; -import { logger } from "./logger.js"; - -export type PackageManager = "npm" | "pnpm" | "yarn"; -export const LOCKFILES = { - npm: "package-lock.json", - npmShrinkwrap: "npm-shrinkwrap.json", - pnpm: "pnpm-lock.yaml", - yarn: "yarn.lock", - bun: "bun.lockb", -}; - -export async function getUserPackageManager(path: string): Promise { - const packageManager = await detectPackageManager(path); - logger.debug("Detected package manager", { packageManager }); - return packageManager; -} - -async function detectPackageManager(path: string): Promise { - try { - return await detectPackageManagerFromArtifacts(path); - } catch (error) { - return detectPackageManagerFromCurrentCommand(); - } -} - -function detectPackageManagerFromCurrentCommand(): PackageManager { - // This environment variable is set by npm and yarn but pnpm seems less consistent - const userAgent = process.env.npm_config_user_agent; - - if (userAgent) { - if (userAgent.startsWith("yarn")) { - return "yarn"; - } else if (userAgent.startsWith("pnpm")) { - return "pnpm"; - } else { - return "npm"; - } - } else { - // If no user agent is set, assume npm - return "npm"; - } -} - -export async function detectPackageManagerFromArtifacts(path: string): Promise { - const foundPath = await findUp(Object.values(LOCKFILES), { cwd: path }); - - if (!foundPath) { - throw new Error("Could not detect package manager from artifacts"); - } - - logger.debug("Found path from package manager artifacts", { foundPath }); - - switch (basename(foundPath)) { - case LOCKFILES.yarn: - logger.debug("Found yarn artifact", { foundPath }); - return "yarn"; - case LOCKFILES.pnpm: - logger.debug("Found pnpm artifact", { foundPath }); - return "pnpm"; - case LOCKFILES.npm: - case LOCKFILES.npmShrinkwrap: - logger.debug("Found npm artifact", { foundPath }); - return "npm"; - case LOCKFILES.bun: - logger.debug("Found bun artifact", { foundPath }); - return "npm"; - default: - throw new Error(`Unhandled package manager detection path: ${foundPath}`); - } -} diff --git a/packages/cli-v3/src/utilities/initialBanner.ts b/packages/cli-v3/src/utilities/initialBanner.ts index 3924fae99..45b4ef39b 100644 --- a/packages/cli-v3/src/utilities/initialBanner.ts +++ b/packages/cli-v3/src/utilities/initialBanner.ts @@ -1,11 +1,9 @@ import chalk from "chalk"; -import type { Result } from "update-check"; -import checkForUpdate from "update-check"; -import { chalkGrey, chalkRun, chalkTask, chalkWorker, green, logo } from "./cliOutput.js"; +import { getLatestVersion } from "fast-npm-meta"; +import { VERSION } from "../version.js"; +import { chalkGrey, chalkRun, chalkTask, chalkWorker, logo } from "./cliOutput.js"; import { logger } from "./logger.js"; import { spinner } from "./windows.js"; -import { readPackageJSON } from "pkg-types"; -import { VERSION } from "../version.js"; export async function printInitialBanner(performUpdateCheck = true) { const text = `\n${logo()} ${chalkGrey(`(${VERSION})`)}\n`; @@ -14,24 +12,29 @@ export async function printInitialBanner(performUpdateCheck = true) { let maybeNewVersion: string | undefined; if (performUpdateCheck) { - const loadingSpinner = spinner(); - loadingSpinner.start("Checking for updates"); + const $spinner = spinner(); + $spinner.start("Checking for updates"); maybeNewVersion = await updateCheck(); // Log a slightly more noticeable message if this is a major bump if (maybeNewVersion !== undefined) { - loadingSpinner.stop(`Update available ${chalk.green(maybeNewVersion)}`); + $spinner.stop(`Update available ${chalk.green(maybeNewVersion)}`); + const currentMajor = parseInt(VERSION.split(".")[0]!); const newMajor = parseInt(maybeNewVersion.split(".")[0]!); + + logger.debug(`updateCheck: ${VERSION} -> ${maybeNewVersion}`); + if (newMajor > currentMajor) { logger.warn( `Please update to the latest version of \`trigger.dev\` to prevent critical errors. Run \`npm install --save-dev trigger.dev@${newMajor}\` to update to the latest version. After installation, run Trigger.dev with \`npx trigger.dev\`.` ); + } else { } } else { - loadingSpinner.stop("On latest version"); + $spinner.stop("On latest version"); } } } @@ -67,17 +70,33 @@ export function printDevBanner(printTopBorder = true) { } async function doUpdateCheck(): Promise { - let update: Result | null = null; try { - const pkg = await readPackageJSON(); // default cache for update check is 1 day - update = await checkForUpdate.default(pkg, { - distTag: VERSION.startsWith("3.0.0-beta") ? "beta" : "latest", - }); + const meta = await getLatestVersion( + `trigger.dev@${VERSION.startsWith("3.0.0-beta") ? "beta" : "latest"}`, + { force: true } + ); + + if (!meta.version) { + return; + } + + const compareVersions = (a: string, b: string) => + a.localeCompare(b, "en-US", { numeric: true }); + + const comparison = compareVersions(VERSION, meta.version); + + if (comparison === -1) { + return meta.version; + } + + return; } catch (err) { // ignore error + logger.debug(err); + + return; } - return update?.latest; } //only do this once while the cli is running diff --git a/packages/cli-v3/src/utilities/javascriptProject.ts b/packages/cli-v3/src/utilities/javascriptProject.ts deleted file mode 100644 index 2b0fc1365..000000000 --- a/packages/cli-v3/src/utilities/javascriptProject.ts +++ /dev/null @@ -1,706 +0,0 @@ -import { $, ExecaError } from "execa"; -import { join } from "node:path"; -import { readJSONFileSync } from "./fileSystem.js"; -import { logger } from "./logger.js"; -import { PackageManager, getUserPackageManager } from "./getUserPackageManager.js"; -import { PackageJson } from "type-fest"; -import { assertExhaustive } from "./assertExhaustive.js"; -import { builtinModules } from "node:module"; -import { tracer } from "../cli/common.js"; -import { recordSpanException } from "@trigger.dev/core/v3/otel"; -import { flattenAttributes } from "@trigger.dev/core/v3"; - -export type ResolveOptions = { allowDev: boolean }; -export type DependencyMeta = { version: string; external: boolean }; - -export class JavascriptProject { - private _packageJson?: PackageJson; - private _packageManager?: PackageManager; - - constructor(private projectPath: string) {} - - private get packageJson() { - if (!this._packageJson) { - this._packageJson = readJSONFileSync(join(this.projectPath, "package.json")) as PackageJson; - } - - return this._packageJson; - } - - public get allowedPackageJson(): Record { - const disallowedKeys = [ - "scripts", - "devDependencies", - "dependencies", - "peerDependencies", - "author", - "contributors", - "funding", - "bugs", - "files", - "keywords", - "main", - "module", - "type", - "bin", - "browser", - "man", - "directories", - "repository", - "peerDependenciesMeta", - "optionalDependencies", - "engines", - "os", - "cpu", - "private", - "publishConfig", - "workspaces", - ]; - - return Object.keys(this.packageJson).reduce( - (acc, key) => { - if (!disallowedKeys.includes(key)) { - acc[key] = this.packageJson[key]; - } - - return acc; - }, - {} as Record - ); - } - - public get scripts(): Record { - return this.#filterScripts(); - } - - #filterScripts(): Record { - if (!this.packageJson.scripts || typeof this.packageJson.scripts !== "object") { - return {}; - } - - return this.packageJson.scripts as Record; - } - - async install(): Promise { - const command = await this.#getCommand(); - - try { - await command.installDependencies({ - cwd: this.projectPath, - }); - } catch (error) { - logger.debug(`Failed to install dependencies using ${command.name}`, { - error, - }); - } - } - - async extractDirectDependenciesMeta(): Promise> { - return tracer.startActiveSpan( - "JavascriptProject.extractDirectDependenciesMeta", - async (span) => { - const command = await this.#getCommand(); - - span.setAttributes({ - packageManager: command.name, - }); - - try { - span.end(); - return await command.extractDirectDependenciesMeta({ - cwd: this.projectPath, - }); - } catch (error) { - recordSpanException(span, error); - span.end(); - - logger.debug(`Failed to resolve internal dependencies using ${command.name}`, { - error, - }); - - throw error; - } - } - ); - } - - async resolveAll(packageNames: string[]): Promise> { - return tracer.startActiveSpan("JavascriptProject.resolveAll", async (span) => { - const externalPackages = packageNames.filter((packageName) => !isBuiltInModule(packageName)); - - const command = await this.#getCommand(); - - span.setAttributes({ - externalPackages, - packageManager: command.name, - }); - - try { - const versions = await command.resolveDependencyVersions(externalPackages, { - cwd: this.projectPath, - }); - - if (versions) { - logger.debug(`Resolved [${externalPackages.join(", ")}] version using ${command.name}`, { - versions, - }); - - span.setAttributes({ - ...flattenAttributes(versions, "versions"), - }); - } - - // Merge the resolved versions with the package.json dependencies - const missingPackages = externalPackages.filter((packageName) => !versions[packageName]); - const missingPackageVersions: Record = {}; - - for (const packageName of missingPackages) { - const packageJsonVersion = this.packageJson.dependencies?.[packageName]; - - if (typeof packageJsonVersion === "string") { - logger.debug(`Resolved ${packageName} version using package.json`, { - packageJsonVersion, - }); - - missingPackageVersions[packageName] = packageJsonVersion; - } - } - - span.setAttributes({ - ...flattenAttributes(missingPackageVersions, "missingPackageVersions"), - missingPackages, - }); - - span.end(); - - return { ...versions, ...missingPackageVersions }; - } catch (error) { - recordSpanException(span, error); - span.end(); - - logger.debug(`Failed to resolve dependency versions using ${command.name}`, { - packageNames, - error, - }); - - return {}; - } - }); - } - - async resolve(packageName: string, options?: ResolveOptions): Promise { - if (isBuiltInModule(packageName)) { - return undefined; - } - - const opts = { allowDev: false, ...options }; - - const command = await this.#getCommand(); - - try { - const version = await command.resolveDependencyVersion(packageName, { - cwd: this.projectPath, - }); - - if (version) { - logger.debug(`Resolved ${packageName} version using ${command.name}`, { version }); - - return version; - } - - const packageJsonVersion = this.packageJson.dependencies?.[packageName]; - - if (typeof packageJsonVersion === "string") { - logger.debug(`Resolved ${packageName} version using package.json`, { packageJsonVersion }); - - return packageJsonVersion; - } - - if (opts.allowDev) { - const devPackageJsonVersion = this.packageJson.devDependencies?.[packageName]; - - if (typeof devPackageJsonVersion === "string") { - logger.debug(`Resolved ${packageName} version using devDependencies`, { - devPackageJsonVersion, - }); - - return devPackageJsonVersion; - } - } - } catch (error) { - logger.debug(`Failed to resolve dependency version using ${command.name}`, { - packageName, - error, - }); - } - - return; - } - - async #getCommand(): Promise { - const packageManager = await this.getPackageManager(); - - switch (packageManager) { - case "npm": - return new NPMCommands(); - case "pnpm": - return new PNPMCommands(); - case "yarn": - return new YarnCommands(); - default: - assertExhaustive(packageManager); - } - } - - async getPackageManager(): Promise { - if (!this._packageManager) { - this._packageManager = await getUserPackageManager(this.projectPath); - } - - return this._packageManager; - } -} - -type PnpmList = { - name: string; - path: string; - version: string; - private: boolean; - dependencies?: Record< - string, - { - from: string; - version: string; - resolved: string; - path: string; - } - >; -}[]; - -type PackageManagerOptions = { - cwd?: string; -}; - -interface PackageManagerCommands { - name: string; - - installDependencies(options: PackageManagerOptions): Promise; - - extractDirectDependenciesMeta( - options: PackageManagerOptions - ): Promise>; - - resolveDependencyVersion( - packageName: string, - options: PackageManagerOptions - ): Promise; - - resolveDependencyVersions( - packageNames: string[], - options: PackageManagerOptions - ): Promise>; -} - -class PNPMCommands implements PackageManagerCommands { - get name() { - return "pnpm"; - } - - private get cmd() { - return process.platform === "win32" ? "pnpm.cmd" : "pnpm"; - } - - async installDependencies(options: PackageManagerOptions) { - const { stdout, stderr } = await $({ cwd: options.cwd })`${this.cmd} install`; - - logger.debug(`Installing dependencies using ${this.name}`, { stdout, stderr }); - } - - async resolveDependencyVersion(packageName: string, options: PackageManagerOptions) { - const { stdout } = await $({ cwd: options.cwd })`${this.cmd} list ${packageName} -r --json`; - const result = JSON.parse(stdout) as PnpmList; - - logger.debug(`Resolving ${packageName} version using ${this.name}`); - - // Return the first dependency version that matches the package name - for (const dep of result) { - const dependency = dep.dependencies?.[packageName]; - - if (dependency) { - return dependency.version; - } - } - - return; - } - - async resolveDependencyVersions( - packageNames: string[], - options: PackageManagerOptions - ): Promise> { - const result = await this.#listDependencies(packageNames, options); - - logger.debug(`Resolving ${packageNames.join(" ")} version using ${this.name}`); - - const results: Record = {}; - - // Return the first dependency version that matches the package name - for (const dep of result) { - for (const packageName of packageNames) { - const dependency = dep.dependencies?.[packageName]; - - if (dependency) { - results[packageName] = dependency.version; - } - } - } - - return results; - } - - async extractDirectDependenciesMeta(options: PackageManagerOptions) { - const result = await this.#listDirectDependencies(options); - - logger.debug(`Extracting direct dependencies metadata using ${this.name}`); - - const results: Record = {}; - - for (const projectPkg of result) { - results[projectPkg.name] = { version: projectPkg.version, external: false }; - - if (projectPkg.dependencies) { - for (const [name, dep] of Object.entries(projectPkg.dependencies)) { - const { version } = dep; - - results[name] = { - version, - external: !version.startsWith("link:"), - }; - } - } - } - - return results; - } - - async #listDirectDependencies(options: PackageManagerOptions) { - const childProcess = await $({ - cwd: options.cwd, - reject: false, - })`${this.cmd} list --recursive --json`; - - if (childProcess.failed) { - logger.debug("Failed to list dependencies, using stdout anyway...", { - error: childProcess, - }); - } - - return JSON.parse(childProcess.stdout) as PnpmList; - } - - async #listDependencies(packageNames: string[], options: PackageManagerOptions) { - const childProcess = await $({ - cwd: options.cwd, - reject: false, - })`${this.cmd} list ${packageNames} -r --json`; - - if (childProcess.failed) { - logger.debug("Failed to list dependencies, using stdout anyway...", { - error: childProcess, - }); - } - - return JSON.parse(childProcess.stdout) as PnpmList; - } -} - -type NpmDependency = { - version: string; - resolved: string; - overridden: boolean; - required?: { version: string }; - dependencies?: Record; -}; - -type NpmListOutput = { - dependencies: Record; -}; - -class NPMCommands implements PackageManagerCommands { - get name() { - return "npm"; - } - - private get cmd() { - return process.platform === "win32" ? "npm.cmd" : "npm"; - } - - async installDependencies(options: PackageManagerOptions) { - const { stdout, stderr } = await $({ cwd: options.cwd })`${this.cmd} install`; - - logger.debug(`Installing dependencies using ${this.name}`, { stdout, stderr }); - } - - async resolveDependencyVersion(packageName: string, options: PackageManagerOptions) { - const { stdout } = await $({ cwd: options.cwd })`${this.cmd} list ${packageName} --json`; - const output = JSON.parse(stdout) as NpmListOutput; - - logger.debug(`Resolving ${packageName} version using ${this.name}`, { output }); - - return this.#recursivelySearchDependencies(output.dependencies, packageName); - } - - async resolveDependencyVersions( - packageNames: string[], - options: PackageManagerOptions - ): Promise> { - const output = await this.#listDependencies(packageNames, options); - - logger.debug(`Resolving ${packageNames.join(" ")} version using ${this.name}`, { output }); - - const results: Record = {}; - - for (const packageName of packageNames) { - const version = this.#recursivelySearchDependencies(output.dependencies, packageName); - - if (version) { - results[packageName] = version; - } - } - - return results; - } - - async extractDirectDependenciesMeta( - options: PackageManagerOptions - ): Promise> { - const result = await this.#listDirectDependencies(options); - - logger.debug(`Extracting direct dependencies metadata using ${this.name}`); - - return result.dependencies ? this.#flattenDependenciesMeta(result.dependencies) : {}; - } - - async #listDirectDependencies(options: PackageManagerOptions) { - const childProcess = await $({ - cwd: options.cwd, - reject: false, - })`${this.cmd} list --json`; - - if (childProcess.failed) { - logger.debug("Failed to list dependencies, using stdout anyway...", { - error: childProcess, - }); - } - - return JSON.parse(childProcess.stdout) as NpmListOutput; - } - - async #listDependencies(packageNames: string[], options: PackageManagerOptions) { - const childProcess = await $({ - cwd: options.cwd, - reject: false, - })`${this.cmd} list ${packageNames} --json`; - - if (childProcess.failed) { - logger.debug("Failed to list dependencies, using stdout anyway...", { - error: childProcess, - }); - } - - return JSON.parse(childProcess.stdout) as NpmListOutput; - } - - #recursivelySearchDependencies( - dependencies: Record, - packageName: string - ): string | undefined { - for (const [name, dependency] of Object.entries(dependencies)) { - if (name === packageName) { - return dependency.version; - } - - if (dependency.dependencies) { - const result = this.#recursivelySearchDependencies(dependency.dependencies, packageName); - - if (result) { - return result; - } - } - } - - return; - } - - #flattenDependenciesMeta( - dependencies: Record - ): Record { - let results: Record = {}; - - for (const [name, dep] of Object.entries(dependencies)) { - const { version, resolved, dependencies: children } = dep; - results[name] = { version, external: !!resolved && !resolved.startsWith("file:") }; - - if (children) { - results = { ...results, ...this.#flattenDependenciesMeta(children) }; - } - } - - return results; - } -} - -class YarnCommands implements PackageManagerCommands { - get name() { - return "yarn"; - } - - private get cmd() { - return process.platform === "win32" ? "yarn.cmd" : "yarn"; - } - - async installDependencies(options: PackageManagerOptions) { - const { stdout, stderr } = await $({ cwd: options.cwd })`${this.cmd} install`; - - logger.debug(`Installing dependencies using ${this.name}`, { stdout, stderr }); - } - - async resolveDependencyVersion(packageName: string, options: PackageManagerOptions) { - const { stdout } = await $({ cwd: options.cwd })`${this.cmd} info ${packageName} --json`; - - const lines = stdout.split("\n"); - - logger.debug(`Resolving ${packageName} version using ${this.name}`); - - for (const line of lines) { - const json = JSON.parse(line); - - if (json.value === packageName) { - return json.children.Version; - } - } - } - - async resolveDependencyVersions( - packageNames: string[], - options: PackageManagerOptions - ): Promise> { - const stdout = await this.#listDependencies(packageNames, options); - - const lines = stdout.split("\n"); - - logger.debug(`Resolving ${packageNames.join(" ")} version using ${this.name}`); - - const results: Record = {}; - - for (const line of lines) { - const json = JSON.parse(line); - - const packageName = this.#parseYarnValueIntoPackageName(json.value); - - if (packageNames.includes(packageName)) { - results[packageName] = json.children.Version; - } - } - - return results; - } - - async extractDirectDependenciesMeta(options: PackageManagerOptions) { - const result = await this.#listDirectDependencies(options); - - const rawPackagesData = result.split("\n"); - logger.debug(`Extracting direct dependencies metadata using ${this.name}`); - - const results: Record = {}; - - for (const rawPackageData of rawPackagesData) { - const packageData = JSON.parse(rawPackageData); - - const [name, dependencyMeta] = this.#parseYarnValueIntoDependencyMeta(packageData.value); - results[name] = dependencyMeta; - } - - return results; - } - - async #listDirectDependencies(options: PackageManagerOptions) { - const childProcess = await $({ - cwd: options.cwd, - reject: false, - })`${this.cmd} info --all --json`; - - if (childProcess.failed) { - logger.debug("Failed to list dependencies, using stdout anyway...", { - error: childProcess, - }); - } - - return childProcess.stdout; - } - - async #listDependencies(packageNames: string[], options: PackageManagerOptions) { - const childProcess = await $({ - cwd: options.cwd, - reject: false, - })`${this.cmd} info ${packageNames} --json`; - - if (childProcess.failed) { - logger.debug("Failed to list dependencies, using stdout anyway...", { - error: childProcess, - }); - } - - return childProcess.stdout; - } - - // The "value" when doing yarn info is formatted like this: - // "package-name@npm:version" or "package-name@workspace:version" - // This function will parse the value into just the package name. - // This correctly handles scoped packages as well e.g. @scope/package-name@npm:version - #parseYarnValueIntoPackageName(value: string): string { - const parts = value.split("@"); - - // If the value does not contain an "@" symbol, then it's just the package name - if (parts.length === 3) { - return parts[1] as string; - } - - // If the value contains an "@" symbol, then the package name is the first part - return parts[0] as string; - } - - #parseYarnValueIntoDependencyMeta(value: string): [string, DependencyMeta] { - const parts = value.split("@"); - let name: string, protocol: string, version: string; - - if (parts.length === 3) { - // e.g. @/@: -> ["", "/"", ":""] - name = `@${parts[1]}`; - [protocol = "", version = ""] = parts[2]!.split(":"); - } else if (parts.length === 2) { - // e.g. @: -> [""", ":""] - name = parts[0]!.toString(); - [protocol = "", version = ""] = parts[1]!.split(":"); - } else { - throw new Error("Failed parsing ${value} into dependency meta"); - } - - return [ - name, - { - version, - external: protocol !== "workspace" && protocol !== "file", - }, - ]; - } -} - -function isBuiltInModule(module: string): boolean { - // if the module has node: prefix, it's a built-in module - if (module.startsWith("node:")) { - return true; - } - - return builtinModules.includes(module); -} diff --git a/packages/cli-v3/src/utilities/resolveEnvVars.ts b/packages/cli-v3/src/utilities/resolveEnvVars.ts deleted file mode 100644 index c0be51a1e..000000000 --- a/packages/cli-v3/src/utilities/resolveEnvVars.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { logger } from "./logger.js"; - -export async function callResolveEnvVars( - configModule: any, - env: Record, - environment: string, - projectRef: string -): Promise<{ variables: Record; override: boolean } | undefined> { - if ( - configModule && - configModule.resolveEnvVars && - typeof configModule.resolveEnvVars === "function" - ) { - let resolvedEnvVars: Record = {}; - - try { - let result = await configModule.resolveEnvVars({ - projectRef, - environment, - env, - }); - - if (!result) { - return; - } - - result = await result; - - if (typeof result === "object" && result !== null && "variables" in result) { - const variables = result.variables; - - if (Array.isArray(variables)) { - for (const item of variables) { - if ( - typeof item === "object" && - item !== null && - "name" in item && - "value" in item && - typeof item.name === "string" && - typeof item.value === "string" - ) { - resolvedEnvVars[item.name] = item.value; - } - } - } else if (typeof variables === "object") { - for (const [key, value] of Object.entries(variables)) { - if (typeof key === "string" && typeof value === "string") { - resolvedEnvVars[key] = value; - } - } - } - } - - return { - variables: resolvedEnvVars, - override: result.override, - }; - } catch (error) { - logger.error(error); - } - } - - return; -} diff --git a/packages/cli-v3/src/utilities/runtimeCheck.ts b/packages/cli-v3/src/utilities/runtimeCheck.ts index 7e07955d6..254b950d4 100644 --- a/packages/cli-v3/src/utilities/runtimeCheck.ts +++ b/packages/cli-v3/src/utilities/runtimeCheck.ts @@ -1,9 +1,28 @@ +import { chalkError } from "./cliOutput.js"; import { logger } from "./logger.js"; +export type RuntimeMinimumVersion = { + major: number; + minor: number; +}; + +const REQUIRED_MINIMUM_VERSIONS: RuntimeMinimumVersion[] = [ + { major: 18, minor: 20 }, + { major: 20, minor: 5 }, +]; /** * This function is used by the dev CLI to make sure that the runtime is compatible */ -export function runtimeCheck(minimumMajor: number, minimumMinor: number) { +export function runtimeChecks() { + try { + REQUIRED_MINIMUM_VERSIONS.forEach((version) => runtimeCheck(version)); + } catch (e) { + logger.log(`${chalkError("X Error:")} ${e}`); + process.exit(1); + } +} + +function runtimeCheck(version: RuntimeMinimumVersion) { // Check if the runtime is Node.js if (typeof process === "undefined") { throw "The dev CLI can only be run in a Node.js compatible environment"; @@ -14,11 +33,11 @@ export function runtimeCheck(minimumMajor: number, minimumMinor: number) { const isBun = typeof process.versions.bun === "string"; - if (major < minimumMajor || (major === minimumMajor && minor < minimumMinor)) { + if (major < version.major || (major === version.major && minor < version.minor)) { if (isBun) { - throw `The dev CLI requires at least Node.js ${minimumMajor}.${minimumMinor}. You are running Bun ${process.versions.bun}, which is compatible with Node.js ${process.versions.node}`; + throw `The dev CLI requires at least Node.js ${version.major}.${version.minor}. You are running Bun ${process.versions.bun}, which is compatible with Node.js ${process.versions.node}`; } else { - throw `The dev CLI requires at least Node.js ${minimumMajor}.${minimumMinor}. You are running Node.js ${process.versions.node}`; + throw `The dev CLI requires at least Node.js ${version.major}.${version.minor}. You are running Node.js ${process.versions.node}`; } } diff --git a/packages/cli-v3/src/utilities/session.ts b/packages/cli-v3/src/utilities/session.ts index 0b50be786..1853a3847 100644 --- a/packages/cli-v3/src/utilities/session.ts +++ b/packages/cli-v3/src/utilities/session.ts @@ -98,6 +98,11 @@ export type GetEnvOptions = { }; export async function getProjectClient(options: GetEnvOptions) { + logger.debug( + `Initializing ${options.env} environment for project ${options.projectRef}`, + options.apiUrl + ); + const apiClient = new CliApiClient(options.apiUrl, options.accessToken); const projectEnv = await apiClient.getProjectEnv({ @@ -112,7 +117,7 @@ export async function getProjectClient(options: GetEnvOptions) { ); } else { logger.error( - `Failed to initialize dev environment: ${projectEnv.error}. Using project ref ${options.projectRef}` + `Failed to initialize ${options.env} environment: ${projectEnv.error}. Using project ref ${options.projectRef}` ); } @@ -122,6 +127,7 @@ export async function getProjectClient(options: GetEnvOptions) { const client = new CliApiClient(projectEnv.data.apiUrl, projectEnv.data.apiKey); return { + id: projectEnv.data.projectId, name: projectEnv.data.name, client, }; diff --git a/packages/cli-v3/src/utilities/sourceFiles.ts b/packages/cli-v3/src/utilities/sourceFiles.ts new file mode 100644 index 000000000..a31225b41 --- /dev/null +++ b/packages/cli-v3/src/utilities/sourceFiles.ts @@ -0,0 +1,73 @@ +import type { + BackgroundWorkerSourceFileMetadata, + TaskFile, + TaskManifest, +} from "@trigger.dev/core/v3/schemas"; +import { createHash } from "node:crypto"; +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; +import * as zlib from "node:zlib"; + +export async function resolveFileSources(files: TaskFile[], baseDir: string) { + const sources: Record = {}; + + for (const file of files) { + const fullPath = join(baseDir, file.entry); + const content = await readFile(fullPath, "utf-8"); + const hasher = createHash("md5"); + hasher.update(content); + + sources[file.entry] = { + contents: compressContent(content), + contentHash: hasher.digest("hex"), + }; + } + + return sources; +} + +export function resolveTaskSourceFiles( + sources: Record, + tasks: TaskManifest[] +): Array { + const tasksGroupedByFile: Record = {}; + + for (const task of tasks) { + if (!tasksGroupedByFile[task.filePath]) { + tasksGroupedByFile[task.filePath] = []; + } + + tasksGroupedByFile[task.filePath]!.push(task); + } + + const taskFiles: Array = []; + + for (const [filePath, tasks] of Object.entries(tasksGroupedByFile)) { + const source = sources[filePath]; + + if (!source) { + continue; + } + + const taskIds = tasks.map((task) => task.id); + + taskFiles.push({ + ...source, + taskIds, + filePath, + }); + } + + return taskFiles; +} + +function compressContent(data: string) { + // Convert data to string if it's not already + // Compress the data + const compressedData = zlib.deflateSync(data); + + // Encode the compressed data to base64 + const base64Encoded = compressedData.toString("base64"); + + return base64Encoded; +} diff --git a/packages/core/src/v3/apiClient/core.ts b/packages/core/src/v3/apiClient/core.ts index d7a3107cf..888edcad8 100644 --- a/packages/core/src/v3/apiClient/core.ts +++ b/packages/core/src/v3/apiClient/core.ts @@ -1,8 +1,8 @@ import { z } from "zod"; -import { fromZodError } from "zod-validation-error"; +import { fromZodError, ValidationError } from "zod-validation-error"; import { RetryOptions } from "../schemas/index.js"; import { calculateNextRetryDelay } from "../utils/retries.js"; -import { ApiConnectionError, ApiError } from "./errors.js"; +import { ApiConnectionError, ApiError, ApiSchemaValidationError } from "./errors.js"; import { Attributes, Span } from "@opentelemetry/api"; import { SemanticInternalAttributes } from "../semanticInternalAttributes.js"; @@ -232,12 +232,23 @@ async function _doZodFetchWithRetries( return { data: parsedResult.data, response }; } - throw fromZodError(parsedResult.error); + const validationError = fromZodError(parsedResult.error); + + throw new ApiSchemaValidationError({ + status: response.status, + cause: validationError, + message: validationError.message, + rawBody: jsonBody, + headers: responseHeaders, + }); } catch (error) { if (error instanceof ApiError) { throw error; } + if (error instanceof ValidationError) { + } + if (options?.retry) { const retry = { ...defaultRetryOptions, ...options.retry }; diff --git a/packages/core/src/v3/apiClient/errors.ts b/packages/core/src/v3/apiClient/errors.ts index 3f49e411c..4f84298dc 100644 --- a/packages/core/src/v3/apiClient/errors.ts +++ b/packages/core/src/v3/apiClient/errors.ts @@ -155,6 +155,32 @@ export class RateLimitError extends ApiError { export class InternalServerError extends ApiError {} +export class ApiSchemaValidationError extends ApiError { + override readonly status: 200 = 200; + readonly rawBody: any; + + constructor({ + message, + cause, + status, + rawBody, + headers, + }: { + message?: string; + cause?: Error | undefined; + status: number; + rawBody: any; + headers: APIHeaders | undefined; + }) { + super(status, undefined, message || "Validation error.", headers); + // in some environments the 'cause' property is already declared + // @ts-ignore + if (cause) this.cause = cause; + + this.rawBody = rawBody; + } +} + function castToError(err: any): Error { if (err instanceof Error) return err; return new Error(err); diff --git a/packages/core/src/v3/build/extensions.ts b/packages/core/src/v3/build/extensions.ts index e377ac6c8..1c4908277 100644 --- a/packages/core/src/v3/build/extensions.ts +++ b/packages/core/src/v3/build/extensions.ts @@ -24,10 +24,17 @@ export interface BuildExtension { ) => Promise | undefined | void; } +export interface BuildLogger { + debug: (...args: unknown[]) => void; + log: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; +} + export interface BuildContext { target: BuildTarget; config: ResolvedConfig; workingDir: string; + logger: BuildLogger; addLayer(layer: BuildLayer): void; registerPlugin(plugin: Plugin, options?: RegisterPluginOptions): void; @@ -47,6 +54,7 @@ export interface BuildLayer { }; deploy?: { env?: Record; + override?: boolean; }; dependencies?: Record; } diff --git a/packages/core/src/v3/build/resolvedConfig.ts b/packages/core/src/v3/build/resolvedConfig.ts index 54e05866c..4eb6952d2 100644 --- a/packages/core/src/v3/build/resolvedConfig.ts +++ b/packages/core/src/v3/build/resolvedConfig.ts @@ -2,6 +2,7 @@ import { type Defu } from "defu"; import type { Prettify } from "ts-essentials"; import { TriggerConfig } from "../config.js"; import { BuildRuntime } from "../schemas/build.js"; +import { ResolveEnvironmentVariablesFunction } from "../types/index.js"; export type ResolvedConfig = Prettify< Defu< @@ -23,5 +24,6 @@ export type ResolvedConfig = Prettify< packageJsonPath: string; lockfilePath: string; configFile?: string; + resolveEnvVars?: ResolveEnvironmentVariablesFunction; } ->; \ No newline at end of file +>; diff --git a/packages/core/src/v3/extensions/prisma.ts b/packages/core/src/v3/extensions/prisma.ts index 59ffb3888..0846f9a95 100644 --- a/packages/core/src/v3/extensions/prisma.ts +++ b/packages/core/src/v3/extensions/prisma.ts @@ -41,7 +41,7 @@ export class PrismaExtension implements BuildExtension { // Resolve the path to the prisma schema, relative to the config.directory this._resolvedSchemaPath = resolve(context.workingDir, this.options.schema); - console.log(`Resolved the prisma schema to: ${this._resolvedSchemaPath}`); + context.logger.debug(`Resolved the prisma schema to: ${this._resolvedSchemaPath}`); // Check that the prisma schema exists if (!existsSync(this._resolvedSchemaPath)) { @@ -58,7 +58,7 @@ export class PrismaExtension implements BuildExtension { assert(this._resolvedSchemaPath, "Resolved schema path is not set"); - console.log("Looking for @prisma/client in the externals", { + context.logger.debug("Looking for @prisma/client in the externals", { externals: manifest.externals, }); @@ -74,7 +74,7 @@ export class PrismaExtension implements BuildExtension { ); } - console.log(`PrismaExtension is generating the Prisma client for version ${version}`); + context.logger.debug(`PrismaExtension is generating the Prisma client for version ${version}`); // Now we need to add a layer that: // Copies the prisma schema to the build outputPath @@ -82,7 +82,7 @@ export class PrismaExtension implements BuildExtension { // Adds the `prisma generate` command, which generates the Prisma client const schemaDestinationPath = join(manifest.outputPath, "prisma", "schema.prisma"); // Copy the prisma schema to the build output path - console.log( + context.logger.debug( `Copying the prisma schema from ${this._resolvedSchemaPath} to ${schemaDestinationPath}` ); diff --git a/packages/core/src/v3/extensions/syncEnvVars.ts b/packages/core/src/v3/extensions/syncEnvVars.ts new file mode 100644 index 000000000..bc0c5b8c0 --- /dev/null +++ b/packages/core/src/v3/extensions/syncEnvVars.ts @@ -0,0 +1,156 @@ +import { BuildExtension } from "../build/extensions.js"; + +export type SyncEnvVarsBody = Record | Array<{ name: string; value: string }>; + +export type SyncEnvVarsResult = + | SyncEnvVarsBody + | Promise + | void + | undefined; + +export type SyncEnvVarsParams = { + projectRef: string; + environment: string; + env: Record; +}; + +const UNSYNCABLE_ENV_VARS = [ + "PWD", + "MallocNanoZone", + "USER", + "LANG", + "__CFBundleIdentifier", + "COMMAND_MODE", + "PATH", + "LOGNAME", + "SSLKEYLOGFILE", + "SSH_AUTH_SOCK", + "SHLVL", + "SHELL", + "HOME", + "__CF_USER_TEXT_ENCODING", + "XPC_SERVICE_NAME", + "XPC_FLAGS", + "ORIGINAL_XDG_CURRENT_DESKTOP", + "TERM_PROGRAM", + "TERM_PROGRAM_VERSION", + "COLORTERM", + "GIT_ASKPASS", + "VSCODE_GIT_ASKPASS_NODE", + "VSCODE_GIT_ASKPASS_EXTRA_ARGS", + "VSCODE_GIT_ASKPASS_MAIN", + "VSCODE_GIT_IPC_HANDLE", + "VSCODE_INJECTION", + "ZDOTDIR", + "USER_ZDOTDIR", + "TERM", + "OLDPWD", + "HOMEBREW_PREFIX", + "HOMEBREW_CELLAR", + "HOMEBREW_REPOSITORY", + "MANPATH", + "INFOPATH", + "__GIT_PROMPT_DIR", + "GIT_PROMPT_EXECUTABLE", + "NVM_DIR", + "NVM_CD_FLAGS", + "NVM_BIN", + "NVM_INC", + "BUN_INSTALL", + "DENO_INSTALL", + "GITHUB_TOKEN", + "TMPDIR", + "_", +]; + +export type SyncEnvVarsFunction = (params: SyncEnvVarsParams) => SyncEnvVarsResult; + +export type SyncEnvVarsOptions = { + override?: boolean; +}; + +export function syncEnvVars(fn: SyncEnvVarsFunction, options?: SyncEnvVarsOptions): BuildExtension { + return { + name: "SyncEnvVarsExtension", + async onBuildComplete(context, manifest) { + if (context.target === "dev") { + return; + } + + const result = await callSyncEnvVarsFn( + fn, + manifest.deploy.env ?? {}, + manifest.environment, + context.config.project + ); + + if (!result) { + return; + } + + const env = Object.entries(result).reduce( + (acc, [key, value]) => { + if (UNSYNCABLE_ENV_VARS.includes(key)) { + return acc; + } + + acc[key] = value; + return acc; + }, + {} as Record + ); + + context.addLayer({ + id: "sync-env-vars", + deploy: { + env, + override: options?.override ?? true, + }, + }); + }, + }; +} + +async function callSyncEnvVarsFn( + syncEnvVarsFn: SyncEnvVarsFunction | undefined, + env: Record, + environment: string, + projectRef: string +): Promise | undefined> { + if (syncEnvVarsFn && typeof syncEnvVarsFn === "function") { + let resolvedEnvVars: Record = {}; + + let result = syncEnvVarsFn({ + projectRef, + environment, + env, + }); + + if (!result) { + return; + } + + result = await result; + + if (Array.isArray(result)) { + for (const item of result) { + if ( + typeof item === "object" && + item !== null && + "name" in item && + "value" in item && + typeof item.name === "string" && + typeof item.value === "string" + ) { + resolvedEnvVars[item.name] = item.value; + } + } + } else if (result) { + resolvedEnvVars = result; + } + + return resolvedEnvVars; + } + + return; +} diff --git a/packages/core/src/v3/index.ts b/packages/core/src/v3/index.ts index 812e3eed2..1dbb4b47c 100644 --- a/packages/core/src/v3/index.ts +++ b/packages/core/src/v3/index.ts @@ -57,3 +57,7 @@ export { } from "./utils/ioSerialization.js"; export * from "./config.js"; + +import { VERSION } from "../version.js"; + +export { VERSION as CORE_VERSION }; diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 22b9fc86e..cd4f93647 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -35,6 +35,7 @@ export const GetProjectEnvResponse = z.object({ apiKey: z.string(), name: z.string(), apiUrl: z.string(), + projectId: z.string(), }); export type GetProjectEnvResponse = z.infer; @@ -153,6 +154,13 @@ export type StartDeploymentIndexingResponseBody = z.infer< typeof StartDeploymentIndexingResponseBody >; +export const FinalizeDeploymentRequestBody = z.object({ + imageReference: z.string(), + selfHosted: z.boolean().optional(), +}); + +export type FinalizeDeploymentRequestBody = z.infer; + export const ExternalBuildData = z.object({ buildId: z.string(), buildToken: z.string(), @@ -187,6 +195,18 @@ export const DeploymentErrorData = z.object({ stderr: z.string().optional(), }); +export const FailDeploymentRequestBody = z.object({ + error: DeploymentErrorData, +}); + +export type FailDeploymentRequestBody = z.infer; + +export const FailDeploymentResponseBody = z.object({ + id: z.string(), +}); + +export type FailDeploymentResponseBody = z.infer; + export const GetDeploymentResponseBody = z.object({ id: z.string(), status: z.enum([ @@ -201,8 +221,8 @@ export const GetDeploymentResponseBody = z.object({ contentHash: z.string(), shortCode: z.string(), version: z.string(), - imageReference: z.string().optional(), - errorData: DeploymentErrorData.optional().nullable(), + imageReference: z.string().nullish(), + errorData: DeploymentErrorData.nullish(), worker: z .object({ id: z.string(), diff --git a/packages/core/src/v3/schemas/build.ts b/packages/core/src/v3/schemas/build.ts index fd91fa37c..28a0c7592 100644 --- a/packages/core/src/v3/schemas/build.ts +++ b/packages/core/src/v3/schemas/build.ts @@ -19,10 +19,19 @@ export type BuildRuntime = z.infer; export const BuildManifest = z.object({ target: BuildTarget, + packageVersion: z.string(), + cliPackageVersion: z.string(), contentHash: z.string(), runtime: BuildRuntime, + environment: z.string(), config: ConfigManifest, files: z.array(TaskFile), + sources: z.record( + z.object({ + contents: z.string(), + contentHash: z.string(), + }) + ), outputPath: z.string(), indexerEntryPoint: z.string(), workerEntryPoint: z.string(), @@ -35,6 +44,7 @@ export const BuildManifest = z.object({ }), deploy: z.object({ env: z.record(z.string()).optional(), + needsSyncing: z.boolean().optional(), }), }); diff --git a/packages/core/src/v3/schemas/resources.ts b/packages/core/src/v3/schemas/resources.ts index 228aad325..fbdb9f27a 100644 --- a/packages/core/src/v3/schemas/resources.ts +++ b/packages/core/src/v3/schemas/resources.ts @@ -15,21 +15,21 @@ export const TaskResource = z.object({ export type TaskResource = z.infer; -export const BackgroundWorkerFileMetadata = z.object({ +export const BackgroundWorkerSourceFileMetadata = z.object({ filePath: z.string(), contents: z.string(), contentHash: z.string(), taskIds: z.array(z.string()), }); -export type BackgroundWorkerFileMetadata = z.infer; +export type BackgroundWorkerSourceFileMetadata = z.infer; export const BackgroundWorkerMetadata = z.object({ packageVersion: z.string(), contentHash: z.string(), cliPackageVersion: z.string().optional(), tasks: z.array(TaskResource), - fileContents: z.array(BackgroundWorkerFileMetadata).optional(), + sourceFiles: z.array(BackgroundWorkerSourceFileMetadata).optional(), }); export type BackgroundWorkerMetadata = z.infer; diff --git a/packages/core/src/v3/types/index.ts b/packages/core/src/v3/types/index.ts index 1d02c1553..68e64516b 100644 --- a/packages/core/src/v3/types/index.ts +++ b/packages/core/src/v3/types/index.ts @@ -78,7 +78,7 @@ export type ResolveEnvironmentVariablesResult = export type ResolveEnvironmentVariablesParams = { projectRef: string; - environment: "dev" | "staging" | "prod"; + environment: string; env: Record; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1774853ff..96df72595 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -852,15 +852,12 @@ importers: packages/cli-v3: dependencies: - '@anatine/esbuild-decorators': - specifier: ^0.2.19 - version: 0.2.19(esbuild@0.23.0) '@clack/prompts': specifier: ^0.7.0 version: 0.7.0 '@depot/cli': - specifier: 0.0.1-cli.2.71.0 - version: 0.0.1-cli.2.71.0 + specifier: 0.0.1-cli.2.73.0 + version: 0.0.1-cli.2.73.0 '@opentelemetry/api': specifier: 1.9.0 version: 1.9.0 @@ -900,21 +897,12 @@ importers: '@trigger.dev/core': specifier: workspace:3.0.0-beta.52 version: link:../core - '@types/degit': - specifier: ^2.8.3 - version: 2.8.3 - async-sema: - specifier: ^3.1.1 - version: 3.1.1 c12: specifier: ^1.11.1 version: 1.11.1(magicast@0.3.4) chalk: specifier: ^5.2.0 version: 5.3.0 - chokidar: - specifier: ^3.5.3 - version: 3.5.3 cli-table3: specifier: ^0.6.3 version: 0.6.3 @@ -924,9 +912,6 @@ importers: defu: specifier: ^6.1.4 version: 6.1.4 - degit: - specifier: ^2.8.4 - version: 2.8.4 dotenv: specifier: ^16.4.5 version: 16.4.5 @@ -936,24 +921,12 @@ importers: evt: specifier: ^2.4.13 version: 2.4.13 - execa: - specifier: ^9.1.0 - version: 9.1.0 - find-up: - specifier: ^7.0.0 - version: 7.0.0 - glob: - specifier: ^11.0.0 - version: 11.0.0 - glob-to-regexp: - specifier: ^0.4.1 - version: 0.4.1 + fast-npm-meta: + specifier: ^0.2.2 + version: 0.2.2 gradient-string: specifier: ^2.0.2 version: 2.0.2 - hono: - specifier: ^4.4.13 - version: 4.5.4 import-in-the-middle: specifier: 1.9.1 version: 1.9.1 @@ -966,9 +939,6 @@ importers: jsonc-parser: specifier: 3.2.1 version: 3.2.1 - liquidjs: - specifier: ^10.9.2 - version: 10.9.3 magicast: specifier: ^0.3.4 version: 0.3.4 @@ -978,33 +948,21 @@ importers: mlly: specifier: ^1.7.1 version: 1.7.1 - mock-fs: - specifier: ^5.2.0 - version: 5.2.0 - nanoid: - specifier: ^4.0.2 - version: 4.0.2 - node-fetch: - specifier: ^3.3.0 - version: 3.3.0 + nypm: + specifier: ^0.3.9 + version: 0.3.9 object-hash: specifier: ^3.0.0 version: 3.0.0 - p-debounce: - specifier: ^4.0.0 - version: 4.0.0 - p-throttle: - specifier: ^6.1.0 - version: 6.1.0 + open: + specifier: ^10.0.3 + version: 10.0.3 partysocket: specifier: ^0.0.17 version: 0.0.17 pkg-types: specifier: ^1.1.3 version: 1.1.3 - proxy-agent: - specifier: ^6.3.0 - version: 6.3.0 react: specifier: ^18.2.0 version: 18.2.0 @@ -1014,15 +972,9 @@ importers: resolve: specifier: ^1.22.8 version: 1.22.8 - semver: - specifier: ^7.5.0 - version: 7.5.4 signal-exit: specifier: ^4.1.0 version: 4.1.0 - simple-git: - specifier: ^3.19.0 - version: 3.19.0 source-map-support: specifier: 0.5.21 version: 0.5.21 @@ -1032,24 +984,15 @@ importers: tiny-invariant: specifier: ^1.2.0 version: 1.3.1 - tsconfig-paths: - specifier: ^4.2.0 - version: 4.2.0 - typescript: - specifier: ^5.4.0 - version: 5.4.5 - unplugin: - specifier: ^1.12.0 - version: 1.12.0 - update-check: - specifier: ^1.5.4 - version: 1.5.4 - url: - specifier: ^0.11.1 - version: 0.11.1 + tinyexec: + specifier: ^0.1.4 + version: 0.1.4 ws: specifier: ^8.12.0 version: 8.12.0 + xdg-app-paths: + specifier: ^8.3.0 + version: 8.3.0 zod: specifier: 3.23.8 version: 3.23.8 @@ -1060,14 +1003,11 @@ importers: '@types/gradient-string': specifier: ^1.1.2 version: 1.1.2 - '@types/mock-fs': - specifier: ^4.13.1 - version: 4.13.1 '@types/node': specifier: 20.14.14 version: 20.14.14 '@types/object-hash': - specifier: ^3.0.6 + specifier: 3.0.6 version: 3.0.6 '@types/react': specifier: ^18.2.48 @@ -1087,15 +1027,6 @@ importers: cpy-cli: specifier: ^5.0.0 version: 5.0.0 - nodemon: - specifier: ^3.0.1 - version: 3.0.1 - npm-run-all: - specifier: ^4.1.5 - version: 4.1.5 - open: - specifier: ^10.0.3 - version: 10.0.3 p-retry: specifier: ^6.1.0 version: 6.1.0 @@ -1104,22 +1035,19 @@ importers: version: 5.0.7 ts-essentials: specifier: 10.0.1 - version: 10.0.1(typescript@5.4.5) + version: 10.0.1(typescript@5.5.4) tshy: specifier: ^3.0.2 version: 3.0.2 tsx: specifier: 4.17.0 version: 4.17.0 - type-fest: - specifier: ^3.6.0 - version: 3.13.0 + typescript: + specifier: ^5.5.4 + version: 5.5.4 vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.14.14) - xdg-app-paths: - specifier: ^8.3.0 - version: 8.3.0 packages/core: dependencies: @@ -1558,14 +1486,6 @@ packages: '@jridgewell/trace-mapping': 0.3.19 dev: true - /@anatine/esbuild-decorators@0.2.19(esbuild@0.23.0): - resolution: {integrity: sha512-pyj6ULyMacyzpDqlnbS2OCkOqxcVgk8IqiTMRJ5CrsF8Yl1azvlX/AM6xWR8UzHKUYDlWOw5mOpos3+7KKR0Lw==} - peerDependencies: - esbuild: ~0.14.29 - dependencies: - esbuild: 0.23.0 - dev: false - /@ariakit/core@0.4.6: resolution: {integrity: sha512-L2WIZZlxDs611m3YLSv2xvJyQrkkVQJlxn8Y4DlI1G65VLTEH7hysw3RYUNdXsl0gP6S20id3zBMJCHT9BCRcg==} dev: false @@ -4102,8 +4022,8 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.9 - /@depot/cli-darwin-arm64@0.0.1-cli.2.71.0: - resolution: {integrity: sha512-mPfke/8bc+nm+ESun5JYW7MakrIjxAgyd6qZfFt3ZQV9FYILKhSzaWQK1C/JafGBB0Sn6RNMG+EgM2M0UIW2Ew==} + /@depot/cli-darwin-arm64@0.0.1-cli.2.73.0: + resolution: {integrity: sha512-nXG6loMJxbfWnKoVvTvrw19GAX3TxsgUy2mnb7sKv06VjUeMvsvBueXAW4lxO5Cf89oim9b0/Agnj/B3IVD2Kg==} engines: {node: '>=14'} cpu: [arm64] os: [darwin] @@ -4111,8 +4031,8 @@ packages: dev: false optional: true - /@depot/cli-darwin-x64@0.0.1-cli.2.71.0: - resolution: {integrity: sha512-P9FX91q7hKSGfkDyY1rKZfHLuAj0No4DuE83B8rliTGlkILsyR9odlvsODk3wo6lVZ6KkqTx23zUlXVem2vvhg==} + /@depot/cli-darwin-x64@0.0.1-cli.2.73.0: + resolution: {integrity: sha512-AqkNIIOuzh/8hmwTwu1FIY5Lvez7h4/STO3oPjaalLajV+ZfHgV2xpmbtTwxPmgLVFPaE2e77wzvpRWuFarWwQ==} engines: {node: '>=14'} cpu: [x64] os: [darwin] @@ -4120,8 +4040,8 @@ packages: dev: false optional: true - /@depot/cli-linux-arm64@0.0.1-cli.2.71.0: - resolution: {integrity: sha512-nW1k8AmaYXtX4Bbt3L5jLLPBmu63iZ/ARZfaaR2sfdubtpmyPbRAuxtzwqM42jnvzxjUOnKs1ex4det4qh8iig==} + /@depot/cli-linux-arm64@0.0.1-cli.2.73.0: + resolution: {integrity: sha512-Rfiil6MAO/SkAK27vaEA1IOPOJq8DgCvm09sfasrXFvpldDYa700Hx7c+V1uuBYfIckVh2S/i7AQjGg535MDjg==} engines: {node: '>=14'} cpu: [arm64] os: [linux] @@ -4129,8 +4049,8 @@ packages: dev: false optional: true - /@depot/cli-linux-arm@0.0.1-cli.2.71.0: - resolution: {integrity: sha512-hv0T7P2GcmcEQrdJmOkBbEKNFcjFr3xcMUrwfOPKFIC75hFgchc1vB5uFTqpBkJ4drjlk6xiEcujzE3EQznzhw==} + /@depot/cli-linux-arm@0.0.1-cli.2.73.0: + resolution: {integrity: sha512-/HspMwaVYYPErg372Bk4x7Gno7n8VuaLqaGm39wbZEb9C4S9YAP7r7T0wSywrUZBzJBxX7f+GoHbBic0IQoraQ==} engines: {node: '>=14'} cpu: [arm] os: [linux] @@ -4138,8 +4058,8 @@ packages: dev: false optional: true - /@depot/cli-linux-ia32@0.0.1-cli.2.71.0: - resolution: {integrity: sha512-D5f+zbUyiOkiT6bGf9VjcWSBWNHQ9GVCpLpeHjjPFxkeX+0Xyktuyae2pIzVlYqiASDXAbxJa3WhvgNk6ElErw==} + /@depot/cli-linux-ia32@0.0.1-cli.2.73.0: + resolution: {integrity: sha512-1GHDaNe6KXYjHWNoUrd2GutnseyYt3yrUKmRYKAR+/AU3BW/eQGcRAp50bSu9nntIaL+ByugkR+LjwKJIqRzmg==} engines: {node: '>=14'} cpu: [ia32] os: [linux] @@ -4147,8 +4067,8 @@ packages: dev: false optional: true - /@depot/cli-linux-x64@0.0.1-cli.2.71.0: - resolution: {integrity: sha512-vlo8R0o8+eiHkFN1Rcw55/9ASpONSNAfUclE2rxx2oJbRjVlacj9GJjPzOFAhIKyqgGpMrZJvBBIsHBv21STXw==} + /@depot/cli-linux-x64@0.0.1-cli.2.73.0: + resolution: {integrity: sha512-CY4IwXT5l2btXbjCYl+85tfF/viz6zdyaWjiu7MUuxxAM+s9bMYl7HKBITHdflx9Z/dRfDGFX8tM1i2AsKBcqA==} engines: {node: '>=14'} cpu: [x64] os: [linux] @@ -4156,8 +4076,8 @@ packages: dev: false optional: true - /@depot/cli-win32-arm64@0.0.1-cli.2.71.0: - resolution: {integrity: sha512-gXjitRRbSVkcDkkbPMalFqXn8I8I3GZAAgkwyWOkUHmZhwsEXpSeVi/B6sPdlAIFPLpspPtOYSKtrvnNU1Tflg==} + /@depot/cli-win32-arm64@0.0.1-cli.2.73.0: + resolution: {integrity: sha512-mslCCUzlK53aYstJH6//mPOVTWa8vU2IemBGLFsntgB1f/0fqDdfAskyfu+RVx/ZNqjr2Ih2jtdlfyOA3XKZdw==} engines: {node: '>=14'} cpu: [arm64] os: [win32] @@ -4165,8 +4085,8 @@ packages: dev: false optional: true - /@depot/cli-win32-ia32@0.0.1-cli.2.71.0: - resolution: {integrity: sha512-G2I97FT2IY4NtMdskLe5KLEPUJpMrZkU4VpAQwokLR9VFQn9TJ+J4DhXpsXpCtMQ5vZ01jTtiS3/WBqx50c5oA==} + /@depot/cli-win32-ia32@0.0.1-cli.2.73.0: + resolution: {integrity: sha512-/xsLWyIO7tAHTkFTFO0j9Csc78WnoENeEWROeUWsvAizFX6GF6Toir1xRZbM28yRRlGsOG+KWsFwULyEJkI1Wg==} engines: {node: '>=14'} cpu: [ia32] os: [win32] @@ -4174,8 +4094,8 @@ packages: dev: false optional: true - /@depot/cli-win32-x64@0.0.1-cli.2.71.0: - resolution: {integrity: sha512-L3h2zl24ItOlFYU7C6IyfrzNh9RhuxugASDczjry0aVbCv2EvjvUofpbwEsDY88CawXtn2cpwNbBesTWtDEZ2w==} + /@depot/cli-win32-x64@0.0.1-cli.2.73.0: + resolution: {integrity: sha512-iC8FpHij2yrO1hNQPuRYgreYsyvXgC6xaBdBpfM5x7C7Uf+vOg4k5ERToj0WCfvRhkja+uguQdw2V3il6IhNeA==} engines: {node: '>=14'} cpu: [x64] os: [win32] @@ -4183,21 +4103,21 @@ packages: dev: false optional: true - /@depot/cli@0.0.1-cli.2.71.0: - resolution: {integrity: sha512-InXTr/Ld30JwKgkRhdTWaB18iKi/5EnESnRhJkcW1XoxqWdZ0fbvMT6W0glEE9n68dZfuPEpI9aJBsfZg0K+yA==} + /@depot/cli@0.0.1-cli.2.73.0: + resolution: {integrity: sha512-pu5SKOj95jzCPG5H5SZvAN6Z+h4ACcjs6shv7S1lXqSGxNBVTvq7LlTiBFGpLcLo+EkSfbEjAdlNBdh1LRqrKA==} engines: {node: '>=14'} hasBin: true requiresBuild: true optionalDependencies: - '@depot/cli-darwin-arm64': 0.0.1-cli.2.71.0 - '@depot/cli-darwin-x64': 0.0.1-cli.2.71.0 - '@depot/cli-linux-arm': 0.0.1-cli.2.71.0 - '@depot/cli-linux-arm64': 0.0.1-cli.2.71.0 - '@depot/cli-linux-ia32': 0.0.1-cli.2.71.0 - '@depot/cli-linux-x64': 0.0.1-cli.2.71.0 - '@depot/cli-win32-arm64': 0.0.1-cli.2.71.0 - '@depot/cli-win32-ia32': 0.0.1-cli.2.71.0 - '@depot/cli-win32-x64': 0.0.1-cli.2.71.0 + '@depot/cli-darwin-arm64': 0.0.1-cli.2.73.0 + '@depot/cli-darwin-x64': 0.0.1-cli.2.73.0 + '@depot/cli-linux-arm': 0.0.1-cli.2.73.0 + '@depot/cli-linux-arm64': 0.0.1-cli.2.73.0 + '@depot/cli-linux-ia32': 0.0.1-cli.2.73.0 + '@depot/cli-linux-x64': 0.0.1-cli.2.73.0 + '@depot/cli-win32-arm64': 0.0.1-cli.2.73.0 + '@depot/cli-win32-ia32': 0.0.1-cli.2.73.0 + '@depot/cli-win32-x64': 0.0.1-cli.2.73.0 dev: false /@depot/sdk-node@0.5.0: @@ -6786,18 +6706,6 @@ packages: - utf-8-validate dev: false - /@kwsites/file-exists@1.1.1: - resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} - dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false - - /@kwsites/promise-deferred@1.1.1: - resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} - dev: false - /@lezer/common@1.0.2: resolution: {integrity: sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==} dev: false @@ -11632,10 +11540,6 @@ packages: resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==} dev: true - /@sec-ant/readable-stream@0.4.1: - resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - dev: false - /@selderee/plugin-htmlparser2@0.10.0: resolution: {integrity: sha512-gW69MEamZ4wk1OsOq1nG1jcyhXIQcnrsX5JwixVw/9xaiav8TCyjESAruu1Rz9yyInhgBXxkNwMeygKnN2uxNA==} dependencies: @@ -11673,11 +11577,6 @@ packages: engines: {node: '>=6'} dev: true - /@sindresorhus/merge-streams@4.0.0: - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} - engines: {node: '>=18'} - dev: false - /@sindresorhus/slugify@2.2.1: resolution: {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==} engines: {node: '>=12'} @@ -12682,10 +12581,6 @@ packages: '@types/ms': 0.7.31 dev: true - /@types/degit@2.8.3: - resolution: {integrity: sha512-CL7y71j2zaDmtPLD5Xq5S1Gv2dFoHl0/GBZm6s39Mj/ls28L3NzAOqf7H4H0/2TNVMgMjMVf9CAFYSjmXhi3bw==} - dev: false - /@types/eslint-scope@3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: @@ -12864,12 +12759,6 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: false - /@types/mock-fs@4.13.1: - resolution: {integrity: sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==} - dependencies: - '@types/node': 18.19.20 - dev: true - /@types/morgan@1.9.4: resolution: {integrity: sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==} dependencies: @@ -13649,10 +13538,6 @@ packages: requiresBuild: true optional: true - /abbrev@1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - dev: true - /abbrev@2.0.0: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -14139,10 +14024,6 @@ packages: hasBin: true dev: true - /async-sema@3.1.1: - resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} - dev: false - /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -14696,7 +14577,7 @@ packages: engines: {node: '>=18'} dependencies: run-applescript: 7.0.0 - dev: true + dev: false /bundle-require@4.0.1(esbuild@0.19.11): resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==} @@ -15817,7 +15698,7 @@ packages: dependencies: ms: 2.0.0 - /debug@3.2.7(supports-color@5.5.0): + /debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: supports-color: '*' @@ -15826,7 +15707,6 @@ packages: optional: true dependencies: ms: 2.1.3 - supports-color: 5.5.0 dev: true /debug@4.3.4: @@ -15916,6 +15796,7 @@ packages: /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + dev: true /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -15931,7 +15812,7 @@ packages: /default-browser-id@5.0.0: resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} engines: {node: '>=18'} - dev: true + dev: false /default-browser@5.2.1: resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} @@ -15939,7 +15820,7 @@ packages: dependencies: bundle-name: 4.1.0 default-browser-id: 5.0.0 - dev: true + dev: false /defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -15967,7 +15848,7 @@ packages: /define-lazy-prop@3.0.0: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} - dev: true + dev: false /define-properties@1.1.4: resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} @@ -16023,12 +15904,6 @@ packages: esprima: 4.0.1 dev: false - /degit@2.8.4: - resolution: {integrity: sha512-vqYuzmSA5I50J882jd+AbAhQtgK6bdKUJIex1JNfEUPENCgYsxugzKVZlFyMwV4i06MmnV47/Iqi5Io86zf3Ng==} - engines: {node: '>=8.0.0'} - hasBin: true - dev: false - /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -17055,7 +16930,7 @@ packages: /eslint-import-resolver-node@0.3.7: resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 is-core-module: 2.13.0 resolve: 1.22.8 transitivePeerDependencies: @@ -17065,7 +16940,7 @@ packages: /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 is-core-module: 2.14.0 resolve: 1.22.8 transitivePeerDependencies: @@ -17118,7 +16993,7 @@ packages: optional: true dependencies: '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.2.2) - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 eslint: 8.31.0 eslint-import-resolver-node: 0.3.7 eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.31.0) @@ -17148,7 +17023,7 @@ packages: optional: true dependencies: '@typescript-eslint/parser': 5.59.6(eslint@8.31.0)(typescript@5.2.2) - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 eslint: 8.31.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.29.1)(eslint@8.31.0) @@ -17182,7 +17057,7 @@ packages: array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 doctrine: 2.1.0 eslint: 8.31.0 eslint-import-resolver-node: 0.3.9 @@ -17704,24 +17579,6 @@ packages: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - /execa@9.1.0: - resolution: {integrity: sha512-lSgHc4Elo2m6bUDhc3Hl/VxvUDJdQWI40RZ4KMY9bKRc+hgMOT7II/JjbNDhI8VnMtrCb7U/fhpJIkLORZozWw==} - engines: {node: '>=18'} - dependencies: - '@sindresorhus/merge-streams': 4.0.0 - cross-spawn: 7.0.3 - figures: 6.1.0 - get-stream: 9.0.1 - human-signals: 7.0.0 - is-plain-obj: 4.1.0 - is-stream: 4.0.1 - npm-run-path: 5.3.0 - pretty-ms: 9.0.0 - signal-exit: 4.1.0 - strip-final-newline: 4.0.0 - yoctocolors: 2.0.0 - dev: false - /exit-hook@2.2.1: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} @@ -17902,6 +17759,10 @@ packages: resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==} dev: false + /fast-npm-meta@0.2.2: + resolution: {integrity: sha512-E+fdxeaOQGo/CMWc9f4uHFfgUPJRAu7N3uB8GBvB3SDPAIWJK4GKyYhkAGFq+GYrcbKNfQIz5VVQyJnDuPPCrg==} + dev: false + /fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} dependencies: @@ -17976,13 +17837,6 @@ packages: escape-string-regexp: 1.0.5 dev: false - /figures@6.1.0: - resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} - engines: {node: '>=18'} - dependencies: - is-unicode-supported: 2.0.0 - dev: false - /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -18049,15 +17903,6 @@ packages: path-exists: 5.0.0 dev: true - /find-up@7.0.0: - resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} - engines: {node: '>=18'} - dependencies: - locate-path: 7.2.0 - path-exists: 5.0.0 - unicorn-magic: 0.1.0 - dev: false - /find-yarn-workspace-root2@1.2.16: resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} dependencies: @@ -18401,14 +18246,6 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - /get-stream@9.0.1: - resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} - engines: {node: '>=18'} - dependencies: - '@sec-ant/readable-stream': 0.4.1 - is-stream: 4.0.1 - dev: false - /get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} engines: {node: '>= 0.4'} @@ -18534,6 +18371,7 @@ packages: minipass: 7.1.2 package-json-from-dist: 1.0.0 path-scurry: 2.0.0 + dev: true /glob@7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} @@ -18890,11 +18728,6 @@ packages: resolution: {integrity: sha512-Rgx+gy0tb2tH4hNzxYi/VK5pL/msaAtaQBIy8XsPHLujdSgo5OPWO6vOdjjB7ufM1l/CI2RLmlQ+L2QZOuHBjw==} dev: false - /hono@4.5.4: - resolution: {integrity: sha512-k2IguJfRgNCpDbAfpxk+o+fZBLFHl4+eIZUpjc1ItZWHeZ37SmT3efA1UpkIaC0hSf1NJg0E79/wWn6g9LQ4Cw==} - engines: {node: '>=16.0.0'} - dev: false - /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -19004,11 +18837,6 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - /human-signals@7.0.0: - resolution: {integrity: sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==} - engines: {node: '>=18.18.0'} - dev: false - /humanize-duration@3.27.3: resolution: {integrity: sha512-iimHkHPfIAQ8zCDQLgn08pRqSVioyWvnGfaQ8gond2wf7Jq2jJ+24ykmnRyiz3fIldcn4oUuQXpjqKLhSVR7lw==} dev: false @@ -19058,10 +18886,6 @@ packages: /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - /ignore-by-default@1.0.1: - resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} - dev: true - /ignore@4.0.6: resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} engines: {node: '>= 4'} @@ -19453,7 +19277,7 @@ packages: resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true - dev: true + dev: false /is-electron@2.2.0: resolution: {integrity: sha512-SpMppC2XR3YdxSzczXReBjqs2zGscWQpBIKqwXYBFic0ERaxNVgwLCHwOLZeESfdJQjX0RDvrJ1lBXX2ij+G1Q==} @@ -19522,7 +19346,7 @@ packages: hasBin: true dependencies: is-docker: 3.0.0 - dev: true + dev: false /is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} @@ -19590,6 +19414,7 @@ packages: /is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + dev: true /is-plain-object@2.0.4: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} @@ -19640,11 +19465,6 @@ packages: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - /is-stream@4.0.1: - resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} - engines: {node: '>=18'} - dev: false - /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -19689,11 +19509,6 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - /is-unicode-supported@2.0.0: - resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==} - engines: {node: '>=18'} - dev: false - /is-upper-case@2.0.2: resolution: {integrity: sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==} dependencies: @@ -19743,7 +19558,7 @@ packages: engines: {node: '>=16'} dependencies: is-inside-container: 1.0.0 - dev: true + dev: false /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -19845,6 +19660,7 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + dev: true /javascript-stringify@2.1.0: resolution: {integrity: sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==} @@ -20595,14 +20411,6 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - /liquidjs@10.9.3: - resolution: {integrity: sha512-vG8fLPSBtf0C6FJg0DwzxwWF0sPE+wIRrrGsNoe0DP3Pg4HOPqycGDT14V4UfyskUXsum7DkhUuZQ9tBRjbX+g==} - engines: {node: '>=14'} - hasBin: true - dependencies: - commander: 10.0.1 - dev: false - /listenercount@1.0.1: resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} dev: false @@ -20675,6 +20483,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: p-locate: 6.0.0 + dev: true /lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -20773,6 +20582,7 @@ packages: /lru-cache@11.0.0: resolution: {integrity: sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==} engines: {node: 20 || >=22} + dev: true /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -21627,11 +21437,6 @@ packages: pkg-types: 1.1.3 ufo: 1.5.4 - /mock-fs@5.2.0: - resolution: {integrity: sha512-2dF2R6YMSZbpip1V1WHKGLNjr/k48uQClqMVb5H3MOvwc9qhYis3/IWbj02qIg/Y8MDXKFF4c5v0rxx2o6xTZw==} - engines: {node: '>=12.0.0'} - dev: false - /module-details-from-path@1.0.3: resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} @@ -21747,12 +21552,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - /nanoid@4.0.2: - resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} - engines: {node: ^14 || ^16 || >=18} - hasBin: true - dev: false - /nanoid@5.0.6: resolution: {integrity: sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==} engines: {node: ^18 || >=20} @@ -21881,15 +21680,6 @@ packages: whatwg-url: 5.0.0 dev: false - /node-fetch@3.3.0: - resolution: {integrity: sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - dev: false - /node-fetch@3.3.2: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -21917,34 +21707,10 @@ packages: /node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - /nodemon@3.0.1: - resolution: {integrity: sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==} - engines: {node: '>=10'} - hasBin: true - dependencies: - chokidar: 3.5.3 - debug: 3.2.7(supports-color@5.5.0) - ignore-by-default: 1.0.1 - minimatch: 3.1.2 - pstree.remy: 1.1.8 - semver: 7.5.4 - simple-update-notifier: 2.0.0 - supports-color: 5.5.0 - touch: 3.1.0 - undefsafe: 2.0.5 - dev: true - /non.geist@1.0.2: resolution: {integrity: sha512-oGCfo7Ub5bJmCOAt4CMAsCrVal/JXkQtxgBcpXzdU3lQib9c5hHdr62I8vI6TtdlsLcaYAPD/HRfZNdBUobwFg==} dev: false - /nopt@1.0.10: - resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} - hasBin: true - dependencies: - abbrev: 1.1.1 - dev: true - /nopt@7.2.0: resolution: {integrity: sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -22052,13 +21818,6 @@ packages: dependencies: path-key: 4.0.0 - /npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - path-key: 4.0.0 - dev: false - /num2fraction@1.2.2: resolution: {integrity: sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==} dev: false @@ -22273,7 +22032,7 @@ packages: define-lazy-prop: 3.0.0 is-inside-container: 1.0.0 is-wsl: 3.1.0 - dev: true + dev: false /open@8.4.0: resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} @@ -22369,7 +22128,7 @@ packages: engines: {node: '>= 4.0'} optionalDependencies: fsevents: 2.3.3 - dev: true + dev: false /os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} @@ -22400,11 +22159,6 @@ packages: engines: {node: '>=6'} dev: true - /p-debounce@4.0.0: - resolution: {integrity: sha512-4Ispi9I9qYGO4lueiLDhe4q4iK5ERK8reLsuzH6BPaXn53EGaua8H66PXIFGrW897hwjXp+pVLrm/DLxN0RF0A==} - engines: {node: '>=12'} - dev: false - /p-event@4.2.0: resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} engines: {node: '>=8'} @@ -22455,6 +22209,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: yocto-queue: 1.0.0 + dev: true /p-limit@5.0.0: resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} @@ -22480,6 +22235,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: p-limit: 4.0.0 + dev: true /p-map@2.1.0: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} @@ -22545,11 +22301,6 @@ packages: retry: 0.13.1 dev: true - /p-throttle@6.1.0: - resolution: {integrity: sha512-eQMdGTxk2+047La67wefUtt0tEHh7D+C8Jl7QXoFCuIiNYeQ9zWs2AZiJdIAs72rSXZ06t11me2bgalRNdy3SQ==} - engines: {node: '>=18'} - dev: false - /p-timeout@3.2.0: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} @@ -22598,6 +22349,7 @@ packages: /package-json-from-dist@1.0.0: resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + dev: true /package-json@6.5.0: resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} @@ -22664,11 +22416,6 @@ packages: engines: {node: '>=6'} dev: true - /parse-ms@4.0.0: - resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} - engines: {node: '>=18'} - dev: false - /parse5-htmlparser2-tree-adapter@6.0.1: resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} dependencies: @@ -22726,6 +22473,7 @@ packages: /path-exists@5.0.0: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} @@ -22760,6 +22508,7 @@ packages: dependencies: lru-cache: 11.0.0 minipass: 7.1.2 + dev: true /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -23569,13 +23318,6 @@ packages: parse-ms: 2.1.0 dev: true - /pretty-ms@9.0.0: - resolution: {integrity: sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==} - engines: {node: '>=18'} - dependencies: - parse-ms: 4.0.0 - dev: false - /pretty@2.0.0: resolution: {integrity: sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==} engines: {node: '>=0.10.0'} @@ -23748,10 +23490,6 @@ packages: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} dev: false - /pstree.remy@1.1.8: - resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} - dev: true - /pump@2.0.1: resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} dependencies: @@ -23854,6 +23592,7 @@ packages: ini: 1.3.8 minimist: 1.2.7 strip-json-comments: 2.0.1 + dev: true /react-aria@3.31.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-q4jRCVDKO6V2o4Sgir5S2obssw/YnMx6QOy10+p0dYqROHpSnMFNkONrKT1w/nA+Nx4ptfPqZbaNra1hR1bUWg==} @@ -24504,13 +24243,6 @@ packages: unicode-match-property-value-ecmascript: 2.1.0 dev: true - /registry-auth-token@3.3.2: - resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==} - dependencies: - rc: 1.2.8 - safe-buffer: 5.2.1 - dev: false - /registry-auth-token@4.2.2: resolution: {integrity: sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==} engines: {node: '>=6.0.0'} @@ -24518,13 +24250,6 @@ packages: rc: 1.2.8 dev: true - /registry-url@3.1.0: - resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} - engines: {node: '>=0.10.0'} - dependencies: - rc: 1.2.8 - dev: false - /registry-url@5.1.0: resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} engines: {node: '>=8'} @@ -24985,7 +24710,7 @@ packages: /run-applescript@7.0.0: resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} engines: {node: '>=18'} - dev: true + dev: false /run-async@3.0.0: resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} @@ -25304,16 +25029,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - /simple-git@3.19.0: - resolution: {integrity: sha512-hyH2p9Ptxjf/xPuL7HfXbpYt9gKhC1yWDh3KYIAYJJePAKV7AEjLN4xhp7lozOdNiaJ9jlVvAbBymVlcS2jRiA==} - dependencies: - '@kwsites/file-exists': 1.1.1 - '@kwsites/promise-deferred': 1.1.1 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false - /simple-oauth2@5.0.0: resolution: {integrity: sha512-8291lo/z5ZdpmiOFzOs1kF3cxn22bMj5FFH+DNUppLJrpoIlM1QnFiE7KpshHu3J3i21TVcx4yW+gXYjdCKDLQ==} dependencies: @@ -25331,13 +25046,6 @@ packages: is-arrayish: 0.3.2 dev: false - /simple-update-notifier@2.0.0: - resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} - engines: {node: '>=10'} - dependencies: - semver: 7.5.4 - dev: true - /simplur@3.0.1: resolution: {integrity: sha512-bBAoTn75tuKh83opmZ1VoyVoQIsvLCKzSxuasAxbnKofrT8eGyOEIaXSuNfhi/hI160+fwsR7ObcbBpOyzDvXg==} dev: false @@ -25930,11 +25638,6 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} - /strip-final-newline@4.0.0: - resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} - engines: {node: '>=18'} - dev: false - /strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -25945,6 +25648,7 @@ packages: /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + dev: true /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} @@ -26404,6 +26108,10 @@ packages: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} dev: false + /tinyexec@0.1.4: + resolution: {integrity: sha512-Ba2ELcNnnWkgqnAJBouhcsDsYitbD9LIAVNSz3746u50f+tlF3wO0uB3uqyz8NHFSTpv23qtT47XGDw8pXW5DA==} + dev: false + /tinyglobby@0.2.2: resolution: {integrity: sha512-mZ2sDMaySvi1PkTp4lTo1In2zjU+cY8OvZsfwrDrx3YGRbXPX1/cbPwCR9zkm3O/Fz9Jo0F1HNgIQ1b8BepqyQ==} engines: {node: '>=12.0.0'} @@ -26502,13 +26210,6 @@ packages: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} dev: true - /touch@3.1.0: - resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} - hasBin: true - dependencies: - nopt: 1.0.10 - dev: true - /tough-cookie@2.5.0: resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} engines: {node: '>=0.8'} @@ -26559,17 +26260,6 @@ packages: resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} dev: false - /ts-essentials@10.0.1(typescript@5.4.5): - resolution: {integrity: sha512-HPH+H2bkkO8FkMDau+hFvv7KYozzned9Zr1Urn7rRPXMF4mZmCKOq+u4AI1AAW+2bofIOXTuSdKo9drQuni2dQ==} - peerDependencies: - typescript: '>=4.5.0' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - typescript: 5.4.5 - dev: true - /ts-essentials@10.0.1(typescript@5.5.4): resolution: {integrity: sha512-HPH+H2bkkO8FkMDau+hFvv7KYozzned9Zr1Urn7rRPXMF4mZmCKOq+u4AI1AAW+2bofIOXTuSdKo9drQuni2dQ==} peerDependencies: @@ -26745,6 +26435,7 @@ packages: json5: 2.2.3 minimist: 1.2.7 strip-bom: 3.0.0 + dev: true /tshy@3.0.2: resolution: {integrity: sha512-8GkWnAfmNXxl8iDTZ1o2H4jdaj9H7HeDKkr5qd0ZhQBCNA41D3xqTyg2Ycs51VCfmjJ5e+0v9AUmD6ylAI9Bgw==} @@ -27039,6 +26730,7 @@ packages: /type-fest@3.13.0: resolution: {integrity: sha512-Gur3yQGM9qiLNs0KPP7LPgeRbio2QTt4xXouobMCarR0/wyW3F+F/+OWwshg3NG0Adon7uQfSZBpB46NfhoF1A==} engines: {node: '>=14.16'} + dev: false /type-fest@4.10.3: resolution: {integrity: sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==} @@ -27226,11 +26918,6 @@ packages: hasBin: true dev: true - /typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} - engines: {node: '>=14.17'} - hasBin: true - /typescript@5.5.4: resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} @@ -27264,10 +26951,6 @@ packages: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 - /undefsafe@2.0.5: - resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} - dev: true - /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -27311,11 +26994,6 @@ packages: engines: {node: '>=4'} dev: true - /unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} - dev: false - /unified@10.1.2: resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} dependencies: @@ -27413,16 +27091,6 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - /unplugin@1.12.0: - resolution: {integrity: sha512-KeczzHl2sATPQUx1gzo+EnUkmN4VmGBYRRVOZSGvGITE9rGHRDGqft6ONceP3vgXcyJ2XjX5axG5jMWUwNCYLw==} - engines: {node: '>=14.0.0'} - dependencies: - acorn: 8.12.1 - chokidar: 3.6.0 - webpack-sources: 3.2.3 - webpack-virtual-modules: 0.6.2 - dev: false - /unset-value@1.0.0: resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} engines: {node: '>=0.10.0'} @@ -27477,13 +27145,6 @@ packages: escalade: 3.1.1 picocolors: 1.0.0 - /update-check@1.5.4: - resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==} - dependencies: - registry-auth-token: 3.3.2 - registry-url: 3.1.0 - dev: false - /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -27501,13 +27162,6 @@ packages: prepend-http: 2.0.0 dev: true - /url@0.11.1: - resolution: {integrity: sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==} - dependencies: - punycode: 1.4.1 - qs: 6.11.0 - dev: false - /urlpattern-polyfill@9.0.0: resolution: {integrity: sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==} dev: false @@ -28269,10 +27923,6 @@ packages: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} - /webpack-virtual-modules@0.6.2: - resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - dev: false - /webpack@5.88.2(@swc/core@1.3.101)(esbuild@0.19.11): resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==} engines: {node: '>=10.13.0'} @@ -28661,7 +28311,7 @@ packages: xdg-portable: 10.6.0 optionalDependencies: fsevents: 2.3.3 - dev: true + dev: false /xdg-portable@10.6.0: resolution: {integrity: sha512-xrcqhWDvtZ7WLmt8G4f3hHy37iK7D2idtosRgkeiSPZEPmBShp0VfmRBLWAPC6zLF48APJ21yfea+RfQMF4/Aw==} @@ -28670,7 +28320,7 @@ packages: os-paths: 7.4.0 optionalDependencies: fsevents: 2.3.3 - dev: true + dev: false /xmlhttprequest-ssl@2.0.0: resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} @@ -28781,11 +28431,7 @@ packages: /yocto-queue@1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} - - /yoctocolors@2.0.0: - resolution: {integrity: sha512-esbDnt0Z1zI1KgvOZU90hJbL6BkoUbrP9yy7ArNZ6TmxBxydMJTYMf9FZjmwwcA8ZgEQzriQ3hwZ0NYXhlFo8Q==} - engines: {node: '>=18'} - dev: false + dev: true /yoga-wasm-web@0.3.3: resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} diff --git a/references/v3-catalog/package.json b/references/v3-catalog/package.json index 8705059c9..57c7005be 100644 --- a/references/v3-catalog/package.json +++ b/references/v3-catalog/package.json @@ -2,6 +2,7 @@ "name": "@references/v3-catalog", "version": "0.1.0", "private": true, + "type": "module", "zenstack": { "schema": "./prisma/schema.zmodel" }, diff --git a/references/v3-catalog/src/trigger/javascript.js b/references/v3-catalog/src/trigger/javascript.cjs similarity index 100% rename from references/v3-catalog/src/trigger/javascript.js rename to references/v3-catalog/src/trigger/javascript.cjs diff --git a/references/v3-catalog/trigger.config.ts b/references/v3-catalog/trigger.config.ts index c34b4b1fc..a95234aad 100644 --- a/references/v3-catalog/trigger.config.ts +++ b/references/v3-catalog/trigger.config.ts @@ -1,7 +1,13 @@ import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai"; -import { defineConfig } from "@trigger.dev/sdk/v3"; +import { defineConfig, ResolveEnvironmentVariablesFunction } from "@trigger.dev/sdk/v3"; import { emitDecoratorMetadata } from "@trigger.dev/sdk/v3/extensions"; +export const resolveEnvVars: ResolveEnvironmentVariablesFunction = async ({ + projectRef, + env, + environment, +}) => {}; + export default defineConfig({ project: "yubjwjsfkxnylobaqvqz", machine: "small-2x",