diff --git a/.gitignore b/.gitignore index 3bc1bd9b8..a60308472 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ coverage # next.js .next/ out/ -build dist packages/**/dist diff --git a/apps/proxy/package.json b/apps/proxy/package.json index 15ed6ae0e..b05133ca5 100644 --- a/apps/proxy/package.json +++ b/apps/proxy/package.json @@ -15,7 +15,7 @@ "@aws-sdk/client-sqs": "^3.445.0", "@trigger.dev/core": "workspace:*", "ulidx": "^2.2.1", - "zod": "3.22.3", + "zod": "3.23.8", "zod-error": "1.5.0" } -} +} \ No newline at end of file diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 64efcde7d..5e4a33945 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -175,7 +175,7 @@ "ulid": "^2.3.0", "ulidx": "^2.2.1", "ws": "^8.11.0", - "zod": "3.22.3", + "zod": "3.23.8", "zod-error": "1.5.0", "zod-validation-error": "^1.5.0" }, diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index a8d664022..1b25dafd9 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -29,7 +29,7 @@ "dist" ], "bin": { - "triggerdev": "./dist/index.js" + "triggerdev": "./dist/esm/index.js" }, "tshy": { "selfLink": false, @@ -62,10 +62,12 @@ "typescript": "^5.5.4", "vitest": "^1.6.0", "xdg-app-paths": "^8.3.0", - "tshy": "^3.0.2" + "tshy": "^3.0.2", + "ts-essentials": "10.0.1" }, "scripts": { "typecheck": "tsc", + "prepare": "tshy", "build": "tshy", "dev": "tshy --watch", "test": "vitest", @@ -94,14 +96,9 @@ "cli-table3": "^0.6.3", "commander": "^9.4.1", "degit": "^2.8.4", - "dotenv": "^16.4.4", - "esbuild": "^0.19.11", "evt": "^2.4.13", "execa": "^9.1.0", - "find-up": "^7.0.0", - "glob": "^10.3.10", "gradient-string": "^2.0.2", - "import-meta-resolve": "^4.0.0", "ink": "^4.4.1", "jsonc-parser": "3.2.1", "liquidjs": "^10.9.2", @@ -117,7 +114,6 @@ "react-error-boundary": "^4.0.12", "semver": "^7.5.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", @@ -125,10 +121,38 @@ "update-check": "^1.5.4", "url": "^0.11.1", "ws": "^8.12.0", - "zod": "3.22.3", - "zod-validation-error": "^1.5.0" + "zod-validation-error": "^1.5.0", + "async-sema": "^3.1.1", + "c12": "^1.11.1", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "esbuild": "^0.23.0", + "find-up": "^7.0.0", + "glob": "^11.0.0", + "glob-to-regexp": "^0.4.1", + "hono": "^4.4.13", + "import-in-the-middle": "1.9.1", + "import-meta-resolve": "^4.1.0", + "magicast": "^0.3.4", + "mlly": "^1.7.1", + "package-json-from-dist": "^1.0.0", + "pkg-types": "^1.1.3", + "resolve": "^1.22.8", + "signal-exit": "^4.1.0", + "source-map-support": "0.5.21", + "unplugin": "^1.12.0", + "zod": "3.23.8" }, "engines": { "node": ">=18.20.0" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + } + } } -} \ No newline at end of file +} diff --git a/packages/cli-v3/src/Containerfile.prod b/packages/cli-v3/src/Containerfile.prod deleted file mode 100644 index 3d5061952..000000000 --- a/packages/cli-v3/src/Containerfile.prod +++ /dev/null @@ -1,58 +0,0 @@ -# syntax=docker/dockerfile:labs - -FROM node:21-bookworm-slim@sha256:fb82287cf66ca32d854c05f54251fca8b572149163f154248df7e800003c90b5 AS base - -ARG AUDIOWAVEFORM_VERSION=1.10.1 -ARG AUDIOWAVEFORM_CHECKSUM=sha256:00b41ea4d6e7a5b4affcfe4ac99951ec89da81a8cba40af19e9b98c3a8f9b4b8 -ADD --checksum=${AUDIOWAVEFORM_CHECKSUM} \ - # on debian major version upgrades, this url will need to be updated - https://github.com/bbc/audiowaveform/releases/download/${AUDIOWAVEFORM_VERSION}/audiowaveform_${AUDIOWAVEFORM_VERSION}-1-12_amd64.deb . -# errors due to missing deps are expected here, these will get fixed in the apt install step -RUN dpkg -i audiowaveform_${AUDIOWAVEFORM_VERSION}-1-12_amd64.deb || true - -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && \ - # required for audiowaveform - apt-get --fix-broken install -y && \ - apt-get install -y --no-install-recommends \ - busybox \ - ca-certificates \ - dumb-init \ - git \ - openssl \ - sox \ - && \ - rm -rf /var/lib/apt/lists/* audiowaveform*.deb - -# Create and set workdir with appropriate permissions -RUN mkdir /app && chown node:node /app -WORKDIR /app - -# copy all the files just in case anything is needed in postinstall -COPY --chown=node:node . . - -USER node -RUN npm ci --no-fund --no-audit && npm cache clean --force - -# Development or production stage builds upon the base stage -FROM base AS final - -# Use ARG for build-time variables -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 - -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 - -USER node - -CMD [ "dumb-init", "node", "index.js" ] \ No newline at end of file diff --git a/packages/cli-v3/src/cli/index.ts b/packages/cli-v3/src/cli/index.ts index 52f80346e..1ad63073d 100644 --- a/packages/cli-v3/src/cli/index.ts +++ b/packages/cli-v3/src/cli/index.ts @@ -1,12 +1,10 @@ import { Command } from "commander"; -import { configureDeployCommand } from "../commands/deploy.js"; import { configureDevCommand } from "../commands/dev.js"; import { configureInitCommand } from "../commands/init.js"; import { configureLoginCommand } from "../commands/login.js"; import { configureLogoutCommand } from "../commands/logout.js"; import { configureWhoamiCommand } from "../commands/whoami.js"; -import { COMMAND_NAME } from "../consts.js"; -import { getVersion } from "../utilities/getVersion.js"; +import { COMMAND_NAME, VERSION } from "../consts.js"; import { configureListProfilesCommand } from "../commands/list-profiles.js"; import { configureUpdateCommand } from "../commands/update.js"; @@ -15,12 +13,11 @@ export const program = new Command(); program .name(COMMAND_NAME) .description("Create, run locally and deploy Trigger.dev background tasks.") - .version(getVersion(), "-v, --version", "Display the version number"); + .version(VERSION, "-v, --version", "Display the version number"); 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 deleted file mode 100644 index e99732afb..000000000 --- a/packages/cli-v3/src/commands/deploy.ts +++ /dev/null @@ -1,1962 +0,0 @@ -import { intro, log, outro } from "@clack/prompts"; -import { depot } from "@depot/cli"; -import { context, trace } from "@opentelemetry/api"; -import { - ResolvedConfig, - TaskMetadataFailedToParseData, - detectDependencyVersion, - flattenAttributes, -} from "@trigger.dev/core/v3"; -import { recordSpanException } from "@trigger.dev/core/v3/workers"; -import { Command, Option as CommandOption } from "commander"; -import { Metafile, build } from "esbuild"; -import { execa } from "execa"; -import { createHash } from "node:crypto"; -import { readFileSync } from "node:fs"; -import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises"; -import { dirname, join, posix, relative, resolve } from "node:path"; -import { setTimeout } from "node:timers/promises"; -import invariant from "tiny-invariant"; -import { z } from "zod"; -import * as packageJson from "../../package.json"; -import { CliApiClient } from "../apiClient.js"; -import { - CommonCommandOptions, - SkipCommandError, - SkipLoggingError, - commonOptions, - handleTelemetry, - tracer, - wrapCommandAction, -} from "../cli/common.js"; -import { ReadConfigResult, readConfig } from "../utilities/configFiles.js"; -import { createTempDir, writeJSONFile } from "../utilities/fileSystem.js"; -import { printStandloneInitialBanner } from "../utilities/initialBanner.js"; -import { - detectPackageNameFromImportPath, - parsePackageName, - stripWorkspaceFromVersion, -} from "../utilities/installPackages"; -import { logger } from "../utilities/logger.js"; -import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles"; -import { login } from "./login"; - -import { esbuildDecorators } from "@anatine/esbuild-decorators"; -import { Glob, GlobOptions } from "glob"; -import type { SetOptional } from "type-fest"; -import { - bundleDependenciesPlugin, - mockServerOnlyPlugin, - workerSetupImportConfigPlugin, -} from "../utilities/build"; -import { chalkError, chalkPurple, chalkWarning, cliLink } from "../utilities/cliOutput"; -import { - logESMRequireError, - logTaskMetadataParseError, - parseBuildErrorStack, - parseNpmInstallError, -} from "../utilities/deployErrors"; -import { JavascriptProject } from "../utilities/javascriptProject"; -import { docs, getInTouch } from "../utilities/links"; -import { cliRootPath } from "../utilities/resolveInternalFilePath"; -import { safeJsonParse } from "../utilities/safeJsonParse"; -import { escapeImportPath, spinner } from "../utilities/windows"; -import { updateTriggerPackages } from "./update"; -import { callResolveEnvVars } from "../utilities/resolveEnvVars"; - -const DeployCommandOptions = CommonCommandOptions.extend({ - skipTypecheck: z.boolean().default(false), - skipDeploy: 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(), - outputMetafile: 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; - -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-typecheck", "Whether to skip the pre-build typecheck") - .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." - ) - ) - .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( - "--ignore-env-var-check", - "(deprecated) Detected missing environment variables won't block deployment" - ).hideHelp() - ) - .addOption(new CommandOption("-D, --skip-deploy", "Skip deploying the image").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( - "--output-metafile ", - "If provided, will save the esbuild metafile for the build to the specified path" - ).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) { - const span = trace.getSpan(context.active()); - - intro("Deploying project"); - - if (!options.skipUpdateCheck) { - await updateTriggerPackages(dir, { ...options }, true, true); - } - - 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}` - ); - } - } - - span?.setAttributes({ - "cli.userId": authorization.userId, - "cli.email": authorization.email, - "cli.config.apiUrl": authorization.auth.apiUrl, - }); - - const resolvedConfig = await readConfig(dir, { - configFile: options.config, - projectRef: options.projectRef, - }); - - if (resolvedConfig.status === "error") { - logger.error("Failed to read config:", resolvedConfig.error); - span && recordSpanException(span, resolvedConfig.error); - - throw new SkipLoggingError("Failed to read config"); - } - - logger.debug("Resolved config", { resolvedConfig }); - - span?.setAttributes({ - "resolvedConfig.status": resolvedConfig.status, - "resolvedConfig.path": resolvedConfig.status === "file" ? resolvedConfig.path : undefined, - "resolvedConfig.config.project": resolvedConfig.config.project, - "resolvedConfig.config.projectDir": resolvedConfig.config.projectDir, - "resolvedConfig.config.triggerUrl": resolvedConfig.config.triggerUrl, - "resolvedConfig.config.triggerDirectories": resolvedConfig.config.triggerDirectories, - ...flattenAttributes(resolvedConfig.config.retries, "resolvedConfig.config.retries"), - }); - - const apiClient = new CliApiClient(authorization.auth.apiUrl, authorization.auth.accessToken); - - const deploymentEnv = await apiClient.getProjectEnv({ - projectRef: resolvedConfig.config.project, - env: options.env, - }); - - if (!deploymentEnv.success) { - throw new Error(deploymentEnv.error); - } - - const environmentClient = new CliApiClient(authorization.auth.apiUrl, deploymentEnv.data.apiKey); - - log.step( - `Preparing to deploy "${deploymentEnv.data.name}" (${resolvedConfig.config.project}) to ${options.env}` - ); - - // Step 1: Build the project into a temporary directory - const compilation = await compileProject( - resolvedConfig.config, - options, - resolvedConfig.status === "file" ? resolvedConfig.path : undefined - ); - - logger.debug("Compilation result", { compilation }); - - // Optional Step 1.1: resolve environment variables - await resolveEnvironmentVariables(resolvedConfig, environmentClient, options); - - // Step 2: Initialize a deployment on the server (response will have everything we need to build an image) - const deploymentResponse = await environmentClient.initializeDeployment({ - contentHash: compilation.contentHash, - userId: authorization.userId, - }); - - if (!deploymentResponse.success) { - throw new Error(`Failed to start deployment: ${deploymentResponse.error}`); - } - - // 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 (!deploymentResponse.data.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.` - ); - } - - const version = deploymentResponse.data.version; - - const deploymentSpinner = spinner(); - - deploymentSpinner.start(`Deploying version ${version}`); - const selfHostedRegistryHost = deploymentResponse.data.registryHost ?? options.registry; - const registryHost = selfHostedRegistryHost ?? "registry.trigger.dev"; - - const buildImage = async () => { - if (options.selfHosted) { - return buildAndPushSelfHostedImage({ - registryHost: selfHostedRegistryHost, - imageTag: deploymentResponse.data.imageTag, - cwd: compilation.path, - projectId: resolvedConfig.config.project, - deploymentId: deploymentResponse.data.id, - deploymentVersion: version, - contentHash: deploymentResponse.data.contentHash, - projectRef: resolvedConfig.config.project, - buildPlatform: options.buildPlatform, - pushImage: options.push, - selfHostedRegistry: !!options.registry, - noCache: options.noCache, - extraCACerts: resolvedConfig.config.extraCACerts, - }); - } - - if (!deploymentResponse.data.externalBuildData) { - 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 buildAndPushImage( - { - registryHost, - auth: authorization.auth.accessToken, - imageTag: deploymentResponse.data.imageTag, - buildId: deploymentResponse.data.externalBuildData.buildId, - buildToken: deploymentResponse.data.externalBuildData.buildToken, - buildProjectId: deploymentResponse.data.externalBuildData.projectId, - cwd: compilation.path, - projectId: resolvedConfig.config.project, - deploymentId: deploymentResponse.data.id, - deploymentVersion: deploymentResponse.data.version, - contentHash: deploymentResponse.data.contentHash, - projectRef: resolvedConfig.config.project, - loadImage: options.loadImage, - buildPlatform: options.buildPlatform, - noCache: options.noCache, - extraCACerts: resolvedConfig.config.extraCACerts, - }, - deploymentSpinner - ); - }; - - const image = await buildImage(); - - const warnings = checkLogsForWarnings(image.logs); - - if (!warnings.ok) { - await failDeploy( - deploymentResponse.data.shortCode, - warnings.summary, - image.logs, - deploymentSpinner, - warnings.warnings, - warnings.errors - ); - - throw new SkipLoggingError(`Failed to build project image: ${warnings.summary}`); - } - - if (!image.ok) { - await failDeploy( - deploymentResponse.data.shortCode, - image.error, - image.logs, - deploymentSpinner, - warnings.warnings - ); - - throw new SkipLoggingError(`Failed to build project image: ${image.error}`); - } - - const preExitTasks = async () => { - printWarnings(warnings.warnings); - - if (options.saveLogs) { - const logPath = await saveLogs(deploymentResponse.data.shortCode, image.logs); - log.info(`Build logs have been saved to ${logPath}`); - } - }; - - const imageReference = options.selfHosted - ? `${selfHostedRegistryHost ? `${selfHostedRegistryHost}/` : ""}${image.image}${ - image.digest ? `@${image.digest}` : "" - }` - : `${registryHost}/${image.image}${image.digest ? `@${image.digest}` : ""}`; - - span?.setAttributes({ - "image.reference": imageReference, - }); - - if (options.skipDeploy) { - deploymentSpinner.stop( - `Project image built: ${imageReference}. Skipping deployment as requested` - ); - - await preExitTasks(); - - throw new SkipCommandError("Skipping deployment as requested"); - } - - deploymentSpinner.message( - `${deploymentResponse.data.version} image built, detecting deployed tasks` - ); - - logger.debug(`Start indexing image ${imageReference}`); - - const startIndexingResponse = await environmentClient.startDeploymentIndexing( - deploymentResponse.data.id, - { - imageReference, - selfHosted: options.selfHosted, - } - ); - - if (!startIndexingResponse.success) { - deploymentSpinner.stop(`Failed to start indexing: ${startIndexingResponse.error}`); - - await preExitTasks(); - - throw new SkipLoggingError(`Failed to start indexing: ${startIndexingResponse.error}`); - } - - const finishedDeployment = await waitForDeploymentToFinish( - deploymentResponse.data.id, - environmentClient - ); - - if (!finishedDeployment) { - deploymentSpinner.stop(`Deployment failed to complete`); - - await preExitTasks(); - - throw new SkipLoggingError("Deployment failed to complete: unknown issue"); - } - - if (typeof finishedDeployment === "string") { - deploymentSpinner.stop(`Deployment failed to complete: ${finishedDeployment}`); - - await preExitTasks(); - - throw new SkipLoggingError(`Deployment failed to complete: ${finishedDeployment}`); - } - - const deploymentLink = cliLink( - "View deployment", - `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.config.project}/deployments/${finishedDeployment.shortCode}` - ); - - const testLink = cliLink( - "Test tasks", - `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.config.project}/test?environment=${ - options.env === "prod" ? "prod" : "stg" - }` - ); - - switch (finishedDeployment.status) { - case "DEPLOYED": { - if (warnings.warnings.length > 0) { - deploymentSpinner.stop("Deployment completed with warnings"); - } else { - deploymentSpinner.stop("Deployment completed"); - } - - await preExitTasks(); - - const taskCount = finishedDeployment.worker?.tasks.length ?? 0; - - if (taskCount === 0) { - outro( - `Version ${version} deployed with no detected tasks. Please make sure you are exporting tasks in your project. ${deploymentLink}` - ); - } else { - outro( - `Version ${version} deployed with ${taskCount} detected task${ - taskCount === 1 ? "" : "s" - } | ${deploymentLink} | ${testLink}` - ); - } - - break; - } - case "FAILED": { - if (finishedDeployment.errorData) { - if (finishedDeployment.errorData.name === "TaskMetadataParseError") { - const errorJson = safeJsonParse(finishedDeployment.errorData.stack); - - if (errorJson) { - const parsedError = TaskMetadataFailedToParseData.safeParse(errorJson); - - if (parsedError.success) { - deploymentSpinner.stop(`Deployment encountered an error. ${deploymentLink}`); - - logTaskMetadataParseError(parsedError.data.zodIssues, parsedError.data.tasks); - - await preExitTasks(); - - throw new SkipLoggingError( - `Deployment encountered an error: ${finishedDeployment.errorData.name}` - ); - } - } - } - - const parsedError = finishedDeployment.errorData.stack - ? parseBuildErrorStack(finishedDeployment.errorData) ?? - finishedDeployment.errorData.message - : finishedDeployment.errorData.message; - - if (typeof parsedError === "string") { - deploymentSpinner.stop(`Deployment encountered an error. ${deploymentLink}`); - - logger.log(`${chalkError("X Error:")} ${parsedError}`); - } else { - deploymentSpinner.stop(`Deployment encountered an error. ${deploymentLink}`); - - logESMRequireError(parsedError, resolvedConfig); - } - - await preExitTasks(); - - if (finishedDeployment.errorData.stderr) { - log.error(`Error logs:\n${finishedDeployment.errorData.stderr}`); - } - - throw new SkipLoggingError( - `Deployment encountered an error: ${finishedDeployment.errorData.name}` - ); - } else { - deploymentSpinner.stop( - `Deployment failed with an unknown error. Please contact eric@trigger.dev for help. ${deploymentLink}` - ); - - await preExitTasks(); - - throw new SkipLoggingError("Deployment failed with an unknown error"); - } - } - case "CANCELED": { - deploymentSpinner.stop(`Deployment was canceled. ${deploymentLink}`); - - await preExitTasks(); - - throw new SkipLoggingError("Deployment was canceled"); - } - case "TIMED_OUT": { - deploymentSpinner.stop(`Deployment timed out. ${deploymentLink}`); - - await preExitTasks(); - - throw new SkipLoggingError("Deployment timed out"); - } - } -} - -function printErrors(errors?: string[]) { - for (const error of errors ?? []) { - log.error(`${chalkError("Error:")} ${error}`); - } -} - -function printWarnings(warnings?: string[]) { - for (const warning of warnings ?? []) { - log.warn(`${chalkWarning("Warning:")} ${warning}`); - } -} - -type WarningsCheckReturn = - | { - ok: true; - warnings: string[]; - } - | { - ok: false; - summary: string; - errors: string[]; - warnings: string[]; - }; - -type LogParserOptions = Array<{ - regex: RegExp; - message: string; - shouldFail?: boolean; -}>; - -// 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. -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, - }; -} - -// Try to extract useful error messages from the logs -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; -} - -async function saveLogs(shortCode: string, logs: string) { - const logPath = join(await createTempDir(), `build-${shortCode}.log`); - await writeFile(logPath, logs); - return logPath; -} - -async function failDeploy( - shortCode: string, - errorSummary: string, - logs: string, - deploymentSpinner: ReturnType, - warnings?: string[], - errors?: string[] -) { - deploymentSpinner.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(shortCode, logs); - - printWarnings(warnings); - printErrors(errors); - - checkLogsForErrors(logs); - - outro(`${chalkError("Error:")} ${errorSummary}. Full build logs have been saved to ${logPath}`); - } else { - outro(`${chalkError("Error:")} ${errorSummary}.`); - } - - // TODO: Let platform know so it can fail the deploy with an appropriate error -} - -// Poll every 1 second for the deployment to finish -async function waitForDeploymentToFinish( - deploymentId: string, - client: CliApiClient, - timeoutInSeconds: number = 180 -) { - return tracer.startActiveSpan("waitForDeploymentToFinish", async (span) => { - try { - const start = Date.now(); - let attempts = 0; - - while (true) { - if (Date.now() - start > timeoutInSeconds * 1000) { - span.recordException(new Error("Deployment timed out")); - span.end(); - return; - } - - const deployment = await client.getDeployment(deploymentId); - - attempts++; - - if (!deployment.success) { - throw new Error(deployment.error); - } - - logger.debug(`Deployment status: ${deployment.data.status}`); - - if ( - deployment.data.status === "DEPLOYED" || - deployment.data.status === "FAILED" || - deployment.data.status === "CANCELED" || - deployment.data.status === "TIMED_OUT" - ) { - span.setAttributes({ - "deployment.status": deployment.data.status, - "deployment.attempts": attempts, - }); - - span.end(); - - return deployment.data; - } - - await setTimeout(1000); - } - } catch (error) { - recordSpanException(span, error); - span.end(); - - return error instanceof Error ? error.message : JSON.stringify(error); - } - }); -} - -type BuildAndPushImageOptions = { - registryHost: string; - auth: string; - imageTag: string; - buildId: string; - buildToken: string; - buildProjectId: string; - cwd: string; - projectId: string; - deploymentId: string; - deploymentVersion: string; - contentHash: string; - projectRef: string; - loadImage: boolean; - buildPlatform: string; - noCache: boolean; - extraCACerts?: string; -}; - -type BuildAndPushImageResults = - | { - ok: true; - image: string; - logs: string; - digest?: string; - } - | { - ok: false; - error: string; - logs: string; - }; - -async function buildAndPushImage( - options: BuildAndPushImageOptions, - updater: ReturnType -): Promise { - return tracer.startActiveSpan("buildAndPushImage", async (span) => { - span.setAttributes({ - "options.registryHost": options.registryHost, - "options.imageTag": options.imageTag, - "options.buildPlatform": options.buildPlatform, - "options.projectId": options.projectId, - "options.deploymentId": options.deploymentId, - "options.deploymentVersion": options.deploymentVersion, - "options.contentHash": options.contentHash, - "options.projectRef": options.projectRef, - "options.loadImage": options.loadImage, - }); - - // 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}`, - ...(options.extraCACerts - ? ["--build-arg", `NODE_EXTRA_CA_CERTS=${options.extraCACerts}`] - : []), - "-t", - `${options.registryHost}/${options.imageTag}`, - ".", - "--push", - options.loadImage ? "--load" : undefined, - ].filter(Boolean) as string[]; - - logger.debug(`depot ${args.join(" ")}`); - - span.setAttribute("depot.command", `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); - - span.setAttributes({ - "image.digest": digest, - }); - - span.end(); - - return { - ok: true as const, - image: options.imageTag, - logs, - digest, - }; - } catch (e) { - recordSpanException(span, e); - span.end(); - - return { - ok: false as const, - error: e instanceof Error ? e.message : JSON.stringify(e), - logs: extractLogs(errors), - }; - } - }); -} - -type BuildAndPushSelfHostedImageOptions = SetOptional< - Omit< - BuildAndPushImageOptions, - "buildId" | "buildToken" | "buildProjectId" | "auth" | "loadImage" - >, - "registryHost" -> & { - pushImage: boolean; - selfHostedRegistry: boolean; -}; - -async function buildAndPushSelfHostedImage( - options: BuildAndPushSelfHostedImageOptions -): Promise { - return await tracer.startActiveSpan("buildAndPushSelfHostedImage", async (span) => { - span.setAttributes({ - "options.imageTag": options.imageTag, - "options.buildPlatform": options.buildPlatform, - "options.projectId": options.projectId, - "options.deploymentId": options.deploymentId, - "options.deploymentVersion": options.deploymentVersion, - "options.contentHash": options.contentHash, - "options.projectRef": options.projectRef, - }); - - 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}`, - ...(options.extraCACerts - ? ["--build-arg", `NODE_EXTRA_CA_CERTS=${options.extraCACerts}`] - : []), - "-t", - imageRef, - ".", // The build context - ].filter(Boolean) as string[]; - - logger.debug(`docker ${buildArgs.join(" ")}`, { - cwd: options.cwd, - }); - - span.setAttribute("docker.command.build", `docker ${buildArgs.join(" ")}`); - - // Build the image - const buildProcess = execa("docker", buildArgs, { - cwd: options.cwd, - }); - - const errors: string[] = []; - let digest: string | undefined; - - try { - const processCode = await new Promise((res, rej) => { - // For some reason everything is output on stderr, not stdout - buildProcess.stderr?.on("data", (data: Buffer) => { - const text = data.toString(); - - errors.push(text); - logger.debug(text); - }); - - buildProcess.on("error", (e) => rej(e)); - buildProcess.on("close", (code) => res(code)); - }); - - if (processCode !== 0) { - return { - ok: false as const, - error: "Error building image", - logs: extractLogs(errors), - }; - } - - digest = extractImageDigest(errors); - - span.setAttributes({ - "image.digest": digest, - }); - } catch (e) { - recordSpanException(span, e); - - span.end(); - - return { - ok: false as const, - error: e instanceof Error ? e.message : JSON.stringify(e), - logs: extractLogs(errors), - }; - } - - const pushArgs = ["push", imageRef].filter(Boolean) as string[]; - - logger.debug(`docker ${pushArgs.join(" ")}`); - - span.setAttribute("docker.command.push", `docker ${pushArgs.join(" ")}`); - - if (options.selfHostedRegistry || options.pushImage) { - // Push the image - const pushProcess = execa("docker", pushArgs, { - cwd: options.cwd, - }); - - try { - const processCode = await new Promise((res, rej) => { - pushProcess.stdout?.on("data", (data: Buffer) => { - const text = data.toString(); - - logger.debug(text); - }); - - pushProcess.stderr?.on("data", (data: Buffer) => { - const text = data.toString(); - - logger.debug(text); - }); - - pushProcess.on("error", (e) => rej(e)); - pushProcess.on("close", (code) => res(code)); - }); - - if (processCode !== 0) { - return { - ok: false as const, - error: "Error pushing image", - logs: extractLogs(errors), - }; - } - - span.end(); - } catch (e) { - recordSpanException(span, e); - - span.end(); - - return { - ok: false as const, - error: e instanceof Error ? e.message : JSON.stringify(e), - logs: extractLogs(errors), - }; - } - } - - span.end(); - - return { - ok: true as const, - image: options.imageTag, - digest, - logs: extractLogs(errors), - }; - }); -} - -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; - } - } -} - -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"); -} - -async function compileProject( - config: ResolvedConfig, - options: DeployCommandOptions, - configPath?: string -) { - return await tracer.startActiveSpan("compileProject", async (span) => { - try { - if (!options.skipTypecheck) { - const typecheck = await typecheckProject(config); - - if (!typecheck) { - throw new Error("Typecheck failed, aborting deployment"); - } - } - - const compileSpinner = spinner(); - compileSpinner.start(`Building project in ${config.projectDir}`); - - const taskFiles = await gatherTaskFiles(config); - const workerFacade = readFileSync( - join(cliRootPath(), "workers", "prod", "worker-facade.js"), - "utf-8" - ); - - const workerSetupPath = join(cliRootPath(), "workers", "prod", "worker-setup.js"); - - let workerContents = workerFacade - .replace("__TASKS__", createTaskFileImports(taskFiles)) - .replace( - "__WORKER_SETUP__", - `import { tracingSDK, otelTracer, otelLogger } from "${escapeImportPath( - workerSetupPath - )}";` - ); - - if (configPath) { - logger.debug("Importing project config from", { configPath }); - - workerContents = workerContents.replace( - "__IMPORTED_PROJECT_CONFIG__", - `import * as importedConfigExports from "${escapeImportPath( - configPath - )}"; const importedConfig = importedConfigExports.config; const handleError = importedConfigExports.handleError;` - ); - } else { - workerContents = workerContents.replace( - "__IMPORTED_PROJECT_CONFIG__", - `const importedConfig = undefined; const handleError = undefined;` - ); - } - - const result = await build({ - stdin: { - contents: workerContents, - resolveDir: process.cwd(), - sourcefile: "__entryPoint.ts", - }, - bundle: true, - metafile: true, - write: false, - minify: false, - sourcemap: "external", // does not set the //# sourceMappingURL= comment in the file, we handle it ourselves - logLevel: "error", - platform: "node", - format: "cjs", // This is needed to support opentelemetry instrumentation that uses module patching - target: ["node18", "es2020"], - outdir: "out", - banner: { - js: `process.on("uncaughtException", function(error, origin) { if (error instanceof Error) { process.send && process.send({ type: "EVENT", message: { type: "UNCAUGHT_EXCEPTION", payload: { error: { name: error.name, message: error.message, stack: error.stack }, origin }, version: "v1" } }); } else { process.send && process.send({ type: "EVENT", message: { type: "UNCAUGHT_EXCEPTION", payload: { error: { name: "Error", message: typeof error === "string" ? error : JSON.stringify(error) }, origin }, version: "v1" } }); } });`, - }, - define: { - TRIGGER_API_URL: `"${config.triggerUrl}"`, - __PROJECT_CONFIG__: JSON.stringify(config), - }, - plugins: [ - mockServerOnlyPlugin(), - bundleDependenciesPlugin( - "workerFacade", - {}, - config.dependenciesToBundle, - config.tsconfigPath - ), - workerSetupImportConfigPlugin(configPath), - esbuildDecorators({ - tsconfig: config.tsconfigPath, - tsx: true, - force: false, - }), - ], - }); - - if (result.errors.length > 0) { - compileSpinner.stop("Build failed, aborting deployment"); - - span.setAttributes({ - "build.workerErrors": result.errors.map( - (error) => `Error: ${error.text} at ${error.location?.file}` - ), - }); - - throw new Error("Build failed, aborting deployment"); - } - - if (options.outputMetafile) { - await writeJSONFile(join(options.outputMetafile, "worker.json"), result.metafile); - } - - const entryPointContents = readFileSync( - join(cliRootPath(), "workers", "prod", "entry-point.js"), - "utf-8" - ); - - const entryPointResult = await build({ - stdin: { - contents: entryPointContents, - resolveDir: process.cwd(), - sourcefile: "index.ts", - }, - bundle: true, - metafile: true, - write: false, - minify: false, - sourcemap: false, - logLevel: "error", - platform: "node", - packages: "external", - format: "cjs", // This is needed to support opentelemetry instrumentation that uses module patching - target: ["node18", "es2020"], - outdir: "out", - define: { - __PROJECT_CONFIG__: JSON.stringify(config), - }, - plugins: [ - bundleDependenciesPlugin( - "entryPoint.ts", - {}, - config.dependenciesToBundle, - config.tsconfigPath - ), - ], - }); - - if (entryPointResult.errors.length > 0) { - compileSpinner.stop("Build failed, aborting deployment"); - - span.setAttributes({ - "build.entryPointErrors": entryPointResult.errors.map( - (error) => `Error: ${error.text} at ${error.location?.file}` - ), - }); - - throw new Error("Build failed, aborting deployment"); - } - - if (options.outputMetafile) { - await writeJSONFile( - join(options.outputMetafile, "entry-point.json"), - entryPointResult.metafile - ); - } - - // Create a tmp directory to store the build - const tempDir = await createTempDir(); - - logger.debug(`Writing compiled files to ${tempDir}`); - - // Get the metaOutput for the result build - const metaOutput = result.metafile!.outputs[posix.join("out", "stdin.js")]; - - invariant(metaOutput, "Meta output for the result build is missing"); - - // Get the metaOutput for the entryPoint build - const entryPointMetaOutput = - entryPointResult.metafile!.outputs[posix.join("out", "stdin.js")]; - - invariant(entryPointMetaOutput, "Meta output for the entryPoint build is missing"); - - // Get the outputFile and the sourceMapFile for the result build - const workerOutputFile = result.outputFiles.find( - (file) => file.path === join(config.projectDir, "out", "stdin.js") - ); - - invariant(workerOutputFile, "Output file for the result build is missing"); - - const workerSourcemapFile = result.outputFiles.find( - (file) => file.path === join(config.projectDir, "out", "stdin.js.map") - ); - - invariant(workerSourcemapFile, "Sourcemap file for the result build is missing"); - - // Get the outputFile for the entryPoint build - - const entryPointOutputFile = entryPointResult.outputFiles.find( - (file) => file.path === join(config.projectDir, "out", "stdin.js") - ); - - invariant(entryPointOutputFile, "Output file for the entryPoint build is missing"); - - // Save the result outputFile to /tmp/dir/worker.js (and make sure to map the sourceMap to the correct location in the file) - await writeFile( - join(tempDir, "worker.js"), - `${workerOutputFile.text}\n//# sourceMappingURL=worker.js.map` - ); - // Save the sourceMapFile to /tmp/dir/worker.js.map - await writeFile(join(tempDir, "worker.js.map"), workerSourcemapFile.text); - // Save the entryPoint outputFile to /tmp/dir/index.js - await writeFile(join(tempDir, "index.js"), entryPointOutputFile.text); - - logger.debug("Getting the imports for the worker and entryPoint builds", { - workerImports: metaOutput.imports, - entryPointImports: entryPointMetaOutput.imports, - }); - - // Get all the required dependencies from the metaOutputs and save them to /tmp/dir/package.json - const allImports = [...metaOutput.imports, ...entryPointMetaOutput.imports]; - - const javascriptProject = new JavascriptProject(config.projectDir); - - const dependencies = await resolveRequiredDependencies(allImports, config, javascriptProject); - - logger.debug("gatherRequiredDependencies()", { dependencies }); - - const packageJsonContents = { - ...javascriptProject.allowedPackageJson, - dependencies, - scripts: { - ...javascriptProject.scripts, - ...(typeof config.postInstall === "string" ? { postinstall: config.postInstall } : {}), - }, - }; - - span.setAttributes({ - ...flattenAttributes(packageJsonContents, "packageJson.contents"), - }); - - await writeJSONFile(join(tempDir, "package.json"), packageJsonContents); - - const copyResult = await copyAdditionalFiles(config, tempDir); - - if (!copyResult.ok) { - compileSpinner.stop("Project built with warnings"); - - log.warn( - `No additionalFiles matches for:\n\n${copyResult.noMatches - .map((glob) => `- "${glob}"`) - .join("\n")}\n\nIf this is unexpected you should check your ${cliLink( - "glob patterns", - "https://github.com/isaacs/node-glob?tab=readme-ov-file#glob-primer" - )} are valid.` - ); - } else { - compileSpinner.stop("Project built successfully"); - } - - const resolvingDependenciesResult = await resolveDependencies( - tempDir, - packageJsonContents, - config - ); - - if (!resolvingDependenciesResult) { - throw new SkipLoggingError("Failed to resolve dependencies"); - } - - // Write the Containerfile to /tmp/dir/Containerfile - const containerFilePath = join(cliRootPath(), "Containerfile.prod"); - - let containerFileContents = readFileSync(containerFilePath, "utf-8"); - - await writeFile(join(tempDir, "Containerfile"), containerFileContents); - - const contentHasher = createHash("sha256"); - contentHasher.update(Buffer.from(entryPointOutputFile.text)); - contentHasher.update(Buffer.from(workerOutputFile.text)); - contentHasher.update(Buffer.from(JSON.stringify(dependencies))); - - const contentHash = contentHasher.digest("hex"); - - span.setAttributes({ - contentHash: contentHash, - }); - - span.end(); - - return { path: tempDir, contentHash }; - } catch (e) { - recordSpanException(span, e); - - span.end(); - - throw e; - } - }); -} - -async function resolveEnvironmentVariables( - config: ReadConfigResult, - apiClient: CliApiClient, - options: DeployCommandOptions -) { - if (config.status !== "file") { - return; - } - - if (!config.module || typeof config.module.resolveEnvVars !== "function") { - return; - } - - const projectConfig = config.config; - - return await tracer.startActiveSpan("resolveEnvironmentVariables", async (span) => { - const $spinner = spinner(); - $spinner.start("Resolving environment variables"); - - try { - let processEnv: Record = { - ...process.env, - }; - - // Step 1: Get existing env vars from the apiClient - const environmentVariables = await apiClient.getEnvironmentVariables(projectConfig.project); - - if (environmentVariables.success) { - processEnv = { - ...processEnv, - ...environmentVariables.data.variables, - }; - } - - logger.debug("Existing environment variables", { - keys: Object.keys(processEnv), - }); - - // Step 2: Call the resolveEnvVars function with the existing env vars (and process.env) - const resolvedEnvVars = await callResolveEnvVars( - config.module, - processEnv, - options.env, - projectConfig.project - ); - - // Step 3: Upload the new env vars via the apiClient - if (resolvedEnvVars) { - const total = Object.keys(resolvedEnvVars.variables).length; - - logger.debug("Resolved env vars", { - keys: Object.keys(resolvedEnvVars.variables), - }); - - if (total > 0) { - $spinner.message( - `Syncing ${total} environment variable${total > 1 ? "s" : ""} with the server` - ); - - const uploadResult = await apiClient.importEnvVars(projectConfig.project, options.env, { - variables: resolvedEnvVars.variables, - override: - typeof resolvedEnvVars.override === "boolean" ? resolvedEnvVars.override : true, - }); - - if (uploadResult.success) { - $spinner.stop(`${total} environment variable${total > 1 ? "s" : ""} synced`); - return; - } else { - $spinner.stop("Failed to sync environment variables"); - - throw new Error(uploadResult.error); - } - } else { - $spinner.stop("No environment variables to sync"); - return; - } - } else { - $spinner.stop("No environment variables to sync"); - } - - $spinner.stop("Environment variables resolved"); - } catch (e) { - $spinner.stop("Failed to resolve environment variables"); - - recordSpanException(span, e); - - throw e; - } finally { - span.end(); - } - }); -} - -// Let's first create a digest from the package.json, and then use that digest to lookup a cached package-lock.json -// in the `.trigger/cache` directory. If the package-lock.json is found, we'll write it to the project directory -// If the package-lock.json is not found, we will run `npm install --package-lock-only` and then write the package-lock.json -// to the project directory, and finally we'll write the digest to the `.trigger/cache` directory with the contents of the package-lock.json -export async function resolveDependencies( - projectDir: string, - packageJsonContents: any, - config: ResolvedConfig -) { - return await tracer.startActiveSpan("resolveDependencies", async (span) => { - const resolvingDepsSpinner = spinner(); - resolvingDepsSpinner.start("Resolving dependencies"); - - const hasher = createHash("sha256"); - hasher.update(JSON.stringify(packageJsonContents)); - const digest = hasher.digest("hex").slice(0, 16); - - const cacheDir = join(config.projectDir, ".trigger", "cache"); - const cachePath = join(cacheDir, `${digest}.json`); - - span.setAttributes({ - "packageJson.digest": digest, - "cache.path": cachePath, - ...flattenAttributes(packageJsonContents, "packageJson.contents"), - }); - - try { - const cachedPackageLock = await readFile(cachePath, "utf-8"); - - logger.debug(`Using cached package-lock.json for ${digest}`); - - await writeFile(join(projectDir, "package-lock.json"), cachedPackageLock); - - span.setAttributes({ - "cache.hit": true, - }); - - span.end(); - - resolvingDepsSpinner.stop("Dependencies resolved"); - - return true; - } catch (e) { - // If the file doesn't exist, we'll continue to the next step - if (e instanceof Error && "code" in e && e.code !== "ENOENT") { - span.recordException(e as Error); - span.end(); - - resolvingDepsSpinner.stop(`Failed to resolve dependencies: ${e.message}`); - - return false; - } - - span.setAttributes({ - "cache.hit": false, - }); - - logger.debug(`No cached package-lock.json found for ${digest}`); - - try { - if (logger.loggerLevel === "debug") { - const childProcess = await execa("npm", ["config", "list"], { - cwd: projectDir, - stdio: "inherit", - }); - - logger.debug("npm config list"); - console.log(childProcess.stdout); - } - - await execa( - "npm", - [ - "install", - "--package-lock-only", - "--ignore-scripts", - "--no-audit", - "--legacy-peer-deps=false", - "--strict-peer-deps=false", - ], - { - cwd: projectDir, - stdio: logger.loggerLevel === "debug" ? "inherit" : "pipe", - } - ); - - const packageLockContents = await readFile(join(projectDir, "package-lock.json"), "utf-8"); - - logger.debug(`Writing package-lock.json to cache for ${digest}`); - - // Make sure the cache directory exists - await mkdir(cacheDir, { recursive: true }); - - // Save the package-lock.json to the cache - await writeFile(cachePath, packageLockContents); - - // Write the package-lock.json to the project directory - await writeFile(join(projectDir, "package-lock.json"), packageLockContents); - - span.end(); - - resolvingDepsSpinner.stop("Dependencies resolved"); - - return true; - } catch (installError) { - recordSpanException(span, installError); - span.end(); - - const parsedError = parseNpmInstallError(installError); - - if (typeof parsedError === "string") { - resolvingDepsSpinner.stop(`Failed to resolve dependencies: ${parsedError}`); - } else { - switch (parsedError.type) { - case "package-not-found-error": { - resolvingDepsSpinner.stop(`Failed to resolve dependencies`); - - logger.log( - `\n${chalkError("X Error:")} The package ${chalkPurple( - parsedError.packageName - )} could not be found in the npm registry.` - ); - - break; - } - case "no-matching-version-error": { - resolvingDepsSpinner.stop(`Failed to resolve dependencies`); - - logger.log( - `\n${chalkError("X Error:")} The package ${chalkPurple( - parsedError.packageName - )} could not resolve because the version doesn't exist` - ); - - break; - } - } - } - - return false; - } - } - }); -} - -export async function typecheckProject(config: ResolvedConfig) { - return await tracer.startActiveSpan("typecheckProject", async (span) => { - try { - const typecheckSpinner = spinner(); - typecheckSpinner.start("Typechecking project"); - - const tscTypecheck = execa("npm", ["exec", "tsc", "--", "--noEmit"], { - cwd: config.projectDir, - }); - - const stdouts: string[] = []; - const stderrs: string[] = []; - - tscTypecheck.stdout?.on("data", (chunk) => stdouts.push(chunk.toString())); - tscTypecheck.stderr?.on("data", (chunk) => stderrs.push(chunk.toString())); - - try { - await new Promise((resolve, reject) => { - tscTypecheck.addListener("exit", (code) => (code === 0 ? resolve(code) : reject(code))); - }); - } catch (error) { - typecheckSpinner.stop( - `Typechecking failed, check the logs below to view the issues. To skip typechecking, pass the --skip-typecheck flag` - ); - - logger.log(""); - - for (const stdout of stdouts) { - logger.log(stdout); - } - - span.recordException(new Error(stdouts.join("\n"))); - span.end(); - - return false; - } - - typecheckSpinner.stop(`Typechecking passed with 0 errors`); - - span.end(); - return true; - } catch (e) { - recordSpanException(span, e); - - span.end(); - - return false; - } - }); -} - -// Returns the dependencies that are required by the output that are found in output and the CLI package dependencies -// Returns the dependency names and the version to use (taken from the CLI deps package.json) -export async function resolveRequiredDependencies( - imports: Metafile["outputs"][string]["imports"], - config: ResolvedConfig, - project: JavascriptProject -) { - return await tracer.startActiveSpan("resolveRequiredDependencies", async (span) => { - const resolvablePackageNames = new Set(); - - for (const file of imports) { - if ((file.kind !== "require-call" && file.kind !== "dynamic-import") || !file.external) { - continue; - } - - const packageName = detectPackageNameFromImportPath(file.path); - - if (!packageName) { - continue; - } - - resolvablePackageNames.add(packageName); - } - - span.setAttribute("resolvablePackageNames", Array.from(resolvablePackageNames)); - - const resolvedPackageVersions = await project.resolveAll(Array.from(resolvablePackageNames)); - const missingPackages = Array.from(resolvablePackageNames).filter( - (packageName) => !resolvedPackageVersions[packageName] - ); - - span.setAttributes({ - ...flattenAttributes(resolvedPackageVersions, "resolvedPackageVersions"), - }); - span.setAttribute("missingPackages", missingPackages); - - const dependencies: Record = {}; - - for (const missingPackage of missingPackages) { - const internalDependencyVersion = - (packageJson.dependencies as Record)[missingPackage] ?? - detectDependencyVersion(missingPackage); - - if (internalDependencyVersion) { - dependencies[missingPackage] = stripWorkspaceFromVersion(internalDependencyVersion); - } - } - - for (const [packageName, version] of Object.entries(resolvedPackageVersions)) { - dependencies[packageName] = version; - } - - if (config.additionalPackages) { - span.setAttribute("additionalPackages", config.additionalPackages); - - for (const packageName of config.additionalPackages) { - if (dependencies[packageName]) { - continue; - } - - const packageParts = parsePackageName(packageName); - - if (packageParts.version) { - dependencies[packageParts.name] = packageParts.version; - continue; - } else { - const externalDependencyVersion = await project.resolve(packageParts.name, { - allowDev: true, - }); - - if (externalDependencyVersion) { - dependencies[packageParts.name] = externalDependencyVersion; - continue; - } else { - logger.log( - `${chalkWarning("X Warning:")} Could not find version for package ${chalkPurple( - packageName - )}, add a version specifier to the package name (e.g. ${ - packageParts.name - }@latest) or add it to your project's package.json` - ); - } - } - } - } - - if (!dependencies["@trigger.dev/sdk"]) { - logger.debug("Adding missing @trigger.dev/sdk dependency", { - version: packageJson.version, - }); - - span.setAttribute("addingMissingSDK", packageJson.version); - - dependencies["@trigger.dev/sdk"] = packageJson.version; - } - - if (!dependencies["@trigger.dev/core"]) { - logger.debug("Adding missing @trigger.dev/core dependency", { - version: packageJson.version, - }); - - span.setAttribute("addingMissingCore", packageJson.version); - - dependencies["@trigger.dev/core"] = packageJson.version; - } - - // Make sure we sort the dependencies by key to ensure consistent hashing - const result = Object.fromEntries( - Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b)) - ); - - span.setAttributes({ - ...flattenAttributes(result, "dependencies"), - }); - - span.end(); - - return result; - }); -} - -type AdditionalFilesReturn = - | { - ok: true; - } - | { - ok: false; - noMatches: string[]; - }; - -export async function copyAdditionalFiles( - config: ResolvedConfig, - tempDir: string -): Promise { - const additionalFiles = config.additionalFiles ?? []; - const noMatches: string[] = []; - - if (additionalFiles.length === 0) { - return { ok: true }; - } - - return await tracer.startActiveSpan( - "copyAdditionalFiles", - { - attributes: { - "config.additionalFiles": additionalFiles, - }, - }, - async (span) => { - try { - logger.debug(`Copying files to ${tempDir}`, { - additionalFiles, - }); - - const globOptions = { - withFileTypes: true, - ignore: ["node_modules"], - cwd: config.projectDir, - nodir: true, - } satisfies GlobOptions; - - const globs: Array = []; - let i = 0; - - for (const additionalFile of additionalFiles) { - let glob: GlobOptions | Glob; - - if (i === 0) { - glob = new Glob(additionalFile, globOptions); - } else { - const previousGlob = globs[i - 1]; - if (!previousGlob) { - logger.error("No previous glob, this shouldn't happen", { i, additionalFiles }); - continue; - } - - // Use the previous glob's options and cache - glob = new Glob(additionalFile, previousGlob); - } - - if (!(Symbol.asyncIterator in glob)) { - logger.error("Glob should be an async iterator", { glob }); - throw new Error("Unrecoverable error while copying additional files"); - } - - let matches = 0; - for await (const file of glob) { - matches++; - - // Any additional files that aren't a child of projectDir will be moved inside tempDir, so they can be part of the build context - // The file "../foo/bar" will be written to "tempDir/foo/bar" - // The file "../../bar/baz" will be written to "tempDir/bar/baz" - const pathInsideTempDir = relative(config.projectDir, file.fullpath()) - .split(posix.sep) - .filter((p) => p !== "..") - .join(posix.sep); - - const relativeDestinationPath = join(tempDir, pathInsideTempDir); - - logger.debug(`Copying file ${file.fullpath()} to ${relativeDestinationPath}`); - - await mkdir(dirname(relativeDestinationPath), { recursive: true }); - await copyFile(file.fullpath(), relativeDestinationPath); - } - - if (matches === 0) { - noMatches.push(additionalFile); - } - - globs[i] = glob; - i++; - } - - span.end(); - - if (noMatches.length > 0) { - return { - ok: false, - noMatches, - } as const; - } - - return { - ok: true, - } as const; - } catch (error) { - recordSpanException(span, error); - - span.end(); - - throw error; - } - } - ); -} - -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; -} diff --git a/packages/cli-v3/src/commands/dev.tsx b/packages/cli-v3/src/commands/dev.tsx index a6146dbb3..190ae582a 100644 --- a/packages/cli-v3/src/commands/dev.tsx +++ b/packages/cli-v3/src/commands/dev.tsx @@ -1,69 +1,11 @@ -import { - CreateBackgroundWorkerRequestBody, - ResolvedConfig, - TaskResource, - clientWebsocketMessages, - detectDependencyVersion, - serverWebsocketMessages, -} from "@trigger.dev/core/v3"; -import { ZodMessageHandler, ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler"; -import { watch } from "chokidar"; import { Command } from "commander"; -import { BuildContext, Metafile, context } from "esbuild"; -import { render, useInput } from "ink"; -import { createHash } from "node:crypto"; -import fs, { readFileSync } from "node:fs"; -import { ClientRequestArgs } from "node:http"; -import { basename, dirname, join, normalize } from "node:path"; -import pDebounce from "p-debounce"; -import { WebSocket } from "partysocket"; -import React, { Suspense, useEffect } from "react"; -import { ClientOptions, WebSocket as wsWebSocket } from "ws"; import { z } from "zod"; -import * as packageJson from "../../package.json"; import { CliApiClient } from "../apiClient.js"; import { CommonCommandOptions, commonOptions, wrapCommandAction } from "../cli/common.js"; -import { - bundleDependenciesPlugin, - bundleTriggerDevCore, - mockServerOnlyPlugin, - workerSetupImportConfigPlugin, -} from "../utilities/build.js"; -import { - chalkError, - chalkGrey, - chalkLink, - chalkPurple, - chalkTask, - chalkWorker, - cliLink, -} from "../utilities/cliOutput.js"; -import { readConfig } from "../utilities/configFiles.js"; -import { readJSONFile } from "../utilities/fileSystem.js"; -import { printDevBanner, printStandloneInitialBanner } from "../utilities/initialBanner.js"; -import { - detectPackageNameFromImportPath, - parsePackageName, - stripWorkspaceFromVersion, -} from "../utilities/installPackages.js"; +import { chalkError } from "../utilities/cliOutput.js"; import { logger } from "../utilities/logger.js"; +import { runtimeCheck } from "../utilities/runtimeCheck.js"; import { isLoggedIn } from "../utilities/session.js"; -import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles"; -import { TaskMetadataParseError, UncaughtExceptionError } from "../workers/common/errors"; -import { BackgroundWorker, BackgroundWorkerCoordinator } from "../workers/dev/backgroundWorker.js"; -import { runtimeCheck } from "../utilities/runtimeCheck"; -import { - logESMRequireError, - logTaskMetadataParseError, - parseBuildErrorStack, - parseNpmInstallError, -} from "../utilities/deployErrors"; -import { findUp, pathExists } from "find-up"; -import { cliRootPath } from "../utilities/resolveInternalFilePath"; -import { escapeImportPath } from "../utilities/windows"; -import { updateTriggerPackages } from "./update"; -import { esbuildDecorators } from "@anatine/esbuild-decorators"; -import { callResolveEnvVars } from "../utilities/resolveEnvVars"; let apiClient: CliApiClient | undefined; @@ -99,7 +41,7 @@ export function configureDevCommand(program: Command) { } const MINIMUM_NODE_MAJOR = 18; -const MINIMUM_NODE_MINOR = 16; +const MINIMUM_NODE_MINOR = 20; export async function devCommand(dir: string, options: DevCommandOptions) { try { @@ -129,867 +71,4 @@ export async function devCommand(dir: string, options: DevCommandOptions) { process.exitCode = 1; return; } - - const devInstance = await startDev(dir, options, authorization.auth, authorization.dashboardUrl); - const { waitUntilExit } = devInstance.devReactElement; - await waitUntilExit(); -} - -async function startDev( - dir: string, - options: DevCommandOptions, - authorization: { apiUrl: string; accessToken: string }, - dashboardUrl: string -) { - let rerender: (node: React.ReactNode) => void | undefined; - - try { - if (options.logLevel) { - logger.loggerLevel = options.logLevel; - } - - await printStandloneInitialBanner(true); - - let displayedUpdateMessage = false; - - if (!options.skipUpdateCheck) { - displayedUpdateMessage = await updateTriggerPackages(dir, { ...options }, true, true); - } - - printDevBanner(displayedUpdateMessage); - - logger.debug("Starting dev session", { dir, options, authorization }); - - let config = await readConfig(dir, { - projectRef: options.projectRef, - configFile: options.config, - }); - - logger.debug("Initial config", { config }); - - if (config.status === "error") { - logger.error("Failed to read config", config.error); - process.exit(1); - } - - async function getDevReactElement( - configParam: ResolvedConfig, - authorization: { apiUrl: string; accessToken: string }, - configPath?: string, - configModule?: any - ) { - const accessToken = authorization.accessToken; - const apiUrl = authorization.apiUrl; - - apiClient = new CliApiClient(apiUrl, accessToken); - - const devEnv = await apiClient.getProjectEnv({ - projectRef: configParam.project, - env: "dev", - }); - - if (!devEnv.success) { - if (devEnv.error === "Project not found") { - logger.error( - `Project not found: ${configParam.project}. Ensure you are using the correct project ref and CLI profile (use --profile). Currently using the "${options.profile}" profile, which points to ${authorization.apiUrl}` - ); - } else { - logger.error( - `Failed to initialize dev environment: ${devEnv.error}. Using project ref ${configParam.project}` - ); - } - - process.exit(1); - } - - const environmentClient = new CliApiClient(apiUrl, devEnv.data.apiKey); - - return ( - - ); - } - - const devReactElement = render( - await getDevReactElement( - config.config, - authorization, - config.status === "file" ? config.path : undefined, - config.status === "file" ? config.module : undefined - ) - ); - - rerender = devReactElement.rerender; - - return { - devReactElement, - stop: async () => { - devReactElement.unmount(); - }, - }; - } catch (e) { - throw e; - } -} - -type DevProps = { - config: ResolvedConfig; - dashboardUrl: string; - apiUrl: string; - apiKey: string; - environmentClient: CliApiClient; - projectName: string; - debuggerOn: boolean; - debugOtel: boolean; - configPath?: string; - configModule?: any; -}; - -function useDev({ - config, - dashboardUrl, - apiUrl, - apiKey, - environmentClient, - projectName, - debuggerOn, - debugOtel, - configPath, - configModule, -}: DevProps) { - useEffect(() => { - const websocketUrl = new URL(apiUrl); - websocketUrl.protocol = websocketUrl.protocol.replace("http", "ws"); - websocketUrl.pathname = `/ws`; - - const websocket = new WebSocket(websocketUrl.href, [], { - WebSocket: WebsocketFactory(apiKey), - connectionTimeout: 10000, - maxRetries: 10, - minReconnectionDelay: 1000, - maxReconnectionDelay: 30000, - reconnectionDelayGrowFactor: 1.4, // This leads to the following retry times: 1, 1.4, 1.96, 2.74, 3.84, 5.38, 7.53, 10.54, 14.76, 20.66 - maxEnqueuedMessages: 250, - }); - - const sender = new ZodMessageSender({ - schema: clientWebsocketMessages, - sender: async (message) => { - websocket.send(JSON.stringify(message)); - }, - }); - - const backgroundWorkerCoordinator = new BackgroundWorkerCoordinator( - `${dashboardUrl}/projects/v3/${config.project}` - ); - - websocket.addEventListener("open", async (event) => { - logger.debug("WebSocket opened", { event }); - }); - - websocket.addEventListener("close", (event) => { - logger.debug("WebSocket closed", { event }); - }); - - websocket.addEventListener("error", (event) => { - logger.log(`${chalkError("WebSocketError:")} ${event.error.message}`); - logger.debug("WebSocket error", { event, rawError: event.error }); - }); - - // This is the deprecated task heart beat that uses the friendly attempt ID - // It will only be used if the worker does not support lazy attempts - backgroundWorkerCoordinator.onWorkerTaskHeartbeat.attach( - async ({ worker, backgroundWorkerId, id }) => { - await sender.send("BACKGROUND_WORKER_MESSAGE", { - backgroundWorkerId, - data: { - type: "TASK_HEARTBEAT", - id, - }, - }); - } - ); - - // "Task Run Heartbeat" id is the actual run ID that corresponds to the MarQS message ID - backgroundWorkerCoordinator.onWorkerTaskRunHeartbeat.attach( - async ({ worker, backgroundWorkerId, id }) => { - await sender.send("BACKGROUND_WORKER_MESSAGE", { - backgroundWorkerId, - data: { - type: "TASK_RUN_HEARTBEAT", - id, - }, - }); - } - ); - - backgroundWorkerCoordinator.onTaskCompleted.attach( - async ({ backgroundWorkerId, completion, execution }) => { - await sender.send("BACKGROUND_WORKER_MESSAGE", { - backgroundWorkerId, - data: { - type: "TASK_RUN_COMPLETED", - completion, - execution, - }, - }); - } - ); - - backgroundWorkerCoordinator.onTaskFailedToRun.attach( - async ({ backgroundWorkerId, completion }) => { - await sender.send("BACKGROUND_WORKER_MESSAGE", { - backgroundWorkerId, - data: { - type: "TASK_RUN_FAILED_TO_RUN", - completion, - }, - }); - } - ); - - backgroundWorkerCoordinator.onWorkerRegistered.attach(async ({ id, worker, record }) => { - await sender.send("READY_FOR_TASKS", { - backgroundWorkerId: id, - }); - }); - - backgroundWorkerCoordinator.onWorkerDeprecated.attach(async ({ id, worker }) => { - await sender.send("BACKGROUND_WORKER_DEPRECATED", { - backgroundWorkerId: id, - }); - }); - - websocket.addEventListener("message", async (event) => { - try { - const data = JSON.parse( - typeof event.data === "string" ? event.data : new TextDecoder("utf-8").decode(event.data) - ); - - const messageHandler = new ZodMessageHandler({ - schema: serverWebsocketMessages, - messages: { - SERVER_READY: async (payload) => { - for (const worker of backgroundWorkerCoordinator.currentWorkers) { - await sender.send("READY_FOR_TASKS", { - backgroundWorkerId: worker.id, - inProgressRuns: worker.worker.inProgressRuns, - }); - } - }, - BACKGROUND_WORKER_MESSAGE: async (payload) => { - await backgroundWorkerCoordinator.handleMessage( - payload.backgroundWorkerId, - payload.data - ); - }, - }, - }); - - await messageHandler.handleMessage(data); - } catch (error) { - if (error instanceof Error) { - logger.error("Error while handling websocket message", { error: error.message }); - } else { - logger.error( - "Unkown error while handling websocket message, use `-l debug` for additional output" - ); - logger.debug("Error while handling websocket message", { error }); - } - } - }); - - let ctx: BuildContext | undefined; - - let firstBuild = true; - - async function runBuild() { - if (ctx) { - // This will stop the watching - await ctx.dispose(); - } - - let latestWorkerContentHash: string | undefined; - - const taskFiles = await gatherTaskFiles(config); - - const workerFacadePath = join(cliRootPath(), "workers", "dev", "worker-facade.js"); - const workerFacade = readFileSync(workerFacadePath, "utf-8"); - - const workerSetupPath = join(cliRootPath(), "workers", "dev", "worker-setup.js"); - - let entryPointContents = workerFacade - .replace("__TASKS__", createTaskFileImports(taskFiles)) - .replace( - "__WORKER_SETUP__", - `import { tracingSDK, otelTracer, otelLogger, sender } from "${escapeImportPath( - workerSetupPath - )}";` - ); - - if (configPath) { - configPath = normalize(configPath); - logger.debug("Importing project config from", { configPath }); - - entryPointContents = entryPointContents.replace( - "__IMPORTED_PROJECT_CONFIG__", - `import * as importedConfigExports from "${escapeImportPath( - configPath - )}"; const importedConfig = importedConfigExports.config; const handleError = importedConfigExports.handleError;` - ); - } else { - entryPointContents = entryPointContents.replace( - "__IMPORTED_PROJECT_CONFIG__", - `const importedConfig = undefined; const handleError = undefined;` - ); - } - - logger.log(chalkGrey("○ Building background worker…")); - - ctx = await context({ - stdin: { - contents: entryPointContents, - resolveDir: process.cwd(), - sourcefile: "__entryPoint.ts", - }, - banner: { - js: `process.on("uncaughtException", function(error, origin) { if (error instanceof Error) { process.send && process.send({ type: "UNCAUGHT_EXCEPTION", payload: { error: { name: error.name, message: error.message, stack: error.stack }, origin }, version: "v1" }); } else { process.send && process.send({ type: "UNCAUGHT_EXCEPTION", payload: { error: { name: "Error", message: typeof error === "string" ? error : JSON.stringify(error) }, origin }, version: "v1" }); } });`, - }, - bundle: true, - metafile: true, - write: false, - minify: false, - sourcemap: "external", // does not set the //# sourceMappingURL= comment in the file, we handle it ourselves - logLevel: "error", - platform: "node", - format: "cjs", // This is needed to support opentelemetry instrumentation that uses module patching - target: ["node18", "es2020"], - outdir: "out", - define: { - TRIGGER_API_URL: `"${config.triggerUrl}"`, - __PROJECT_CONFIG__: JSON.stringify(config), - }, - plugins: [ - mockServerOnlyPlugin(), - bundleTriggerDevCore("workerFacade", config.tsconfigPath), - bundleDependenciesPlugin( - "workerFacade", - {}, - (config.dependenciesToBundle ?? []).concat([/^@trigger.dev/]), - config.tsconfigPath - ), - workerSetupImportConfigPlugin(configPath), - esbuildDecorators({ - tsconfig: config.tsconfigPath, - tsx: true, - force: false, - }), - { - name: "trigger.dev v3", - setup(build) { - build.onEnd(async (result) => { - if (result.errors.length > 0) return; - if (!result || !result.outputFiles) { - logger.error("Build failed: no result"); - return; - } - - if (!firstBuild) { - logger.log(chalkGrey("○ Building background worker…")); - } - - const metaOutputKey = join("out", `stdin.js`).replace(/\\/g, "/"); - - const metaOutput = result.metafile!.outputs[metaOutputKey]; - - if (!metaOutput) { - throw new Error(`Could not find metafile`); - } - - const outputFileKey = join(config.projectDir, metaOutputKey); - const outputFile = result.outputFiles.find((file) => file.path === outputFileKey); - - if (!outputFile) { - throw new Error( - `Could not find output file for entry point ${metaOutput.entryPoint}` - ); - } - - const sourceMapFileKey = join(config.projectDir, `${metaOutputKey}.map`); - const sourceMapFile = result.outputFiles.find( - (file) => file.path === sourceMapFileKey - ); - - const md5Hasher = createHash("md5"); - md5Hasher.update(Buffer.from(outputFile.contents.buffer)); - - const contentHash = md5Hasher.digest("hex"); - - if (latestWorkerContentHash === contentHash) { - logger.log(chalkGrey("○ No changes detected, skipping build…")); - - return; - } - - // Create a file at join(dir, ".trigger", path) with the fileContents - const fullPath = join(config.projectDir, ".trigger", `${contentHash}.js`); - const sourceMapPath = `${fullPath}.map`; - - const outputFileWithSourceMap = `${ - outputFile.text - }\n//# sourceMappingURL=${basename(sourceMapPath)}`; - - await fs.promises.mkdir(dirname(fullPath), { recursive: true }); - await fs.promises.writeFile(fullPath, outputFileWithSourceMap); - - logger.debug(`Wrote background worker to ${fullPath}`); - - const dependencies = await gatherRequiredDependencies(metaOutput, config); - - if (sourceMapFile) { - const sourceMapPath = `${fullPath}.map`; - await fs.promises.writeFile(sourceMapPath, sourceMapFile.text); - } - - const environmentVariablesResponse = - await environmentClient.getEnvironmentVariables(config.project); - - const processEnv = await gatherProcessEnv(); - - const backgroundWorker = new BackgroundWorker( - fullPath, - { - projectConfig: config, - dependencies, - env: { - ...processEnv, - TRIGGER_API_URL: apiUrl, - TRIGGER_SECRET_KEY: apiKey, - ...(environmentVariablesResponse.success - ? environmentVariablesResponse.data.variables - : {}), - }, - debuggerOn, - debugOtel, - resolveEnvVariables: createResolveEnvironmentVariablesFunction(configModule), - }, - environmentClient - ); - - try { - await backgroundWorker.initialize(); - - latestWorkerContentHash = contentHash; - - let packageVersion: string | undefined; - - const taskResources: Array = []; - - if (!backgroundWorker.tasks || backgroundWorker.tasks.length === 0) { - logger.log( - `${chalkError( - "X Error:" - )} Worker failed to build: no tasks found. Searched in ${config.triggerDirectories.join( - ", " - )}` - ); - return; - } - - for (const task of backgroundWorker.tasks) { - taskResources.push(task); - - packageVersion = task.packageVersion; - } - - if (!packageVersion) { - throw new Error(`Background Worker started without package version`); - } - - // Check for any duplicate task ids - const taskIds = taskResources.map((task) => task.id); - const duplicateTaskIds = taskIds.filter( - (id, index) => taskIds.indexOf(id) !== index - ); - - if (duplicateTaskIds.length > 0) { - logger.error( - createDuplicateTaskIdOutputErrorMessage(duplicateTaskIds, taskResources) - ); - return; - } - - logger.debug("Creating background worker with tasks", { - tasks: taskResources, - }); - - const backgroundWorkerBody: CreateBackgroundWorkerRequestBody = { - localOnly: true, - metadata: { - packageVersion, - cliPackageVersion: packageJson.version, - tasks: taskResources, - contentHash: contentHash, - }, - supportsLazyAttempts: true, - }; - - const backgroundWorkerRecord = await environmentClient.createBackgroundWorker( - config.project, - backgroundWorkerBody - ); - - if (!backgroundWorkerRecord.success) { - throw new Error(backgroundWorkerRecord.error); - } - - backgroundWorker.metadata = backgroundWorkerRecord.data; - backgroundWorker; - - const testUrl = `${dashboardUrl}/projects/v3/${config.project}/test?environment=dev`; - const runsUrl = `${dashboardUrl}/projects/v3/${config.project}/runs?envSlug=dev`; - - const pipe = chalkGrey("|"); - const bullet = chalkGrey("○"); - const arrow = chalkGrey("->"); - - const testLink = chalkLink(cliLink("Test tasks", testUrl)); - const runsLink = chalkLink(cliLink("View runs", runsUrl)); - - const workerStarted = chalkGrey("Background worker started"); - const workerVersion = chalkWorker(backgroundWorkerRecord.data.version); - - logger.log( - `${bullet} ${workerStarted} ${arrow} ${workerVersion} ${pipe} ${testLink} ${pipe} ${runsLink}` - ); - - firstBuild = false; - - await backgroundWorkerCoordinator.registerWorker( - backgroundWorkerRecord.data, - backgroundWorker - ); - } catch (e) { - logger.debug("Error starting background worker", { - error: e, - }); - - if (e instanceof TaskMetadataParseError) { - logTaskMetadataParseError(e.zodIssues, e.tasks); - return; - } else if (e instanceof UncaughtExceptionError) { - const parsedBuildError = parseBuildErrorStack(e.originalError); - - if (parsedBuildError && typeof parsedBuildError !== "string") { - logESMRequireError( - parsedBuildError, - configPath - ? { status: "file", path: configPath, config } - : { status: "in-memory", config } - ); - return; - } else { - } - - if (e.originalError.message || e.originalError.stack) { - logger.log( - `${chalkError("X Error:")} Worker failed to start`, - e.originalError.stack ?? e.originalError.message - ); - } - - return; - } - - const parsedError = parseNpmInstallError(e); - - if (typeof parsedError === "string") { - logger.log(`\n${chalkError("X Error:")} ${parsedError}`); - } else { - switch (parsedError.type) { - case "package-not-found-error": { - logger.log( - `\n${chalkError("X Error:")} The package ${chalkPurple( - parsedError.packageName - )} could not be found in the npm registry.` - ); - - break; - } - case "no-matching-version-error": { - logger.log( - `\n${chalkError("X Error:")} The package ${chalkPurple( - parsedError.packageName - )} could not resolve because the version doesn't exist` - ); - - break; - } - } - } - - const stderr = backgroundWorker.stderr - .map((line) => line.trim()) - .filter((line) => line.length > 0) - .join("\n"); - - if (stderr) { - logger.log(`\n${chalkError("X Error logs:")}\n${stderr}`); - } - } - }); - }, - }, - ], - }); - - await ctx.watch(); - } - - const throttledRebuild = pDebounce(runBuild, 250, { before: true }); - - const taskFileWatcher = watch( - config.triggerDirectories.map((triggerDir) => `${triggerDir}/**/*.ts`), - { - ignoreInitial: true, - } - ); - - taskFileWatcher.on("add", async (path) => { - throttledRebuild().catch((error) => { - logger.error(error); - }); - }); - - taskFileWatcher.on("unlink", async (path) => { - throttledRebuild().catch((error) => { - logger.error(error); - }); - }); - - throttledRebuild().catch((error) => { - logger.error(error); - }); - - return () => { - const cleanup = async () => { - logger.debug(`Shutting down dev session for ${config.project}`); - - const start = Date.now(); - - await taskFileWatcher.close(); - - websocket?.close(); - backgroundWorkerCoordinator.close(); - ctx?.dispose().catch((error) => { - console.error(error); - }); - - logger.debug(`Shutdown completed in ${Date.now() - start}ms`); - }; - - cleanup(); - }; - }, [config, apiUrl, apiKey, environmentClient]); -} - -function DevUI(props: DevProps) { - return ( - - - - ); -} - -function DevUIImp(props: DevProps) { - const dev = useDev(props); - - return ( - <> - - - ); -} - -function useHotkeys() { - useInput(async (input, key) => {}); -} - -function HotKeys() { - useHotkeys(); - - return <>; -} - -function WebsocketFactory(apiKey: string) { - return class extends wsWebSocket { - constructor(address: string | URL, options?: ClientOptions | ClientRequestArgs) { - super(address, { ...(options ?? {}), headers: { Authorization: `Bearer ${apiKey}` } }); - } - }; -} - -// Returns the dependencies that are required by the output that are found in output and the CLI package dependencies -// Returns the dependency names and the version to use (taken from the CLI deps package.json) -async function gatherRequiredDependencies( - outputMeta: Metafile["outputs"][string], - config: ResolvedConfig -) { - const dependencies: Record = {}; - - logger.debug("Gathering required dependencies from imports", { - imports: outputMeta.imports, - }); - - for (const file of outputMeta.imports) { - if ((file.kind !== "require-call" && file.kind !== "dynamic-import") || !file.external) { - continue; - } - - const packageName = detectPackageNameFromImportPath(file.path); - - if (dependencies[packageName]) { - continue; - } - - const internalDependencyVersion = - (packageJson.dependencies as Record)[packageName] ?? - detectDependencyVersion(packageName); - - if (internalDependencyVersion) { - dependencies[packageName] = stripWorkspaceFromVersion(internalDependencyVersion); - } - } - - if (config.additionalPackages) { - const projectPackageJson = await readJSONFile(join(config.projectDir, "package.json")); - - for (const packageName of config.additionalPackages) { - if (dependencies[packageName]) { - continue; - } - - const packageParts = parsePackageName(packageName); - - if (packageParts.version) { - dependencies[packageParts.name] = packageParts.version; - continue; - } else { - const externalDependencyVersion = { - ...projectPackageJson?.devDependencies, - ...projectPackageJson?.dependencies, - }[packageName]; - - if (externalDependencyVersion) { - dependencies[packageParts.name] = externalDependencyVersion; - continue; - } else { - logger.warn( - `Could not find version for package ${packageName}, add a version specifier to the package name (e.g. ${packageParts.name}@latest) or add it to your project's package.json` - ); - } - } - } - } - - return dependencies; -} - -function createDuplicateTaskIdOutputErrorMessage( - duplicateTaskIds: Array, - taskResources: Array -) { - const duplicateTable = duplicateTaskIds - .map((id) => { - const tasks = taskResources.filter((task) => task.id === id); - - return `\n\n${chalkTask(id)} was found in:${tasks - .map((task) => `\n${task.filePath} -> ${task.exportName}`) - .join("")}`; - }) - .join(""); - - return `Duplicate ${chalkTask("task id")} detected:${duplicateTable}`; -} - -async function gatherProcessEnv() { - const env = { - ...process.env, - NODE_ENV: process.env.NODE_ENV ?? "development", - NODE_PATH: await amendNodePathWithPnpmNodeModules(process.env.NODE_PATH), - }; - - // Filter out undefined values - return Object.fromEntries(Object.entries(env).filter(([key, value]) => value !== undefined)); -} - -async function amendNodePathWithPnpmNodeModules(nodePath?: string): Promise { - const pnpmModulesPath = await findPnpmNodeModulesPath(); - - if (!pnpmModulesPath) { - return nodePath; - } - - if (nodePath) { - if (nodePath.includes(pnpmModulesPath)) { - return nodePath; - } - - return `${nodePath}:${pnpmModulesPath}`; - } - - return pnpmModulesPath; -} - -async function findPnpmNodeModulesPath(): Promise { - return await findUp( - async (directory) => { - const pnpmModules = join(directory, "node_modules", ".pnpm", "node_modules"); - - const hasPnpmNodeModules = await pathExists(pnpmModules); - - if (hasPnpmNodeModules) { - return pnpmModules; - } - }, - { type: "directory" } - ); -} - -let hasResolvedEnvVars = false; -let resolvedEnvVars: Record = {}; - -function createResolveEnvironmentVariablesFunction(configModule?: any) { - return async ( - env: Record, - worker: BackgroundWorker - ): Promise | undefined> => { - if (hasResolvedEnvVars) { - return resolvedEnvVars; - } - - const $resolvedEnvVars = await callResolveEnvVars( - configModule, - env, - "dev", - worker.params.projectConfig.project - ); - - if ($resolvedEnvVars) { - resolvedEnvVars = $resolvedEnvVars.variables; - hasResolvedEnvVars = true; - } - - return resolvedEnvVars; - }; } diff --git a/packages/cli-v3/src/commands/init.ts b/packages/cli-v3/src/commands/init.ts index dd83cc4ce..830938a4e 100644 --- a/packages/cli-v3/src/commands/init.ts +++ b/packages/cli-v3/src/commands/init.ts @@ -20,7 +20,6 @@ import { tracer, wrapCommandAction, } from "../cli/common.js"; -import { readConfig } from "../utilities/configFiles.js"; import { createFileFromTemplate } from "../utilities/createFileFromTemplate.js"; import { createFile, pathExists, readFile } from "../utilities/fileSystem.js"; import { PackageManager, getUserPackageManager } from "../utilities/getUserPackageManager.js"; @@ -30,8 +29,8 @@ 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 { version } from "../../package.json"; import { cliLink, prettyError } from "../utilities/cliOutput.js"; +import { loadConfig } from "../config.js"; const InitCommandOptions = CommonCommandOptions.extend({ projectRef: z.string().optional(), @@ -56,7 +55,7 @@ export function configureInitCommand(program: Command) { .option( "-t, --tag ", "The version of the @trigger.dev/sdk package to install", - version + "latest" ) .option("--skip-package-install", "Skip installing the @trigger.dev/sdk package") .option("--override-config", "Override the existing config file if it exists") @@ -112,11 +111,11 @@ async function _initCommand(dir: string, options: InitCommandOptions) { if (!options.overrideConfig) { try { // check to see if there is an existing trigger.dev config file in the project directory - const result = await readConfig(dir); + const result = await loadConfig({ cwd: dir }); outro( - result.status === "file" - ? `Project already initialized: Found config file at ${result.path}. Pass --override-config to override` + result.configFile + ? `Project already initialized: Found config file at ${result.configFile}. Pass --override-config to override` : "Project already initialized" ); diff --git a/packages/cli-v3/src/commands/login.ts b/packages/cli-v3/src/commands/login.ts index 459f063f9..8efd4932f 100644 --- a/packages/cli-v3/src/commands/login.ts +++ b/packages/cli-v3/src/commands/login.ts @@ -15,13 +15,13 @@ import { } from "../cli/common.js"; import { chalkLink, prettyError } from "../utilities/cliOutput.js"; import { readAuthConfigProfile, writeAuthConfigProfile } from "../utilities/configFiles.js"; -import { getVersion } from "../utilities/getVersion.js"; import { printInitialBanner } from "../utilities/initialBanner.js"; import { LoginResult } from "../utilities/session.js"; import { whoAmI } from "./whoami.js"; import { logger } from "../utilities/logger.js"; import { spinner } from "../utilities/windows.js"; import { isLinuxServer } from "../utilities/linux.js"; +import { VERSION } from "../consts.js"; export const LoginCommandOptions = CommonCommandOptions.extend({ apiUrl: z.string(), @@ -35,7 +35,7 @@ export function configureLoginCommand(program: Command) { .command("login") .description("Login with Trigger.dev so you can perform authenticated actions") ) - .version(getVersion(), "-v, --version", "Display the version number") + .version(VERSION, "-v, --version", "Display the version number") .action(async (options) => { await handleTelemetry(async () => { await printInitialBanner(false); diff --git a/packages/cli-v3/src/commands/update.ts b/packages/cli-v3/src/commands/update.ts index 2bb1c9c5e..51b8e9512 100644 --- a/packages/cli-v3/src/commands/update.ts +++ b/packages/cli-v3/src/commands/update.ts @@ -10,8 +10,8 @@ import { printStandloneInitialBanner, updateCheck } from "../utilities/initialBa import { join, resolve } from "path"; import { JavascriptProject } from "../utilities/javascriptProject.js"; import { PackageManager } from "../utilities/getUserPackageManager.js"; -import { getVersion } from "../utilities/getVersion.js"; import { chalkError, prettyError, prettyWarning } from "../utilities/cliOutput.js"; +import { VERSION } from "../consts.js"; export const UpdateCommandOptions = CommonCommandOptions.pick({ logLevel: true, @@ -66,7 +66,7 @@ export async function updateTriggerPackages( return false; } - const cliVersion = getVersion(); + const cliVersion = VERSION; const newCliVersion = await updateCheck(); if (newCliVersion) { diff --git a/packages/cli-v3/src/config.ts b/packages/cli-v3/src/config.ts new file mode 100644 index 000000000..a60bc1142 --- /dev/null +++ b/packages/cli-v3/src/config.ts @@ -0,0 +1,201 @@ +import { TriggerConfig } from "@trigger.dev/core/v3"; +import { DEFAULT_RUNTIME, ResolvedConfig } from "@trigger.dev/core/v3/build"; +import * as c12 from "c12"; +import { defu } from "defu"; +import * as esbuild from "esbuild"; +import { readdir } from "node:fs/promises"; +import { basename, dirname, isAbsolute, join, relative } from "node:path"; +import { findWorkspaceDir, resolveLockfile, resolvePackageJSON, resolveTSConfig } from "pkg-types"; +import { generateCode, loadFile } from "./utilities/importMagicast.js"; + +export type ResolveConfigOptions = { + cwd?: string; +}; + +export async function loadConfig({ + cwd = process.cwd(), +}: ResolveConfigOptions = {}): Promise { + const result = await c12.loadConfig({ + name: "trigger", + cwd, + }); + + return await resolveConfig(cwd, result); +} + +type ResolveWatchConfigOptions = ResolveConfigOptions & { + onUpdate: (config: ResolvedConfig) => void; + debounce?: number; + ignoreInitial?: boolean; +}; + +type ResolveWatchConfigResult = { + config: ResolvedConfig; + files: string[]; + stop: () => Promise; +}; + +export async function watchConfig({ + cwd = process.cwd(), + onUpdate, + debounce = 100, + ignoreInitial = true, +}: ResolveWatchConfigOptions): Promise { + const result = await c12.watchConfig({ + name: "trigger", + cwd, + debounce, + chokidarOptions: { ignoreInitial }, + acceptHMR: async ({ oldConfig, newConfig, getDiff }) => { + const diff = getDiff(); + + console.log("watchConfig.acceptHMR", { diff, oldConfig, newConfig }); + + if (diff.length === 0) { + console.log("No config changed detected!"); + return true; // No changes! + } + + return false; + }, + onUpdate: async ({ newConfig, getDiff }) => { + const diff = getDiff(); + + if (diff.length === 0) { + console.log("No config changed detected!"); + return; + } + + const resolvedConfig = await resolveConfig(cwd, newConfig); + + onUpdate(resolvedConfig); + }, + }); + + const config = await resolveConfig(cwd, result); + + return { + config, + files: result.watchingFiles, + stop: result.unwatch, + }; +} + +export function configPlugin(resolvedConfig: ResolvedConfig): esbuild.Plugin | undefined { + const configFile = resolvedConfig.configFile; + + if (!configFile) { + return; + } + + // We need to strip the "build" key from the config file, so build dependencies don't make it into the final bundle + return { + name: "trigger-config-strip", + setup(build) { + const filename = basename(configFile); + // Convert the filename to a regex to filter against + const filter = new RegExp(`${filename.replace(/\./g, "\\.")}$`); + + console.log("trigger-config-strip.filter", filter); + + build.onLoad({ filter }, async (args) => { + console.log("trigger-config-strip.onLoad", args); + + const $mod = await loadFile(args.path); + + // Support for both bare object export and `defineConfig` wrapper + const options = + $mod.exports.default.$type === "function-call" + ? $mod.exports.default.$args[0] + : $mod.exports.default; + + options.build = {}; + + const contents = generateCode($mod); + + console.log("trigger-config-strip.onLoad.contents", contents); + + return { + contents: contents.code, + loader: "ts", + resolveDir: dirname(args.path), + }; + }); + }, + }; +} + +async function resolveConfig( + cwd: string, + result: c12.ResolvedConfig +): Promise { + const packageJsonPath = await resolvePackageJSON(cwd); + const tsconfigPath = await resolveTSConfig(cwd); + const lockfilePath = await resolveLockfile(cwd); + const workspaceDir = await findWorkspaceDir(cwd); + + const workingDir = packageJsonPath ? dirname(packageJsonPath) : cwd; + + let dirs = result.config.dirs ? result.config.dirs : await autoDetectDirs(workingDir); + + dirs = dirs.map((dir) => (isAbsolute(dir) ? relative(workingDir, dir) : dir)); + + const mergedConfig = defu( + { + workingDir: packageJsonPath ? dirname(packageJsonPath) : cwd, + configFile: result.configFile, + packageJsonPath, + tsconfigPath, + lockfilePath, + workspaceDir, + }, + result.config, + { + dirs, + runtime: DEFAULT_RUNTIME, + tsconfig: tsconfigPath, + build: { + jsx: { + factory: "React.createElement", + fragment: "React.Fragment", + automatic: true, + }, + extensions: [], + external: [], + }, + } + ); + + return { + ...mergedConfig, + dirs: Array.from(new Set(mergedConfig.dirs)), + }; +} + +const IGNORED_DIRS = ["node_modules", ".git", "dist", "out", "build"]; + +async function autoDetectDirs(workingDir: string): Promise { + const entries = await readdir(workingDir, { withFileTypes: true }); + + const dirs: string[] = []; + + for (const entry of entries) { + if (!entry.isDirectory() || IGNORED_DIRS.includes(entry.name) || entry.name.startsWith(".")) + continue; + + const fullPath = join(workingDir, entry.name); + + // Ignore the directory if it's /app/api/trigger + if (fullPath.endsWith("app/api/trigger")) { + continue; + } + + if (entry.name === "trigger") { + dirs.push(fullPath); + } + + dirs.push(...(await autoDetectDirs(fullPath))); + } + + return dirs; +} diff --git a/packages/cli-v3/src/consts.ts b/packages/cli-v3/src/consts.ts index e265a6a9e..eddc0d83e 100644 --- a/packages/cli-v3/src/consts.ts +++ b/packages/cli-v3/src/consts.ts @@ -1,13 +1,5 @@ -import path from "path"; -import { fileURLToPath } from "url"; - -// With the move to TSUP as a build tool, this keeps path routes in other files (installers, loaders, etc) in check more easily. -// Path is in relation to a single index.js file inside ./dist -const __filename = fileURLToPath(import.meta.url); -const distPath = path.dirname(__filename); - -export const PKG_ROOT = path.join(distPath, "../"); export const COMMAND_NAME = "trigger.dev"; export const CLOUD_WEB_URL = "https://cloud.trigger.dev"; export const CLOUD_API_URL = "https://api.trigger.dev"; export const CONFIG_FILES = ["trigger.config.ts", "trigger.config.js", "trigger.config.mjs"]; +export const VERSION = "0.0.1"; // This is replaced by the build script diff --git a/packages/cli-v3/src/packageDir-cjs.cts b/packages/cli-v3/src/packageDir-cjs.cts new file mode 100644 index 000000000..3cc5fffb5 --- /dev/null +++ b/packages/cli-v3/src/packageDir-cjs.cts @@ -0,0 +1,3 @@ +import { pathToFileURL } from "node:url"; +//@ts-ignore - Have to ignore because TSC thinks this is ESM +export const packageDir = pathToFileURL(__dirname).pathname; diff --git a/packages/cli-v3/src/packageDir.ts b/packages/cli-v3/src/packageDir.ts new file mode 100644 index 000000000..a75fb232a --- /dev/null +++ b/packages/cli-v3/src/packageDir.ts @@ -0,0 +1,2 @@ +//@ts-ignore +export const packageDir = new URL(".", import.meta.url).pathname; diff --git a/packages/cli-v3/src/telemetry/tracing.ts b/packages/cli-v3/src/telemetry/tracing.ts index 552d467d4..425bf5b4b 100644 --- a/packages/cli-v3/src/telemetry/tracing.ts +++ b/packages/cli-v3/src/telemetry/tracing.ts @@ -4,12 +4,12 @@ import { Resource, detectResourcesSync, processDetectorSync } from "@opentelemet import { NodeTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-node"; import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch"; import { DiagConsoleLogger, DiagLogLevel, diag, trace } from "@opentelemetry/api"; -import { version } from "../../package.json"; import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions"; import { logger } from "../utilities/logger.js"; +import { VERSION } from "../consts.js"; function initializeTracing(): NodeTracerProvider | undefined { if ( @@ -30,7 +30,7 @@ function initializeTracing(): NodeTracerProvider | undefined { }).merge( new Resource({ [SEMRESATTRS_SERVICE_NAME]: "trigger.dev cli v3", - [SEMRESATTRS_SERVICE_VERSION]: version, + [SEMRESATTRS_SERVICE_VERSION]: VERSION, }) ); @@ -70,5 +70,5 @@ function initializeTracing(): NodeTracerProvider | undefined { export const provider = initializeTracing(); export function getTracer() { - return trace.getTracer("trigger.dev cli v3", version); + return trace.getTracer("trigger.dev cli v3", VERSION); } diff --git a/packages/cli-v3/src/utilities/build.ts b/packages/cli-v3/src/utilities/build.ts deleted file mode 100644 index 476bd2565..000000000 --- a/packages/cli-v3/src/utilities/build.ts +++ /dev/null @@ -1,258 +0,0 @@ -import type * as esbuild from "esbuild"; -import type { Plugin } from "esbuild"; -import { readFileSync } from "node:fs"; -import { extname, isAbsolute } from "node:path"; -import tsConfigPaths from "tsconfig-paths"; -import { logger } from "./logger"; -import { escapeImportPath } from "./windows"; -import { DependencyMeta } from "./javascriptProject"; - -export function mockServerOnlyPlugin(): Plugin { - return { - name: "trigger-mock-server-only", - setup(build) { - build.onResolve({ filter: /server-only/ }, (args) => { - if (args.path !== "server-only") { - return undefined; - } - - logger.debug(`[trigger-mock-server-only] Bundling ${args.path}`, { - ...args, - }); - - return { - path: args.path, - external: false, - namespace: "server-only-mock", - }; - }); - - build.onLoad({ filter: /server-only/, namespace: "server-only-mock" }, (args) => { - return { - contents: `export default true;`, - loader: "js", - }; - }); - }, - }; -} - -export function bundleTriggerDevCore(buildIdentifier: string, tsconfigPath?: string): Plugin { - return { - name: "trigger-bundle-core", - setup(build) { - build.onResolve({ filter: /.*/ }, (args) => { - if (!args.path.startsWith("@trigger.dev/core/v3")) { - return undefined; - } - - const triggerSdkPath = require.resolve("@trigger.dev/sdk/v3", { paths: [process.cwd()] }); - - logger.debug(`[${buildIdentifier}][trigger-bundle-core] Resolved @trigger.dev/sdk/v3`, { - ...args, - triggerSdkPath, - }); - - const resolvedPath = require.resolve(args.path, { - paths: [triggerSdkPath], - }); - - logger.debug(`[${buildIdentifier}][trigger-bundle-core] Externalizing ${args.path}`, { - ...args, - triggerSdkPath, - resolvedPath, - }); - - return { - path: resolvedPath, - external: false, - }; - }); - }, - }; -} - -export function workerSetupImportConfigPlugin(configPath?: string): Plugin { - return { - name: "trigger-worker-setup", - setup(build) { - if (!configPath) { - return; - } - - build.onLoad({ filter: /worker-setup\.js$/ }, async (args) => { - let workerSetupContents = readFileSync(args.path, "utf-8"); - - workerSetupContents = workerSetupContents.replace( - "__SETUP_IMPORTED_PROJECT_CONFIG__", - `import * as setupImportedConfigExports from "${escapeImportPath( - configPath - )}"; const setupImportedConfig = setupImportedConfigExports.config;` - ); - - logger.debug("Loading worker setup", { - args, - workerSetupContents, - configPath, - }); - - return { - contents: workerSetupContents, - loader: "js", - }; - }); - }, - }; -} - -export function bundleDependenciesPlugin( - buildIdentifier: string, - dependencies: Record, - dependenciesToBundle?: Array, - tsconfigPath?: string -): Plugin { - const matchPath = tsconfigPath ? createMatchPath(tsconfigPath) : undefined; - - function resolvePath(id: string) { - if (!matchPath) { - return id; - } - return matchPath(id, undefined, undefined, [".ts", ".tsx", ".js", ".jsx"]) || id; - } - - return { - name: "trigger-bundle-dependencies", - setup(build) { - build.onResolve({ filter: /.*/ }, (args) => { - const resolvedPath = resolvePath(args.path); - - if (!isBareModuleId(resolvedPath)) { - return undefined; // let esbuild handle it - } - - // Skip assets that are treated as files (.css, .svg, .png, etc.). - // Otherwise, esbuild would emit code that would attempt to require() - // or import these files --- which aren't JavaScript! - let loader; - try { - loader = getLoaderForFile(args.path); - } catch (e) { - if (!(e instanceof Error && e.message.startsWith("Cannot get loader for file"))) { - throw e; - } - } - if (loader === "file") { - return undefined; - } - - for (let pattern of dependenciesToBundle ?? []) { - if (typeof pattern === "string" ? args.path === pattern : pattern.test(args.path)) { - return undefined; // let esbuild bundle it - } - } - - if (dependencies[args.path] && !dependencies[args.path]!.external) { - return undefined; // let esbuild bundle it - } - - logger.debug(`[${buildIdentifier}] Externalizing ${args.path}`, { - ...args, - }); - - // Everything else should be external - return { - path: args.path, - external: true, - }; - }); - }, - }; -} - -function isBareModuleId(id: string): boolean { - return !id.startsWith("node:") && !id.startsWith(".") && !isAbsolute(id); -} - -export function createMatchPath(tsconfigPath: string | undefined) { - // There is no tsconfig to match paths against. - if (!tsconfigPath) { - return undefined; - } - - // When passing a absolute path, loadConfig assumes that the path contains - // a tsconfig file. - // Ref.: https://github.com/dividab/tsconfig-paths/blob/v4.0.0/src/__tests__/config-loader.test.ts#L74 - let configLoaderResult = tsConfigPaths.loadConfig(tsconfigPath); - - if (configLoaderResult.resultType === "failed") { - if (configLoaderResult.message === "Missing baseUrl in compilerOptions") { - throw new Error( - `🚨 Oops! No baseUrl found, please set compilerOptions.baseUrl in your tsconfig or jsconfig` - ); - } - return undefined; - } - - return tsConfigPaths.createMatchPath( - configLoaderResult.absoluteBaseUrl, - configLoaderResult.paths, - configLoaderResult.mainFields, - configLoaderResult.addMatchAll - ); -} - -const loaders: { [ext: string]: esbuild.Loader } = { - ".aac": "file", - ".avif": "file", - ".css": "file", - ".csv": "file", - ".eot": "file", - ".fbx": "file", - ".flac": "file", - ".gif": "file", - ".glb": "file", - ".gltf": "file", - ".gql": "text", - ".graphql": "text", - ".hdr": "file", - ".ico": "file", - ".jpeg": "file", - ".jpg": "file", - ".js": "jsx", - ".jsx": "jsx", - ".json": "json", - // We preprocess md and mdx files using @mdx-js/mdx and send through - // the JSX for esbuild to handle - ".md": "jsx", - ".mdx": "jsx", - ".mov": "file", - ".mp3": "file", - ".mp4": "file", - ".node": "copy", - ".ogg": "file", - ".otf": "file", - ".png": "file", - ".psd": "file", - ".sql": "text", - ".svg": "file", - ".ts": "ts", - ".tsx": "tsx", - ".ttf": "file", - ".wasm": "file", - ".wav": "file", - ".webm": "file", - ".webmanifest": "file", - ".webp": "file", - ".woff": "file", - ".woff2": "file", - ".zip": "file", -}; - -export function getLoaderForFile(file: string): esbuild.Loader { - const ext = extname(file); - const loader = loaders[ext]; - - if (loader) return loader; - - throw new Error(`Cannot get loader for file ${file}`); -} diff --git a/packages/cli-v3/src/utilities/configFiles.ts b/packages/cli-v3/src/utilities/configFiles.ts index 744876892..6e230e96f 100644 --- a/packages/cli-v3/src/utilities/configFiles.ts +++ b/packages/cli-v3/src/utilities/configFiles.ts @@ -1,19 +1,14 @@ -import { Config, ResolvedConfig } from "@trigger.dev/core/v3"; import { findUp } from "find-up"; import { mkdirSync, writeFileSync } from "node:fs"; -import path, { join } from "node:path"; -import { pathToFileURL } from "node:url"; +import path from "node:path"; import xdgAppPaths from "xdg-app-paths"; import { z } from "zod"; -import { CLOUD_API_URL, CONFIG_FILES } from "../consts.js"; -import { createTempDir, readJSONFileSync } from "./fileSystem.js"; +import { CONFIG_FILES } from "../consts.js"; +import { readJSONFileSync } from "./fileSystem.js"; import { logger } from "./logger.js"; -import { findTriggerDirectories, resolveTriggerDirectories } from "./taskFiles.js"; -import { build } from "esbuild"; -import { esbuildDecorators } from "@anatine/esbuild-decorators"; function getGlobalConfigFolderPath() { - const configDir = xdgAppPaths("trigger").config(); + const configDir = xdgAppPaths.default("trigger").config(); return configDir; } @@ -111,162 +106,3 @@ async function findFilePath(dir: string, fileName: string): Promise { - const absoluteDir = path.resolve(options?.cwd || process.cwd(), dir); - - const configPath = await getConfigPath(dir, options?.configFile); - - if (!configPath) { - if (options?.projectRef) { - const rawConfig = await normalizeConfig({ project: options.projectRef }); - const config = Config.parse(rawConfig); - - return { - status: "in-memory", - config: await resolveConfig(absoluteDir, config), - }; - } else { - throw new Error(`Config file not found in ${absoluteDir} or any parent directory.`); - } - } - - const tempDir = await createTempDir(); - - const builtConfigFilePath = join(tempDir, "config.js"); - const builtConfigFileHref = pathToFileURL(builtConfigFilePath).href; - - logger.debug("Building config file", { - configPath, - builtConfigFileHref, - builtConfigFilePath, - }); - - // We need to build the path to the config file, and then import it? - await build({ - entryPoints: [configPath], - bundle: true, - metafile: true, - minify: false, - write: true, - format: "cjs", - platform: "node", - target: ["es2020", "node18"], - outfile: builtConfigFilePath, - logLevel: "silent", - plugins: [ - esbuildDecorators({ - cwd: absoluteDir, - tsx: false, - force: false, - }), - { - name: "native-node-modules", - setup(build) { - const opts = build.initialOptions; - opts.loader = opts.loader || {}; - opts.loader[".node"] = "copy"; - }, - }, - ], - }); - - try { - // import the config file - const userConfigModule = await import(builtConfigFileHref); - - // The --project-ref CLI arg will always override the project specified in the config file - const rawConfig = await normalizeConfig( - userConfigModule?.config, - options?.projectRef ? { project: options?.projectRef } : undefined - ); - - const config = Config.parse(rawConfig); - - return { - status: "file", - config: await resolveConfig(absoluteDir, config), - path: configPath, - module: userConfigModule, - }; - } catch (error) { - return { - status: "error", - error, - }; - } -} - -export async function resolveConfig(path: string, config: Config): Promise { - if (!config.triggerDirectories) { - config.triggerDirectories = await findTriggerDirectories(path); - // TODO trigger-dir-missing: throw error if no trigger directory is found - } - - config.triggerDirectories = resolveTriggerDirectories(path, config.triggerDirectories); - // TODO trigger-dir-not-found: throw error if trigger directories do not exist - - logger.debug("Resolved trigger directories", { triggerDirectories: config.triggerDirectories }); - - if (!config.triggerUrl) { - config.triggerUrl = CLOUD_API_URL; - } - - if (!config.projectDir) { - config.projectDir = path; - } - - if (!config.tsconfigPath) { - config.tsconfigPath = await findFilePath(path, "tsconfig.json"); - } - - if (!config.additionalFiles) { - config.additionalFiles = []; - } - - if (config.extraCACerts) { - config.additionalFiles.push(config.extraCACerts); - config.extraCACerts = config.extraCACerts.replace(/^(\.[.]?\/)+/, ""); - } - - return config as ResolvedConfig; -} - -export async function normalizeConfig(config: any, overrides?: Record): Promise { - let normalized = config; - - if (typeof config === "function") { - normalized = await config(); - } - - normalized = { ...normalized, ...overrides }; - - return normalized; -} diff --git a/packages/cli-v3/src/utilities/createFileFromTemplate.ts b/packages/cli-v3/src/utilities/createFileFromTemplate.ts index 424466019..e2b263681 100644 --- a/packages/cli-v3/src/utilities/createFileFromTemplate.ts +++ b/packages/cli-v3/src/utilities/createFileFromTemplate.ts @@ -1,5 +1,5 @@ import fs from "fs/promises"; -import { pathExists, readFile } from "./fileSystem"; +import { pathExists, readFile } from "./fileSystem.js"; import path from "path"; type Result = diff --git a/packages/cli-v3/src/utilities/deployErrors.ts b/packages/cli-v3/src/utilities/deployErrors.ts index eaa01aad7..1fdb1e6f3 100644 --- a/packages/cli-v3/src/utilities/deployErrors.ts +++ b/packages/cli-v3/src/utilities/deployErrors.ts @@ -1,11 +1,17 @@ import chalk from "chalk"; import { relative } from "node:path"; -import { chalkError, chalkPurple, chalkGrey, chalkGreen, chalkWarning, cliLink } from "./cliOutput"; -import { logger } from "./logger"; -import { ReadConfigResult } from "./configFiles"; +import { + chalkError, + chalkPurple, + chalkGrey, + chalkGreen, + chalkWarning, + cliLink, +} from "./cliOutput.js"; +import { logger } from "./logger.js"; import { z } from "zod"; import { groupTaskMetadataIssuesByTask } from "@trigger.dev/core/v3"; -import { docs } from "./links"; +import { docs } from "./links.js"; export type ESMRequireError = { type: "esm-require-error"; @@ -41,6 +47,8 @@ export function parseBuildErrorStack(error: unknown): BuildError | undefined { return error.message; } } + + return; } function getPackageNameFromEsmRequireError(stack: string): string | undefined { @@ -75,7 +83,7 @@ function getPackageNameFromEsmRequireError(stack: string): string | undefined { return match[1]; } -export function logESMRequireError(parsedError: ESMRequireError, resolvedConfig: ReadConfigResult) { +export function logESMRequireError(parsedError: ESMRequireError) { logger.log( `\n${chalkError("X Error:")} The ${chalkPurple( parsedError.moduleName @@ -89,28 +97,13 @@ export function logESMRequireError(parsedError: ESMRequireError, resolvedConfig: )}` ); - if (resolvedConfig.status === "file") { - const relativePath = relative(resolvedConfig.config.projectDir, resolvedConfig.path).replace( - /\\/g, - "/" - ); - - logger.log( - `${chalkGrey("○")} ${chalk.underline("Or")} add ${chalkPurple( - parsedError.moduleName - )} to the ${chalkGreen("dependenciesToBundle")} array in your config file ${chalkGrey( - `(${relativePath})` - )}. This will bundle the module with your code.\n` - ); - } else { - logger.log( - `${chalkGrey("○")} ${chalk.underline("Or")} add ${chalkPurple( - parsedError.moduleName - )} to the ${chalkGreen("dependenciesToBundle")} array in your config file ${chalkGrey( - "(you'll need to create one)" - )}. This will bundle the module with your code.\n` - ); - } + logger.log( + `${chalkGrey("○")} ${chalk.underline("Or")} add ${chalkPurple( + parsedError.moduleName + )} to the ${chalkGreen("dependenciesToBundle")} array in your config file ${chalkGrey( + "(you'll need to create one)" + )}. This will bundle the module with your code.\n` + ); logger.log( `${chalkGrey("○")} For more info see the ${cliLink("relevant docs", docs.config.esm)}.\n` diff --git a/packages/cli-v3/src/utilities/getApiKeyType.test.ts b/packages/cli-v3/src/utilities/getApiKeyType.test.ts deleted file mode 100644 index e8be117e5..000000000 --- a/packages/cli-v3/src/utilities/getApiKeyType.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { checkApiKeyIsDevServer } from "./getApiKeyType.js"; - -describe("Test API keys", () => { - test("dev server succeeds", async () => { - const result = checkApiKeyIsDevServer("tr_dev_12345"); - expect(result.success).toEqual(true); - }); - - test("dev public fails", async () => { - const result = checkApiKeyIsDevServer("pk_dev_12345"); - expect(result.success).toEqual(false); - if (result.success) return; - expect(result.type?.environment).toEqual("dev"); - expect(result.type?.type).toEqual("public"); - }); - - test("prod server fails", async () => { - const result = checkApiKeyIsDevServer("tr_prod_12345"); - expect(result.success).toEqual(false); - if (result.success) return; - expect(result.type?.environment).toEqual("prod"); - expect(result.type?.type).toEqual("server"); - }); - - test("prod public fails", async () => { - const result = checkApiKeyIsDevServer("pk_prod_12345"); - expect(result.success).toEqual(false); - if (result.success) return; - expect(result.type?.environment).toEqual("prod"); - expect(result.type?.type).toEqual("public"); - }); -}); diff --git a/packages/cli-v3/src/utilities/getUserPackageManager.ts b/packages/cli-v3/src/utilities/getUserPackageManager.ts index d50dcf5b8..5d88ae316 100644 --- a/packages/cli-v3/src/utilities/getUserPackageManager.ts +++ b/packages/cli-v3/src/utilities/getUserPackageManager.ts @@ -1,6 +1,6 @@ import { findUp } from "find-up"; import { basename } from "path"; -import { logger } from "./logger"; +import { logger } from "./logger.js"; export type PackageManager = "npm" | "pnpm" | "yarn"; export const LOCKFILES = { diff --git a/packages/cli-v3/src/utilities/getVersion.ts b/packages/cli-v3/src/utilities/getVersion.ts deleted file mode 100644 index 46d91f05a..000000000 --- a/packages/cli-v3/src/utilities/getVersion.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { type PackageJson } from "type-fest"; -import path from "path"; -import { PKG_ROOT } from "../consts.js"; -import { readJSONFileSync } from "./fileSystem.js"; - -export function getVersion() { - const packageJsonPath = path.join(PKG_ROOT, "package.json"); - - const packageJsonContent = readJSONFileSync(packageJsonPath) as PackageJson; - - return packageJsonContent.version ?? "1.0.0"; -} diff --git a/packages/cli-v3/src/utilities/importMagicast-cjs.cts b/packages/cli-v3/src/utilities/importMagicast-cjs.cts new file mode 100644 index 000000000..491d790ec --- /dev/null +++ b/packages/cli-v3/src/utilities/importMagicast-cjs.cts @@ -0,0 +1,7 @@ +// @ts-ignore +const { loadFile, generateCode } = require("magicast"); + +// @ts-ignore +module.exports.loadFile = loadFile; +// @ts-ignore +module.exports.generateCode = generateCode; diff --git a/packages/cli-v3/src/utilities/importMagicast.ts b/packages/cli-v3/src/utilities/importMagicast.ts new file mode 100644 index 000000000..0ad739266 --- /dev/null +++ b/packages/cli-v3/src/utilities/importMagicast.ts @@ -0,0 +1,5 @@ +// @ts-ignore +import { loadFile, generateCode } from "magicast"; + +// @ts-ignore +export { loadFile, generateCode }; diff --git a/packages/cli-v3/src/utilities/initialBanner.ts b/packages/cli-v3/src/utilities/initialBanner.ts index a5db3419a..9b1eee35e 100644 --- a/packages/cli-v3/src/utilities/initialBanner.ts +++ b/packages/cli-v3/src/utilities/initialBanner.ts @@ -1,14 +1,13 @@ import chalk from "chalk"; import type { Result } from "update-check"; import checkForUpdate from "update-check"; -import pkg from "../../package.json"; import { chalkGrey, chalkRun, chalkTask, chalkWorker, green, logo } from "./cliOutput.js"; -import { getVersion } from "./getVersion.js"; import { logger } from "./logger.js"; import { spinner } from "./windows.js"; +import { readPackageJson } from "./packageJson.js"; export async function printInitialBanner(performUpdateCheck = true) { - const cliVersion = getVersion(); + const cliVersion = await getVersion(); const text = `\n${logo()} ${chalkGrey(`(${cliVersion})`)}\n`; logger.info(text); @@ -38,7 +37,7 @@ After installation, run Trigger.dev with \`npx trigger.dev\`.` } export async function printStandloneInitialBanner(performUpdateCheck = true) { - const cliVersion = getVersion(); + const cliVersion = await getVersion(); if (performUpdateCheck) { const maybeNewVersion = await updateCheck(); @@ -72,9 +71,10 @@ 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(pkg, { - distTag: pkg.version.startsWith("3.0.0-beta") ? "beta" : "latest", + update = await checkForUpdate.default(pkg, { + distTag: pkg.version?.startsWith("3.0.0-beta") ? "beta" : "latest", }); } catch (err) { // ignore error @@ -87,3 +87,8 @@ let updateCheckPromise: Promise; export function updateCheck(): Promise { return (updateCheckPromise ??= doUpdateCheck()); } + +async function getVersion() { + const packageJson = await readPackageJson(); + return packageJson.version ?? "unknown"; +} diff --git a/packages/cli-v3/src/utilities/installPackages.ts b/packages/cli-v3/src/utilities/installPackages.ts deleted file mode 100644 index 4dd1e933e..000000000 --- a/packages/cli-v3/src/utilities/installPackages.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { execa } from "execa"; -import { join } from "node:path"; -import { readJSONFile, writeJSONFile } from "./fileSystem"; -import { logger } from "./logger"; - -export type InstallPackagesOptions = { cwd?: string }; - -export async function installPackages( - packages: Record, - options?: InstallPackagesOptions -) { - const cwd = options?.cwd ?? process.cwd(); - - logger.debug("Installing packages", { packages }); - - await setPackageJsonDeps(join(cwd, "package.json"), packages); - - await execa( - "npm", - ["install", "--install-strategy", "nested", "--ignore-scripts", "--no-audit", "--no-fund"], - { - cwd, - stderr: "pipe", - } - ); -} - -// Expects path to be in the format: -// - source-map-support/register.js -// - @opentelemetry/api -// - zod -// -// With the result being: -// - source-map-support -// - @opentelemetry/api -// - zod -export function detectPackageNameFromImportPath(path: string): string { - if (path.startsWith("@")) { - return path.split("/").slice(0, 2).join("/"); - } else { - return path.split("/")[0] as string; - } -} - -/** - * Removes the workspace prefix from a version string. - * @param version - The version string to strip the workspace prefix from. - * @returns The version string without the workspace prefix. - * @example - * stripWorkspaceFromVersion("workspace:1.0.0") // "1.0.0" - * stripWorkspaceFromVersion("1.0.0") // "1.0.0" - */ -export function stripWorkspaceFromVersion(version: string) { - return version.replace(/^workspace:/, ""); -} - -export function parsePackageName(packageSpecifier: string): { name: string; version?: string } { - let name: string | undefined; - let version: string | undefined; - - // Check if the package is scoped - if (packageSpecifier.startsWith("@")) { - const atIndex = packageSpecifier.indexOf("@", 1); - // If a version is included - if (atIndex !== -1) { - name = packageSpecifier.slice(0, atIndex); - version = packageSpecifier.slice(atIndex + 1); - } else { - name = packageSpecifier; - } - } else { - const [packageName, packageVersion] = packageSpecifier.split("@"); - - if (typeof packageName === "string") { - name = packageName; - } - - version = packageVersion; - } - - if (!name) { - return { name: packageSpecifier }; - } - - return { name, version }; -} - -async function setPackageJsonDeps(path: string, deps: Record) { - try { - const existingPackageJson = await readJSONFile(path); - - const newPackageJson = { - ...existingPackageJson, - dependencies: { - ...deps, - }, - }; - - await writeJSONFile(path, newPackageJson); - } catch (error) { - const defaultPackageJson = { - name: "temp", - version: "1.0.0", - description: "", - dependencies: deps, - }; - - await writeJSONFile(path, defaultPackageJson); - } -} diff --git a/packages/cli-v3/src/utilities/javascriptProject.ts b/packages/cli-v3/src/utilities/javascriptProject.ts index 1bbc2e60b..2b0fc1365 100644 --- a/packages/cli-v3/src/utilities/javascriptProject.ts +++ b/packages/cli-v3/src/utilities/javascriptProject.ts @@ -1,12 +1,12 @@ import { $, ExecaError } from "execa"; import { join } from "node:path"; -import { readJSONFileSync } from "./fileSystem"; -import { logger } from "./logger"; -import { PackageManager, getUserPackageManager } from "./getUserPackageManager"; +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"; +import { assertExhaustive } from "./assertExhaustive.js"; import { builtinModules } from "node:module"; -import { tracer } from "../cli/common"; +import { tracer } from "../cli/common.js"; import { recordSpanException } from "@trigger.dev/core/v3/otel"; import { flattenAttributes } from "@trigger.dev/core/v3"; @@ -233,6 +233,8 @@ export class JavascriptProject { error, }); } + + return; } async #getCommand(): Promise { @@ -328,6 +330,8 @@ class PNPMCommands implements PackageManagerCommands { return dependency.version; } } + + return; } async resolveDependencyVersions( @@ -524,6 +528,8 @@ class NPMCommands implements PackageManagerCommands { } } } + + return; } #flattenDependenciesMeta( diff --git a/packages/cli-v3/src/utilities/linux.ts b/packages/cli-v3/src/utilities/linux.ts index 2d11285aa..0a35db401 100644 --- a/packages/cli-v3/src/utilities/linux.ts +++ b/packages/cli-v3/src/utilities/linux.ts @@ -1,5 +1,5 @@ import { spawn } from "child_process"; -import { logger } from "./logger"; +import { logger } from "./logger.js"; export const isLinuxServer = async () => { if (process.platform !== "linux") { diff --git a/packages/cli-v3/src/utilities/packageJson.ts b/packages/cli-v3/src/utilities/packageJson.ts new file mode 100644 index 000000000..e01561080 --- /dev/null +++ b/packages/cli-v3/src/utilities/packageJson.ts @@ -0,0 +1,6 @@ +import { readPackageJSON } from "pkg-types"; +import { packageDir } from "../packageDir.js"; + +export async function readPackageJson() { + return await readPackageJSON(packageDir); +} diff --git a/packages/cli-v3/src/utilities/parseNameAndPath.ts b/packages/cli-v3/src/utilities/parseNameAndPath.ts index e6016c0d9..4cf7d52e8 100644 --- a/packages/cli-v3/src/utilities/parseNameAndPath.ts +++ b/packages/cli-v3/src/utilities/parseNameAndPath.ts @@ -1,11 +1,11 @@ -import pathModule from "path"; +import pathModule from "node:path"; // Takes a relative path (like .) and resolves it to a full path (like /Users/username/Projects/my-triggers) export const resolvePath = (input: string) => { return pathModule.resolve(process.cwd(), input); }; -// Takes an absolute path and derives the relative path from the current working directory +// Takes an absolute path and derives the relative path from the current working directory export const relativePath = (input: string) => { return pathModule.relative(process.cwd(), input); }; diff --git a/packages/cli-v3/src/utilities/resolveEnvVars.ts b/packages/cli-v3/src/utilities/resolveEnvVars.ts index a09052a55..c0be51a1e 100644 --- a/packages/cli-v3/src/utilities/resolveEnvVars.ts +++ b/packages/cli-v3/src/utilities/resolveEnvVars.ts @@ -1,4 +1,4 @@ -import { logger } from "./logger"; +import { logger } from "./logger.js"; export async function callResolveEnvVars( configModule: any, @@ -59,4 +59,6 @@ export async function callResolveEnvVars( logger.error(error); } } + + return; } diff --git a/packages/cli-v3/src/utilities/runtimeCheck.ts b/packages/cli-v3/src/utilities/runtimeCheck.ts index 751481f3d..7e07955d6 100644 --- a/packages/cli-v3/src/utilities/runtimeCheck.ts +++ b/packages/cli-v3/src/utilities/runtimeCheck.ts @@ -1,4 +1,4 @@ -import { logger } from "./logger"; +import { logger } from "./logger.js"; /** * This function is used by the dev CLI to make sure that the runtime is compatible diff --git a/packages/cli-v3/src/utilities/taskFiles.ts b/packages/cli-v3/src/utilities/taskFiles.ts index 55e4ec510..af409866d 100644 --- a/packages/cli-v3/src/utilities/taskFiles.ts +++ b/packages/cli-v3/src/utilities/taskFiles.ts @@ -1,7 +1,7 @@ import { ResolvedConfig } from "@trigger.dev/core/v3"; import fs from "node:fs"; import { join, relative, resolve } from "node:path"; -import { TaskFile } from "../types"; +import { TaskFile } from "../types.js"; export function createTaskFileImports(taskFiles: TaskFile[]) { return taskFiles diff --git a/packages/cli-v3/src/workers/common/errors.ts b/packages/cli-v3/src/workers/common/errors.ts deleted file mode 100644 index 265caea88..000000000 --- a/packages/cli-v3/src/workers/common/errors.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { z } from "zod"; - -export class UncaughtExceptionError extends Error { - constructor( - public readonly originalError: { name: string; message: string; stack?: string }, - public readonly origin: "uncaughtException" | "unhandledRejection" - ) { - super(`Uncaught exception: ${originalError.message}`); - - this.name = "UncaughtExceptionError"; - } -} - -export class TaskMetadataParseError extends Error { - constructor( - public readonly zodIssues: z.ZodIssue[], - public readonly tasks: any - ) { - super(`Failed to parse task metadata`); - - this.name = "TaskMetadataParseError"; - } -} - -export class UnexpectedExitError extends Error { - constructor( - public code: number, - public signal: NodeJS.Signals | null, - public stderr: string | undefined - ) { - super(`Unexpected exit with code ${code}`); - - this.name = "UnexpectedExitError"; - } -} - -export class CleanupProcessError extends Error { - constructor() { - super("Cancelled"); - - this.name = "CleanupProcessError"; - } -} - -export class CancelledProcessError extends Error { - constructor() { - super("Cancelled"); - - this.name = "CancelledProcessError"; - } -} - -export class SigKillTimeoutProcessError extends Error { - constructor() { - super("Process kill timeout"); - - this.name = "SigKillTimeoutProcessError"; - } -} - -export class GracefulExitTimeoutError extends Error { - constructor() { - super("Graceful exit timeout"); - - this.name = "GracefulExitTimeoutError"; - } -} - -export function getFriendlyErrorMessage( - code: number, - signal: NodeJS.Signals | null, - stderr: string | undefined, - dockerMode = true -) { - const message = (text: string) => { - if (signal) { - return `[${signal}] ${text}`; - } else { - return text; - } - }; - - if (code === 137) { - if (dockerMode) { - return message( - "Process ran out of memory! Try choosing a machine preset with more memory for this task." - ); - } else { - // Note: containerState reason and message should be checked to clarify the error - return message( - "Process most likely ran out of memory, but we can't be certain. Try choosing a machine preset with more memory for this task." - ); - } - } - - if (stderr?.includes("OOMErrorHandler")) { - return message( - "Process ran out of memory! Try choosing a machine preset with more memory for this task." - ); - } - - return message(`Process exited with code ${code}.`); -} diff --git a/packages/cli-v3/src/workers/dev/backgroundWorker.ts b/packages/cli-v3/src/workers/dev/backgroundWorker.ts deleted file mode 100644 index 2773831bd..000000000 --- a/packages/cli-v3/src/workers/dev/backgroundWorker.ts +++ /dev/null @@ -1,1141 +0,0 @@ -import { - BackgroundWorkerProperties, - BackgroundWorkerServerMessages, - CreateBackgroundWorkerResponse, - ResolvedConfig, - SemanticInternalAttributes, - TaskMetadataWithFilePath, - TaskRunBuiltInError, - TaskRunError, - TaskRunErrorCodes, - TaskRunExecution, - TaskRunExecutionLazyAttemptPayload, - TaskRunExecutionPayload, - TaskRunExecutionResult, - TaskRunFailedExecutionResult, - childToWorkerMessages, - correctErrorStackTrace, - formatDurationMilliseconds, - workerToChildMessages, -} from "@trigger.dev/core/v3"; -import { ZodMessageHandler, ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler"; -import dotenv from "dotenv"; -import { Evt } from "evt"; -import { ChildProcess, fork } from "node:child_process"; -import { dirname, resolve } from "node:path"; -import { - chalkError, - chalkGrey, - chalkLink, - chalkRun, - chalkSuccess, - chalkTask, - chalkWarning, - chalkWorker, - cliLink, - prettyPrintDate, -} from "../../utilities/cliOutput.js"; -import { safeDeleteFileSync } from "../../utilities/fileSystem.js"; -import { installPackages } from "../../utilities/installPackages.js"; -import { logger } from "../../utilities/logger.js"; -import { - CancelledProcessError, - CleanupProcessError, - SigKillTimeoutProcessError, - TaskMetadataParseError, - UncaughtExceptionError, - UnexpectedExitError, - getFriendlyErrorMessage, -} from "../common/errors.js"; -import { CliApiClient } from "../../apiClient.js"; - -export type CurrentWorkers = BackgroundWorkerCoordinator["currentWorkers"]; -export class BackgroundWorkerCoordinator { - public onTaskCompleted: Evt<{ - backgroundWorkerId: string; - completion: TaskRunExecutionResult; - worker: BackgroundWorker; - execution: TaskRunExecution; - }> = new Evt(); - public onTaskFailedToRun: Evt<{ - backgroundWorkerId: string; - worker: BackgroundWorker; - completion: TaskRunFailedExecutionResult; - }> = new Evt(); - public onWorkerRegistered: Evt<{ - worker: BackgroundWorker; - id: string; - record: CreateBackgroundWorkerResponse; - }> = new Evt(); - - /** - * @deprecated use onWorkerTaskRunHeartbeat instead - */ - public onWorkerTaskHeartbeat: Evt<{ - id: string; - backgroundWorkerId: string; - worker: BackgroundWorker; - }> = new Evt(); - public onWorkerTaskRunHeartbeat: Evt<{ - id: string; - backgroundWorkerId: string; - worker: BackgroundWorker; - }> = new Evt(); - public onWorkerDeprecated: Evt<{ worker: BackgroundWorker; id: string }> = new Evt(); - private _backgroundWorkers: Map = new Map(); - private _records: Map = new Map(); - private _deprecatedWorkers: Set = new Set(); - - constructor(private baseURL: string) { - this.onTaskCompleted.attach(async ({ completion }) => { - if (!completion.ok && typeof completion.retry !== "undefined") { - return; - } - - await this.#notifyWorkersOfTaskCompletion(completion); - }); - - this.onTaskFailedToRun.attach(async ({ completion }) => { - await this.#notifyWorkersOfTaskCompletion(completion); - }); - } - - async #notifyWorkersOfTaskCompletion(completion: TaskRunExecutionResult) { - for (const worker of this._backgroundWorkers.values()) { - await worker.taskRunCompletedNotification(completion); - } - } - - get currentWorkers() { - return Array.from(this._backgroundWorkers.entries()).map(([id, worker]) => ({ - id, - worker, - record: this._records.get(id)!, - isDeprecated: this._deprecatedWorkers.has(id), - })); - } - - async registerWorker(record: CreateBackgroundWorkerResponse, worker: BackgroundWorker) { - for (const [workerId, existingWorker] of this._backgroundWorkers.entries()) { - if (workerId === record.id) { - continue; - } - - this._deprecatedWorkers.add(workerId); - this.onWorkerDeprecated.post({ worker: existingWorker, id: workerId }); - } - - this._backgroundWorkers.set(record.id, worker); - this._records.set(record.id, record); - this.onWorkerRegistered.post({ worker, id: record.id, record }); - - worker.onTaskHeartbeat.attach((id) => { - this.onWorkerTaskHeartbeat.post({ id, backgroundWorkerId: record.id, worker }); - }); - - worker.onTaskRunHeartbeat.attach((id) => { - this.onWorkerTaskRunHeartbeat.post({ id, backgroundWorkerId: record.id, worker }); - }); - } - - close() { - for (const worker of this._backgroundWorkers.values()) { - worker.close(); - } - - this._backgroundWorkers.clear(); - this._records.clear(); - } - - async handleMessage(id: string, message: BackgroundWorkerServerMessages) { - logger.debug(`Received message from worker ${id}`, JSON.stringify({ workerMessage: message })); - - switch (message.type) { - case "EXECUTE_RUNS": { - await Promise.all(message.payloads.map((payload) => this.#executeTaskRun(id, payload))); - break; - } - case "CANCEL_ATTEMPT": { - // Need to cancel the attempt somehow here - const worker = this._backgroundWorkers.get(id); - - if (!worker) { - logger.error(`Could not find worker ${id}`); - return; - } - - await worker.cancelRun(message.taskRunId); - break; - } - case "EXECUTE_RUN_LAZY_ATTEMPT": { - await this.#executeTaskRunLazyAttempt(id, message.payload); - } - } - } - - async #executeTaskRunLazyAttempt(id: string, payload: TaskRunExecutionLazyAttemptPayload) { - const worker = this._backgroundWorkers.get(id); - - if (!worker) { - logger.error(`Could not find worker ${id}`); - return; - } - - const record = this._records.get(id); - - if (!record) { - logger.error(`Could not find worker record ${id}`); - return; - } - - try { - const { completion, execution } = await worker.executeTaskRunLazyAttempt( - payload, - this.baseURL - ); - - this.onTaskCompleted.post({ - completion, - execution, - worker, - backgroundWorkerId: id, - }); - } catch (error) { - this.onTaskFailedToRun.post({ - backgroundWorkerId: id, - worker, - completion: { - ok: false, - id: payload.runId, - retry: undefined, - error: - error instanceof Error - ? { - type: "BUILT_IN_ERROR", - name: error.name, - message: error.message, - stackTrace: error.stack ?? "", - } - : { - type: "BUILT_IN_ERROR", - name: "UnknownError", - message: String(error), - stackTrace: "", - }, - }, - }); - } - } - - async #executeTaskRun(id: string, payload: TaskRunExecutionPayload) { - const worker = this._backgroundWorkers.get(id); - - if (!worker) { - logger.error(`Could not find worker ${id}`); - return; - } - - const record = this._records.get(id); - - if (!record) { - logger.error(`Could not find worker record ${id}`); - return; - } - - const completion = await worker.executeTaskRun(payload, this.baseURL); - - this.onTaskCompleted.post({ - completion, - execution: payload.execution, - worker, - backgroundWorkerId: id, - }); - } -} - -export type BackgroundWorkerParams = { - env: Record; - dependencies?: Record; - projectConfig: ResolvedConfig; - debuggerOn: boolean; - debugOtel?: boolean; - resolveEnvVariables?: ( - env: Record, - worker: BackgroundWorker - ) => Promise | undefined>; -}; - -export class BackgroundWorker { - private _initialized: boolean = false; - private _handler = new ZodMessageHandler({ - schema: childToWorkerMessages, - }); - - /** - * @deprecated use onTaskRunHeartbeat instead - */ - public onTaskHeartbeat: Evt = new Evt(); - public onTaskRunHeartbeat: Evt = new Evt(); - private _onClose: Evt = new Evt(); - - public tasks: Array = []; - public metadata: BackgroundWorkerProperties | undefined; - public stderr: Array = []; - - _taskRunProcesses: Map = new Map(); - private _taskRunProcessesBeingKilled: Set = new Set(); - - private _closed: boolean = false; - - private _fullEnv: Record = {}; - - constructor( - public path: string, - public params: BackgroundWorkerParams, - private apiClient: CliApiClient - ) {} - - close() { - if (this._closed) { - return; - } - - this._closed = true; - - this.onTaskHeartbeat.detach(); - this.onTaskRunHeartbeat.detach(); - - // We need to close all the task run processes - for (const taskRunProcess of this._taskRunProcesses.values()) { - taskRunProcess.cleanup(true); - } - - // Delete worker files - this._onClose.post(); - - safeDeleteFileSync(this.path); - safeDeleteFileSync(`${this.path}.map`); - } - - get inProgressRuns(): Array { - return Array.from(this._taskRunProcesses.keys()); - } - - async initialize() { - if (this._initialized) { - throw new Error("Worker already initialized"); - } - - // Install the dependencies in dirname(this.path) using npm and child_process - if (this.params.dependencies) { - await installPackages(this.params.dependencies, { cwd: dirname(this.path) }); - } - - let resolved = false; - - const cwd = dirname(this.path); - - this._fullEnv = { - ...this.params.env, - ...this.#readEnvVars(), - ...(this.params.debugOtel ? { OTEL_LOG_LEVEL: "debug" } : {}), - }; - - let resolvedEnvVars: Record = {}; - - if (this.params.resolveEnvVariables) { - const resolvedEnv = await this.params.resolveEnvVariables(this._fullEnv, this); - - if (resolvedEnv) { - resolvedEnvVars = resolvedEnv; - } - } - - this._fullEnv = { - ...this._fullEnv, - ...resolvedEnvVars, - }; - - logger.debug("Initializing worker", { path: this.path, cwd, fullEnv: this._fullEnv }); - - this.tasks = await new Promise>((resolve, reject) => { - const child = fork(this.path, { - stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"], - cwd, - env: this._fullEnv, - }); - - // Set a timeout to kill the child process if it doesn't respond - const timeout = setTimeout(() => { - if (resolved) { - return; - } - - resolved = true; - child.kill(); - reject(new Error("Worker timed out")); - }, 20_000); - - child.on("message", async (msg: any) => { - const message = this._handler.parseMessage(msg); - - if (!message.success) { - clearTimeout(timeout); - resolved = true; - reject(new Error(`Failed to parse message: ${message.error}`)); - child.kill(); - return; - } - - if (message.data.type === "TASKS_READY" && !resolved) { - clearTimeout(timeout); - resolved = true; - resolve(message.data.payload.tasks); - child.kill(); - } else if (message.data.type === "UNCAUGHT_EXCEPTION") { - clearTimeout(timeout); - resolved = true; - reject( - new UncaughtExceptionError(message.data.payload.error, message.data.payload.origin) - ); - child.kill(); - } else if (message.data.type === "TASKS_FAILED_TO_PARSE") { - clearTimeout(timeout); - resolved = true; - reject( - new TaskMetadataParseError(message.data.payload.zodIssues, message.data.payload.tasks) - ); - child.kill(); - } - }); - - child.on("exit", (code) => { - if (!resolved) { - clearTimeout(timeout); - resolved = true; - reject(new Error(`Worker exited with code ${code}`)); - } - }); - - child.stdout?.on("data", (data) => { - logger.log(data.toString()); - }); - - child.stderr?.on("data", (data) => { - this.stderr.push(data.toString()); - }); - }); - - this._initialized = true; - } - - // We need to notify all the task run processes that a task run has completed, - // in case they are waiting for it through triggerAndWait - async taskRunCompletedNotification(completion: TaskRunExecutionResult) { - for (const taskRunProcess of this._taskRunProcesses.values()) { - taskRunProcess.taskRunCompletedNotification(completion); - } - } - - #prefixedMessage(payload: TaskRunExecutionPayload, message: string = "") { - return `[${payload.execution.run.id}.${payload.execution.attempt.number}] ${message}`; - } - - async #getFreshTaskRunProcess( - payload: TaskRunExecutionPayload, - messageId?: string - ): Promise { - logger.debug(this.#prefixedMessage(payload, "getFreshTaskRunProcess()")); - - if (!this.metadata) { - throw new Error("Worker not registered"); - } - - this._closed = false; - - logger.debug(this.#prefixedMessage(payload, "killing current task run process before attempt")); - - await this.#killCurrentTaskRunProcessBeforeAttempt(payload.execution.run.id); - - logger.debug(this.#prefixedMessage(payload, "creating new task run process")); - - const taskRunProcess = new TaskRunProcess( - payload.execution.run.id, - payload.execution.run.isTest, - this.path, - { - ...this._fullEnv, - ...(payload.environment ?? {}), - ...this.#readEnvVars(), - }, - this.metadata, - this.params, - messageId - ); - - taskRunProcess.onExit.attach(({ pid }) => { - logger.debug(this.#prefixedMessage(payload, "onExit()"), { pid }); - - const taskRunProcess = this._taskRunProcesses.get(payload.execution.run.id); - - // Only delete the task run process if the pid matches - if (taskRunProcess?.pid === pid) { - this._taskRunProcesses.delete(payload.execution.run.id); - } - - if (pid) { - this._taskRunProcessesBeingKilled.delete(pid); - } - }); - - taskRunProcess.onIsBeingKilled.attach((pid) => { - if (pid) { - this._taskRunProcessesBeingKilled.add(pid); - } - }); - - taskRunProcess.onTaskHeartbeat.attach((id) => { - this.onTaskHeartbeat.post(id); - }); - - taskRunProcess.onTaskRunHeartbeat.attach((id) => { - this.onTaskRunHeartbeat.post(id); - }); - - await taskRunProcess.initialize(); - - this._taskRunProcesses.set(payload.execution.run.id, taskRunProcess); - - return taskRunProcess; - } - - async #killCurrentTaskRunProcessBeforeAttempt(runId: string) { - const taskRunProcess = this._taskRunProcesses.get(runId); - - if (!taskRunProcess) { - logger.debug(`[${runId}] no current task process to kill`); - return; - } - - logger.debug(`[${runId}] killing current task process`, { - pid: taskRunProcess.pid, - }); - - if (taskRunProcess.isBeingKilled) { - if (this._taskRunProcessesBeingKilled.size > 1) { - await this.#tryGracefulExit(taskRunProcess); - } else { - // If there's only one or none being killed, don't do anything so we can create a fresh one in parallel - } - } else { - // It's not being killed, so kill it - if (this._taskRunProcessesBeingKilled.size > 0) { - await this.#tryGracefulExit(taskRunProcess); - } else { - // There's none being killed yet, so we can kill it without waiting. We still set a timeout to kill it forcefully just in case it sticks around. - taskRunProcess.kill("SIGTERM", 5_000).catch(() => {}); - } - } - } - - async #tryGracefulExit( - taskRunProcess: TaskRunProcess, - kill = false, - initialSignal: number | NodeJS.Signals = "SIGTERM" - ) { - try { - const initialExit = taskRunProcess.onExit.waitFor(5_000); - - if (kill) { - taskRunProcess.kill(initialSignal); - } - - await initialExit; - } catch (error) { - logger.error("TaskRunProcess graceful kill timeout exceeded", error); - - this.#tryForcefulExit(taskRunProcess); - } - } - - async #tryForcefulExit(taskRunProcess: TaskRunProcess) { - try { - const forcedKill = taskRunProcess.onExit.waitFor(5_000); - taskRunProcess.kill("SIGKILL"); - await forcedKill; - } catch (error) { - logger.error("TaskRunProcess forced kill timeout exceeded", error); - throw new SigKillTimeoutProcessError(); - } - } - - async cancelRun(taskRunId: string) { - const taskRunProcess = this._taskRunProcesses.get(taskRunId); - - if (!taskRunProcess) { - return; - } - - await taskRunProcess.cancel(); - } - - async executeTaskRunLazyAttempt(payload: TaskRunExecutionLazyAttemptPayload, baseURL: string) { - const attemptResponse = await this.apiClient.createTaskRunAttempt(payload.runId); - - if (!attemptResponse.success) { - throw new Error(`Failed to create task run attempt: ${attemptResponse.error}`); - } - - const execution = attemptResponse.data; - - const completion = await this.executeTaskRun( - { execution, traceContext: payload.traceContext, environment: payload.environment }, - baseURL, - payload.messageId - ); - - return { execution, completion }; - } - - // We need to fork the process before we can execute any tasks - async executeTaskRun( - payload: TaskRunExecutionPayload, - baseURL: string, - messageId?: string - ): Promise { - if (this._closed) { - throw new Error("Worker is closed"); - } - - if (!this.metadata) { - throw new Error("Worker not registered"); - } - - const { execution } = payload; - // ○ Mar 27 09:17:25.653 -> View logs | 20240326.20 | create-avatar | run_slufhjdfiv8ejnrkw9dsj.1 - - const logsUrl = `${baseURL}/runs/${execution.run.id}`; - - const pipe = chalkGrey("|"); - const bullet = chalkGrey("○"); - const link = chalkLink(cliLink("View logs", logsUrl)); - let timestampPrefix = chalkGrey(prettyPrintDate(payload.execution.attempt.startedAt)); - const workerPrefix = chalkWorker(this.metadata.version); - const taskPrefix = chalkTask(execution.task.id); - const runId = chalkRun(`${execution.run.id}.${execution.attempt.number}`); - - logger.log( - `${bullet} ${timestampPrefix} ${chalkGrey( - "->" - )} ${link} ${pipe} ${workerPrefix} ${pipe} ${taskPrefix} ${pipe} ${runId}` - ); - - const now = performance.now(); - - const completion = await this.#doExecuteTaskRun(payload, messageId); - - const elapsed = performance.now() - now; - - const retryingText = chalkGrey( - !completion.ok && completion.skippedRetrying - ? " (retrying skipped)" - : !completion.ok && completion.retry !== undefined - ? ` (retrying in ${completion.retry.delay}ms)` - : "" - ); - - const resultText = !completion.ok - ? completion.error.type === "INTERNAL_ERROR" && - (completion.error.code === TaskRunErrorCodes.TASK_EXECUTION_ABORTED || - completion.error.code === TaskRunErrorCodes.TASK_RUN_CANCELLED) - ? chalkWarning("Cancelled") - : `${chalkError("Error")}${retryingText}` - : chalkSuccess("Success"); - - const errorText = !completion.ok - ? formatErrorLog(completion.error) - : "retry" in completion - ? `retry in ${completion.retry}ms` - : ""; - - const elapsedText = chalkGrey(`(${formatDurationMilliseconds(elapsed, { style: "short" })})`); - - timestampPrefix = chalkGrey(prettyPrintDate()); - - logger.log( - `${bullet} ${timestampPrefix} ${chalkGrey( - "->" - )} ${link} ${pipe} ${workerPrefix} ${pipe} ${taskPrefix} ${pipe} ${runId} ${pipe} ${resultText} ${elapsedText}${errorText}` - ); - - return completion; - } - - async #doExecuteTaskRun( - payload: TaskRunExecutionPayload, - messageId?: string - ): Promise { - try { - const taskRunProcess = await this.#getFreshTaskRunProcess(payload, messageId); - - logger.debug(this.#prefixedMessage(payload, "executing task run"), { - pid: taskRunProcess.pid, - }); - - const result = await taskRunProcess.executeTaskRun(payload); - - // Always kill the worker - await taskRunProcess.cleanup(true); - - if (result.ok) { - return result; - } - - const error = result.error; - - if (error.type === "BUILT_IN_ERROR") { - const mappedError = await this.#correctError(error, payload.execution); - - return { - ...result, - error: mappedError, - }; - } - - return result; - } catch (e) { - if (e instanceof CancelledProcessError) { - return { - id: payload.execution.attempt.id, - ok: false, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_RUN_CANCELLED, - }, - }; - } - - if (e instanceof CleanupProcessError) { - return { - id: payload.execution.attempt.id, - ok: false, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_EXECUTION_ABORTED, - }, - }; - } - - if (e instanceof UnexpectedExitError) { - return { - id: payload.execution.attempt.id, - ok: false, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_PROCESS_EXITED_WITH_NON_ZERO_CODE, - message: getFriendlyErrorMessage(e.code, e.signal, e.stderr), - stackTrace: e.stderr, - }, - }; - } - - return { - id: payload.execution.attempt.id, - ok: false, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_EXECUTION_FAILED, - }, - }; - } - } - - #readEnvVars() { - const result: { [key: string]: string } = {}; - - dotenv.config({ - processEnv: result, - path: [".env", ".env.local", ".env.development.local"].map((p) => resolve(process.cwd(), p)), - }); - - process.env.TRIGGER_API_URL && (result.TRIGGER_API_URL = process.env.TRIGGER_API_URL); - - // remove TRIGGER_API_URL and TRIGGER_SECRET_KEY, since those should be coming from the worker - delete result.TRIGGER_API_URL; - delete result.TRIGGER_SECRET_KEY; - - return result; - } - - async #correctError( - error: TaskRunBuiltInError, - execution: TaskRunExecution - ): Promise { - return { - ...error, - stackTrace: correctErrorStackTrace(error.stackTrace, this.params.projectConfig.projectDir), - }; - } -} - -class TaskRunProcess { - private _handler = new ZodMessageHandler({ - schema: childToWorkerMessages, - }); - private _sender: ZodMessageSender; - private _child: ChildProcess | undefined; - private _childPid?: number; - private _attemptPromises: Map< - string, - { resolver: (value: TaskRunExecutionResult) => void; rejecter: (err?: any) => void } - > = new Map(); - private _attemptStatuses: Map = new Map(); - private _currentExecution: TaskRunExecution | undefined; - private _isBeingKilled: boolean = false; - private _isBeingCancelled: boolean = false; - private _stderr: Array = []; - /** - * @deprecated use onTaskRunHeartbeat instead - */ - public onTaskHeartbeat: Evt = new Evt(); - public onTaskRunHeartbeat: Evt = new Evt(); - public onExit: Evt<{ code: number | null; signal: NodeJS.Signals | null; pid?: number }> = - new Evt(); - public onIsBeingKilled: Evt = new Evt(); - - constructor( - private runId: string, - private isTest: boolean, - private path: string, - private env: NodeJS.ProcessEnv, - private metadata: BackgroundWorkerProperties, - private worker: BackgroundWorkerParams, - private messageId?: string - ) { - this._sender = new ZodMessageSender({ - schema: workerToChildMessages, - sender: async (message) => { - if (this._child?.connected && !this._isBeingKilled && !this._child.killed) { - this._child.send(message); - } - }, - }); - } - - async cancel() { - this._isBeingCancelled = true; - - await this.cleanup(true); - } - - async initialize() { - const fullEnv = { - ...(this.isTest ? { TRIGGER_LOG_LEVEL: "debug" } : {}), - ...this.env, - OTEL_RESOURCE_ATTRIBUTES: JSON.stringify({ - [SemanticInternalAttributes.PROJECT_DIR]: this.worker.projectConfig.projectDir, - }), - OTEL_EXPORTER_OTLP_COMPRESSION: "none", - ...(this.worker.debugOtel ? { OTEL_LOG_LEVEL: "debug" } : {}), - }; - - const cwd = dirname(this.path); - - logger.debug(`[${this.runId}] initializing task run process`, { - env: fullEnv, - path: this.path, - cwd, - }); - - this._child = fork(this.path, { - stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"], - cwd, - env: fullEnv, - execArgv: this.worker.debuggerOn - ? ["--inspect-brk", "--trace-uncaught", "--no-warnings=ExperimentalWarning"] - : ["--trace-uncaught", "--no-warnings=ExperimentalWarning"], - }); - this._childPid = this._child?.pid; - - this._child.on("message", this.#handleMessage.bind(this)); - this._child.on("exit", this.#handleExit.bind(this)); - this._child.stdout?.on("data", this.#handleLog.bind(this)); - this._child.stderr?.on("data", this.#handleStdErr.bind(this)); - } - - async cleanup(kill: boolean = false) { - if (kill && this._isBeingKilled) { - return; - } - - if (kill) { - this._isBeingKilled = true; - this.onIsBeingKilled.post(this._child?.pid); - } - - logger.debug(`[${this.runId}] cleaning up task run process`, { kill, pid: this.pid }); - - await this._sender.send("CLEANUP", { - flush: true, - kill, - }); - - // FIXME: Something broke READY_TO_DISPOSE. We never receive it, so we always have to kill the process after the timeout below. - - if (!kill) { - return; - } - - // Set a timeout to kill the child process if it hasn't been killed within 5 seconds - setTimeout(() => { - if (this._child && !this._child.killed) { - logger.debug(`[${this.runId}] killing task run process after timeout`, { pid: this.pid }); - - this._child.kill(); - } - }, 5000); - } - - async executeTaskRun(payload: TaskRunExecutionPayload): Promise { - let resolver: (value: TaskRunExecutionResult) => void; - let rejecter: (err?: any) => void; - - const promise = new Promise((resolve, reject) => { - resolver = resolve; - rejecter = reject; - }); - - this._attemptStatuses.set(payload.execution.attempt.id, "PENDING"); - - // @ts-expect-error - We know that the resolver and rejecter are defined - this._attemptPromises.set(payload.execution.attempt.id, { resolver, rejecter }); - - const { execution, traceContext } = payload; - - this._currentExecution = execution; - - await this._sender.send("EXECUTE_TASK_RUN", { - execution, - traceContext, - metadata: this.metadata, - }); - - const result = await promise; - - this._currentExecution = undefined; - - return result; - } - - taskRunCompletedNotification(completion: TaskRunExecutionResult) { - if (!completion.ok && typeof completion.retry !== "undefined") { - return; - } - - if (completion.id === this.runId) { - // We don't need to notify the task run process if it's the same as the one we're running - return; - } - - logger.debug(`[${this.runId}] task run completed notification`, { - completion, - }); - - this._sender.send("TASK_RUN_COMPLETED_NOTIFICATION", { - version: "v2", - completion, - }); - } - - async #handleMessage(msg: any) { - const message = this._handler.parseMessage(msg); - - if (!message.success) { - logger.error(`Dropping message: ${message.error}`, { message }); - return; - } - - switch (message.data.type) { - case "TASK_RUN_COMPLETED": { - const { result, execution } = message.data.payload; - - logger.debug(`[${this.runId}] task run completed`, { - result, - }); - - const promiseStatus = this._attemptStatuses.get(execution.attempt.id); - - if (promiseStatus !== "PENDING") { - return; - } - - this._attemptStatuses.set(execution.attempt.id, "RESOLVED"); - - const attemptPromise = this._attemptPromises.get(execution.attempt.id); - - if (!attemptPromise) { - return; - } - - const { resolver } = attemptPromise; - - resolver(result); - - break; - } - case "READY_TO_DISPOSE": { - logger.debug(`[${this.runId}] task run process is ready to dispose`); - - this.#kill(); - - break; - } - case "TASK_HEARTBEAT": { - if (this.messageId) { - this.onTaskRunHeartbeat.post(this.messageId); - } else { - this.onTaskHeartbeat.post(message.data.payload.id); - } - - break; - } - case "TASKS_READY": { - break; - } - } - } - - async #handleExit(code: number | null, signal: NodeJS.Signals | null) { - logger.debug(`[${this.runId}] handle task run process exit`, { code, signal, pid: this.pid }); - - // Go through all the attempts currently pending and reject them - for (const [id, status] of this._attemptStatuses.entries()) { - if (status === "PENDING") { - this._attemptStatuses.set(id, "REJECTED"); - - const attemptPromise = this._attemptPromises.get(id); - - if (!attemptPromise) { - continue; - } - - const { rejecter } = attemptPromise; - - if (this._isBeingCancelled) { - rejecter(new CancelledProcessError()); - } else if (this._isBeingKilled) { - rejecter(new CleanupProcessError()); - } else { - rejecter( - new UnexpectedExitError( - code ?? -1, - signal, - this._stderr.length ? this._stderr.join("\n") : undefined - ) - ); - } - } - } - - this.onExit.post({ code, signal, pid: this.pid }); - } - - #handleLog(data: Buffer) { - if (!this._currentExecution) { - logger.log(`${chalkGrey("○")} ${chalkGrey(prettyPrintDate(new Date()))} ${data.toString()}`); - - return; - } - - const runId = chalkRun( - `${this._currentExecution.run.id}.${this._currentExecution.attempt.number}` - ); - - logger.log( - `${chalkGrey("○")} ${chalkGrey(prettyPrintDate(new Date()))} ${runId} ${data.toString()}` - ); - } - - #handleStdErr(data: Buffer) { - if (this._isBeingKilled) { - return; - } - - if (!this._currentExecution) { - logger.log(`${chalkError("○")} ${chalkGrey(prettyPrintDate(new Date()))} ${data.toString()}`); - - return; - } - - const runId = chalkRun( - `${this._currentExecution.run.id}.${this._currentExecution.attempt.number}` - ); - - const errorLine = data.toString(); - - logger.log( - `${chalkError("○")} ${chalkGrey(prettyPrintDate(new Date()))} ${runId} ${errorLine}` - ); - - if (this._stderr.length > 100) { - this._stderr.shift(); - } - this._stderr.push(errorLine); - } - - #kill() { - logger.debug(`[${this.runId}] #kill()`, { pid: this.pid }); - - if (this._child && !this._child.killed) { - this._child?.kill(); - } - } - - async kill(signal?: number | NodeJS.Signals, timeoutInMs?: number) { - logger.debug(`[${this.runId}] killing task run process`, { - signal, - timeoutInMs, - pid: this.pid, - }); - - this._isBeingKilled = true; - - const killTimeout = this.onExit.waitFor(timeoutInMs); - - this.onIsBeingKilled.post(this._child?.pid); - this._child?.kill(signal); - - if (timeoutInMs) { - await killTimeout; - } - } - - get isBeingKilled() { - return this._isBeingKilled || this._child?.killed; - } - - get pid() { - return this._childPid; - } -} - -function formatErrorLog(error: TaskRunError) { - switch (error.type) { - case "INTERNAL_ERROR": { - return ""; - } - case "STRING_ERROR": { - return `\n\n${chalkError("X Error:")} ${error.raw}\n`; - } - case "CUSTOM_ERROR": { - return `\n\n${chalkError("X Error:")} ${error.raw}\n`; - } - case "BUILT_IN_ERROR": { - return `\n\n${error.stackTrace.replace(/^Error: /, chalkError("X Error: "))}\n`; - } - } -} diff --git a/packages/cli-v3/src/workers/dev/worker-facade.ts b/packages/cli-v3/src/workers/dev/worker-facade.ts deleted file mode 100644 index 1a21b980c..000000000 --- a/packages/cli-v3/src/workers/dev/worker-facade.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { - Config, - LogLevel, - ProjectConfig, - clock, - taskCatalog, - type HandleErrorFunction, -} from "@trigger.dev/core/v3"; -import { - TaskExecutor, - DurableClock, - getEnvVar, - logLevels, - OtelTaskLogger, - ConsoleInterceptor, - type TracingSDK, - usage, - DevUsageManager, -} from "@trigger.dev/core/v3/workers"; - -__WORKER_SETUP__; -declare const __WORKER_SETUP__: unknown; - -__IMPORTED_PROJECT_CONFIG__; -declare const __IMPORTED_PROJECT_CONFIG__: unknown; -declare const importedConfig: ProjectConfig | undefined; -declare const handleError: HandleErrorFunction | undefined; - -declare const __PROJECT_CONFIG__: Config; -declare const tracingSDK: TracingSDK; -declare const otelTracer: Tracer; -declare const otelLogger: Logger; - -import { - TaskRunErrorCodes, - TaskRunExecution, - TriggerTracer, - childToWorkerMessages, - logger, - runtime, - workerToChildMessages, -} from "@trigger.dev/core/v3"; -import { DevRuntimeManager } from "@trigger.dev/core/v3/dev"; -import { - ZodMessageHandler, - ZodMessageSender, - ZodSchemaParsedError, -} from "@trigger.dev/core/v3/zodMessageHandler"; -import type { Tracer } from "@opentelemetry/api"; -import type { Logger } from "@opentelemetry/api-logs"; - -declare const sender: ZodMessageSender; - -const durableClock = new DurableClock(); -clock.setGlobalClock(durableClock); - -usage.setGlobalUsageManager(new DevUsageManager()); - -const tracer = new TriggerTracer({ tracer: otelTracer, logger: otelLogger }); -const consoleInterceptor = new ConsoleInterceptor( - otelLogger, - typeof __PROJECT_CONFIG__.enableConsoleLogging === "boolean" - ? __PROJECT_CONFIG__.enableConsoleLogging - : true -); - -const devRuntimeManager = new DevRuntimeManager(); - -runtime.setGlobalRuntimeManager(devRuntimeManager); - -const triggerLogLevel = getEnvVar("TRIGGER_LOG_LEVEL"); - -const configLogLevel = triggerLogLevel - ? triggerLogLevel - : importedConfig - ? importedConfig.logLevel - : __PROJECT_CONFIG__.logLevel; - -const otelTaskLogger = new OtelTaskLogger({ - logger: otelLogger, - tracer: tracer, - level: logLevels.includes(configLogLevel as any) ? (configLogLevel as LogLevel) : "info", -}); - -logger.setGlobalTaskLogger(otelTaskLogger); - -type TaskFileImport = Record; - -const TaskFileImports: Record = {}; -const TaskFiles: Record = {}; - -__TASKS__; -declare const __TASKS__: Record; - -// Register the task file metadata (fileName and exportName) for each task -(() => { - for (const [importName, taskFile] of Object.entries(TaskFiles)) { - const fileImports = TaskFileImports[importName]; - - for (const [exportName, task] of Object.entries(fileImports ?? {})) { - if ( - typeof task === "object" && - task !== null && - "id" in task && - typeof task.id === "string" - ) { - if (taskCatalog.taskExists(task.id)) { - taskCatalog.registerTaskFileMetadata(task.id, { - exportName, - filePath: (taskFile as any).filePath, - }); - } - } - } - } -})(); - -let _execution: TaskRunExecution | undefined; -let _isRunning = false; - -const handler = new ZodMessageHandler({ - schema: workerToChildMessages, - messages: { - EXECUTE_TASK_RUN: async ({ execution, traceContext, metadata }) => { - if (_isRunning) { - console.error("Worker is already running a task"); - - await sender.send("TASK_RUN_COMPLETED", { - execution, - result: { - ok: false, - id: execution.run.id, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_ALREADY_RUNNING, - }, - usage: { - durationMs: 0, - }, - }, - }); - - return; - } - - process.title = `trigger-dev-worker: ${execution.task.id} ${execution.run.id}`; - - const task = taskCatalog.getTask(execution.task.id); - - if (!task) { - console.error(`Could not find task ${execution.task.id}`); - - await sender.send("TASK_RUN_COMPLETED", { - execution, - result: { - ok: false, - id: execution.run.id, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.COULD_NOT_FIND_EXECUTOR, - }, - usage: { - durationMs: 0, - }, - }, - }); - - return; - } - - const executor = new TaskExecutor(task, { - tracer, - tracingSDK, - consoleInterceptor, - projectConfig: __PROJECT_CONFIG__, - importedConfig, - handleErrorFn: handleError, - }); - - try { - _execution = execution; - _isRunning = true; - - const measurement = usage.start(); - - const { result } = await executor.execute(execution, metadata, traceContext, measurement); - - const usageSample = usage.stop(measurement); - - return sender.send("TASK_RUN_COMPLETED", { - execution, - result: { - ...result, - usage: { - durationMs: usageSample.cpuTime, - }, - }, - }); - } finally { - _execution = undefined; - _isRunning = false; - } - }, - TASK_RUN_COMPLETED_NOTIFICATION: async (payload) => { - switch (payload.version) { - case "v1": { - devRuntimeManager.resumeTask(payload.completion, payload.execution.run.id); - break; - } - case "v2": { - devRuntimeManager.resumeTask(payload.completion, payload.completion.id); - break; - } - } - }, - CLEANUP: async ({ flush, kill }) => { - if (kill) { - await tracingSDK.flush(); - // Now we need to exit the process - await sender.send("READY_TO_DISPOSE", undefined); - } else { - if (flush) { - await tracingSDK.flush(); - } - } - }, - }, -}); - -process.on("message", async (msg: any) => { - await handler.handleMessage(msg); -}); - -const TASK_METADATA = taskCatalog.getAllTaskMetadata(); - -sender.send("TASKS_READY", { tasks: TASK_METADATA }).catch((err) => { - if (err instanceof ZodSchemaParsedError) { - sender.send("TASKS_FAILED_TO_PARSE", { zodIssues: err.error.issues, tasks: TASK_METADATA }); - } else { - console.error("Failed to send TASKS_READY message", err); - } -}); - -process.title = "trigger-dev-worker"; - -async function asyncHeartbeat(initialDelayInSeconds: number = 30, intervalInSeconds: number = 30) { - async function _doHeartbeat() { - while (true) { - if (_isRunning && _execution) { - try { - await sender.send("TASK_HEARTBEAT", { id: _execution.attempt.id }); - } catch (err) { - console.error("Failed to send HEARTBEAT message", err); - } - } - - await new Promise((resolve) => setTimeout(resolve, 1000 * intervalInSeconds)); - } - } - - // Wait for the initial delay - await new Promise((resolve) => setTimeout(resolve, 1000 * initialDelayInSeconds)); - - // Wait for 5 seconds before the next execution - return _doHeartbeat(); -} - -// Start the async interval after 30 seconds -asyncHeartbeat().catch((err) => { - console.error("Failed to start asyncHeartbeat", err); -}); diff --git a/packages/cli-v3/src/workers/dev/worker-setup.ts b/packages/cli-v3/src/workers/dev/worker-setup.ts deleted file mode 100644 index 14af3a27e..000000000 --- a/packages/cli-v3/src/workers/dev/worker-setup.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Tracer } from "@opentelemetry/api"; -import type { Logger } from "@opentelemetry/api-logs"; -import { ProjectConfig, childToWorkerMessages, taskCatalog } from "@trigger.dev/core/v3"; -import { - StandardTaskCatalog, - TracingDiagnosticLogLevel, - TracingSDK, -} from "@trigger.dev/core/v3/workers"; -import { ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler"; -import "source-map-support/register.js"; -import * as packageJson from "../../../package.json"; - -__SETUP_IMPORTED_PROJECT_CONFIG__; -declare const __SETUP_IMPORTED_PROJECT_CONFIG__: unknown; -declare const setupImportedConfig: ProjectConfig | undefined; - -export const tracingSDK = new TracingSDK({ - url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318", - instrumentations: setupImportedConfig?.instrumentations ?? [], - diagLogLevel: (process.env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none", - forceFlushTimeoutMillis: 5_000, -}); - -export const otelTracer: Tracer = tracingSDK.getTracer("trigger-dev-worker", packageJson.version); -export const otelLogger: Logger = tracingSDK.getLogger("trigger-dev-worker", packageJson.version); - -export const sender = new ZodMessageSender({ - schema: childToWorkerMessages, - sender: async (message) => { - process.send?.(message); - }, -}); - -taskCatalog.setGlobalTaskCatalog(new StandardTaskCatalog()); diff --git a/packages/cli-v3/src/workers/prod/backgroundWorker.ts b/packages/cli-v3/src/workers/prod/backgroundWorker.ts deleted file mode 100644 index e9513b97c..000000000 --- a/packages/cli-v3/src/workers/prod/backgroundWorker.ts +++ /dev/null @@ -1,873 +0,0 @@ -import { - BackgroundWorkerProperties, - Config, - CreateBackgroundWorkerResponse, - ProdChildToWorkerMessages, - ProdTaskRunExecution, - ProdTaskRunExecutionPayload, - ProdWorkerToChildMessages, - SemanticInternalAttributes, - TaskMetadataWithFilePath, - TaskRunBuiltInError, - TaskRunErrorCodes, - TaskRunExecution, - TaskRunExecutionLazyAttemptPayload, - TaskRunExecutionPayload, - TaskRunExecutionResult, - correctErrorStackTrace, -} from "@trigger.dev/core/v3"; -import { ZodIpcConnection } from "@trigger.dev/core/v3/zodIpc"; -import type { InferSocketMessageSchema } from "@trigger.dev/core/v3/zodSocket"; -import { Evt } from "evt"; -import { ChildProcess, fork } from "node:child_process"; -import { - CancelledProcessError, - CleanupProcessError, - GracefulExitTimeoutError, - SigKillTimeoutProcessError, - TaskMetadataParseError, - UncaughtExceptionError, - UnexpectedExitError, - getFriendlyErrorMessage, -} from "../common/errors.js"; - -type BackgroundWorkerParams = { - env: Record; - projectConfig: Config; - contentHash: string; - debugOtel?: boolean; -}; - -export type OnWaitForDurationMessage = InferSocketMessageSchema< - typeof ProdChildToWorkerMessages, - "WAIT_FOR_DURATION" ->; -export type OnWaitForTaskMessage = InferSocketMessageSchema< - typeof ProdChildToWorkerMessages, - "WAIT_FOR_TASK" ->; -export type OnWaitForBatchMessage = InferSocketMessageSchema< - typeof ProdChildToWorkerMessages, - "WAIT_FOR_BATCH" ->; - -export class ProdBackgroundWorker { - private _initialized: boolean = false; - - /** - * @deprecated use onTaskRunHeartbeat instead - */ - public onTaskHeartbeat: Evt = new Evt(); - public onTaskRunHeartbeat: Evt = new Evt(); - - public onWaitForDuration: Evt = new Evt(); - public onWaitForTask: Evt = new Evt(); - public onWaitForBatch: Evt = new Evt(); - - public onCreateTaskRunAttempt = Evt.create<{ version?: "v1"; runId: string }>(); - public attemptCreatedNotification = Evt.create< - | { - success: false; - reason?: string; - } - | { - success: true; - execution: ProdTaskRunExecution; - } - >(); - - private _onClose: Evt = new Evt(); - - public tasks: Array = []; - public stderr: Array = []; - - _taskRunProcess: TaskRunProcess | undefined; - private _taskRunProcessesBeingKilled: Map = new Map(); - - private _closed: boolean = false; - - constructor( - public path: string, - private params: BackgroundWorkerParams - ) {} - - async close(gracefulExitTimeoutElapsed = false) { - console.log("Closing worker", { gracefulExitTimeoutElapsed, closed: this._closed }); - - if (this._closed) { - return; - } - - this._closed = true; - - this.onTaskHeartbeat.detach(); - this.onTaskRunHeartbeat.detach(); - - // We need to close the task run process - await this._taskRunProcess?.cleanup(true, gracefulExitTimeoutElapsed); - } - - async #killTaskRunProcess(flush = true, initialSignal: number | NodeJS.Signals = "SIGTERM") { - console.log("Killing task run process", { flush, initialSignal, closed: this._closed }); - - if (this._closed || !this._taskRunProcess) { - return; - } - - if (flush) { - await this.flushTelemetry(); - } - - const currentTaskRunProcess = this._taskRunProcess; - - // Try graceful exit but don't wait. We limit the amount of processes during creation instead. - this.#tryGracefulExit(currentTaskRunProcess, true, initialSignal).catch((error) => { - console.error("Error while trying graceful exit", error); - }); - - console.log("Killed task run process, setting closed to true", { - closed: this._closed, - pid: currentTaskRunProcess.pid, - }); - this._closed = true; - } - - async flushTelemetry() { - console.log("Flushing telemetry"); - const start = performance.now(); - - await this._taskRunProcess?.cleanup(false); - - console.log("Flushed telemetry", { duration: performance.now() - start }); - } - - async initialize(options?: { env?: Record }) { - if (this._initialized) { - throw new Error("Worker already initialized"); - } - - let resolved = false; - - this.tasks = await new Promise>((resolve, reject) => { - const child = fork(this.path, { - stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"], - env: { - ...this.params.env, - ...options?.env, - }, - }); - - // Set a timeout to kill the child process if it doesn't respond - const timeout = setTimeout(() => { - if (resolved) { - return; - } - - resolved = true; - child.kill(); - reject(new Error("Worker timed out")); - }, 10_000); - - child.stdout?.on("data", (data) => { - console.log(data.toString()); - }); - - child.stderr?.on("data", (data) => { - console.error(data.toString()); - this.stderr.push(data.toString()); - }); - - child.on("exit", (code) => { - if (!resolved) { - clearTimeout(timeout); - resolved = true; - reject(new Error(`Worker exited with code ${code}`)); - } - }); - - new ZodIpcConnection({ - listenSchema: ProdChildToWorkerMessages, - emitSchema: ProdWorkerToChildMessages, - process: child, - handlers: { - TASKS_READY: async (message) => { - if (!resolved) { - clearTimeout(timeout); - resolved = true; - resolve(message.tasks); - child.kill(); - } - }, - UNCAUGHT_EXCEPTION: async (message) => { - if (!resolved) { - clearTimeout(timeout); - resolved = true; - reject(new UncaughtExceptionError(message.error, message.origin)); - child.kill(); - } - }, - TASKS_FAILED_TO_PARSE: async (message) => { - if (!resolved) { - clearTimeout(timeout); - resolved = true; - reject(new TaskMetadataParseError(message.zodIssues, message.tasks)); - child.kill(); - } - }, - }, - }); - }); - - this._initialized = true; - } - - getMetadata(workerId: string, version: string): CreateBackgroundWorkerResponse { - return { - contentHash: this.params.contentHash, - id: workerId, - version: version, - }; - } - - // We need to notify all the task run processes that a task run has completed, - // in case they are waiting for it through triggerAndWait - async taskRunCompletedNotification(completion: TaskRunExecutionResult) { - this._taskRunProcess?.taskRunCompletedNotification(completion); - } - - async waitCompletedNotification() { - this._taskRunProcess?.waitCompletedNotification(); - } - - async #getFreshTaskRunProcess( - payload: ProdTaskRunExecutionPayload, - messageId?: string - ): Promise { - const metadata = this.getMetadata( - payload.execution.worker.id, - payload.execution.worker.version - ); - - console.log("Getting fresh task run process, setting closed to false", { - closed: this._closed, - }); - this._closed = false; - - await this.#killCurrentTaskRunProcessBeforeAttempt(); - - const taskRunProcess = new TaskRunProcess( - payload.execution.run.id, - payload.execution.run.isTest, - this.path, - { - ...this.params.env, - ...(payload.environment ?? {}), - }, - metadata, - this.params, - messageId - ); - - taskRunProcess.onExit.attach(({ pid }) => { - console.log("Task run process exited", { pid }); - - // Only delete the task run process if the pid matches - if (this._taskRunProcess?.pid === pid) { - this._taskRunProcess = undefined; - } - - if (pid) { - this._taskRunProcessesBeingKilled.delete(pid); - } - }); - - taskRunProcess.onIsBeingKilled.attach((taskRunProcess) => { - if (taskRunProcess?.pid) { - this._taskRunProcessesBeingKilled.set(taskRunProcess.pid, taskRunProcess); - } - }); - - taskRunProcess.onTaskHeartbeat.attach((id) => { - this.onTaskHeartbeat.post(id); - }); - - taskRunProcess.onTaskRunHeartbeat.attach((id) => { - this.onTaskRunHeartbeat.post(id); - }); - - taskRunProcess.onWaitForBatch.attach((message) => { - this.onWaitForBatch.post(message); - }); - - taskRunProcess.onWaitForDuration.attach((message) => { - this.onWaitForDuration.post(message); - }); - - taskRunProcess.onWaitForTask.attach((message) => { - this.onWaitForTask.post(message); - }); - - await taskRunProcess.initialize(); - - this._taskRunProcess = taskRunProcess; - - return this._taskRunProcess; - } - - async forceKillOldTaskRunProcesses() { - for (const taskRunProcess of this._taskRunProcessesBeingKilled.values()) { - try { - await taskRunProcess.kill("SIGKILL"); - } catch (error) { - console.error("Error while force killing old task run processes", error); - } - } - } - - async #killCurrentTaskRunProcessBeforeAttempt() { - console.log("killCurrentTaskRunProcessBeforeAttempt()", { - hasTaskRunProcess: !!this._taskRunProcess, - }); - - if (!this._taskRunProcess) { - return; - } - - const currentTaskRunProcess = this._taskRunProcess; - - console.log("Killing current task run process", { - isBeingKilled: currentTaskRunProcess?.isBeingKilled, - totalBeingKilled: this._taskRunProcessesBeingKilled.size, - }); - - if (currentTaskRunProcess.isBeingKilled) { - if (this._taskRunProcessesBeingKilled.size > 1) { - await this.#tryGracefulExit(currentTaskRunProcess); - } else { - // If there's only one or none being killed, don't do anything so we can create a fresh one in parallel - } - } else { - // It's not being killed, so kill it - if (this._taskRunProcessesBeingKilled.size > 0) { - await this.#tryGracefulExit(currentTaskRunProcess); - } else { - // There's none being killed yet, so we can kill it without waiting. We still set a timeout to kill it forcefully just in case it sticks around. - currentTaskRunProcess.kill("SIGTERM", 5_000).catch(() => {}); - } - } - } - - async #tryGracefulExit( - taskRunProcess: TaskRunProcess, - kill = false, - initialSignal: number | NodeJS.Signals = "SIGTERM" - ) { - console.log("Trying graceful exit", { kill, initialSignal }); - - try { - const initialExit = taskRunProcess.onExit.waitFor(5_000); - - if (kill) { - taskRunProcess.kill(initialSignal); - } - - await initialExit; - } catch (error) { - console.error("TaskRunProcess graceful kill timeout exceeded", error); - - this.#tryForcefulExit(taskRunProcess); - } - } - - async #tryForcefulExit(taskRunProcess: TaskRunProcess) { - console.log("Trying forceful exit"); - - try { - const forcedKill = taskRunProcess.onExit.waitFor(5_000); - taskRunProcess.kill("SIGKILL"); - await forcedKill; - } catch (error) { - console.error("TaskRunProcess forced kill timeout exceeded", error); - throw new SigKillTimeoutProcessError(); - } - } - - // We need to fork the process before we can execute any tasks, use a fresh process for each execution - async executeTaskRun( - payload: ProdTaskRunExecutionPayload, - messageId?: string - ): Promise { - try { - const taskRunProcess = await this.#getFreshTaskRunProcess(payload, messageId); - - console.log("executing task run", { - attempt: payload.execution.attempt.id, - taskRunPid: taskRunProcess.pid, - }); - - const result = await taskRunProcess.executeTaskRun(payload); - - if (result.ok) { - return result; - } - - const error = result.error; - - if (error.type === "BUILT_IN_ERROR") { - const mappedError = await this.#correctError(error, payload.execution); - - return { - ...result, - error: mappedError, - }; - } - - return result; - } catch (e) { - if (e instanceof CancelledProcessError) { - return { - id: payload.execution.attempt.id, - ok: false, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_RUN_CANCELLED, - }, - }; - } - - if (e instanceof CleanupProcessError) { - return { - id: payload.execution.attempt.id, - ok: false, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_EXECUTION_ABORTED, - }, - }; - } - - if (e instanceof UnexpectedExitError) { - return { - id: payload.execution.attempt.id, - ok: false, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_PROCESS_EXITED_WITH_NON_ZERO_CODE, - message: getFriendlyErrorMessage(e.code, e.signal, e.stderr), - stackTrace: e.stderr, - }, - }; - } - - if (e instanceof SigKillTimeoutProcessError) { - return { - id: payload.execution.attempt.id, - ok: false, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_PROCESS_SIGKILL_TIMEOUT, - }, - }; - } - - if (e instanceof GracefulExitTimeoutError) { - return { - id: payload.execution.attempt.id, - ok: false, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.GRACEFUL_EXIT_TIMEOUT, - message: "Worker process killed while attempt in progress.", - }, - }; - } - - return { - id: payload.execution.attempt.id, - ok: false, - retry: undefined, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_EXECUTION_FAILED, - }, - }; - } finally { - await this.#killTaskRunProcess(); - } - } - - async cancelAttempt(attemptId: string) { - if (!this._taskRunProcess) { - console.error("No task run process to cancel attempt", { attemptId }); - return; - } - - await this._taskRunProcess.cancel(); - } - - async executeTaskRunLazyAttempt(payload: TaskRunExecutionLazyAttemptPayload) { - // Post to coordinator - this.onCreateTaskRunAttempt.post({ runId: payload.runId }); - - let execution: ProdTaskRunExecution; - - try { - const start = performance.now(); - - // ..and wait for response - const attemptCreated = await this.attemptCreatedNotification.waitFor(120_000); - - if (!attemptCreated.success) { - throw new Error(`${attemptCreated.reason ?? "Unknown error"}`); - } - - console.log("Attempt created", { - number: attemptCreated.execution.attempt.number, - duration: performance.now() - start, - }); - - execution = attemptCreated.execution; - } catch (error) { - console.error("Error while creating attempt", error); - throw new Error(`Failed to create attempt: ${error}`); - } - - const completion = await this.executeTaskRun( - { - execution, - traceContext: payload.traceContext, - environment: payload.environment, - }, - payload.messageId - ); - - return { execution, completion }; - } - - async #correctError( - error: TaskRunBuiltInError, - execution: TaskRunExecution - ): Promise { - return { - ...error, - stackTrace: correctErrorStackTrace(error.stackTrace, this.params.projectConfig.projectDir), - }; - } -} - -class TaskRunProcess { - private _ipc?: ZodIpcConnection< - typeof ProdChildToWorkerMessages, - typeof ProdWorkerToChildMessages - >; - private _child?: ChildProcess; - private _childPid?: number; - - private _attemptPromises: Map< - string, - { resolver: (value: TaskRunExecutionResult) => void; rejecter: (err?: any) => void } - > = new Map(); - private _attemptStatuses: Map = new Map(); - private _currentExecution: TaskRunExecution | undefined; - private _isBeingKilled: boolean = false; - private _isBeingCancelled: boolean = false; - private _gracefulExitTimeoutElapsed: boolean = false; - private _stderr: Array = []; - - /** - * @deprecated use onTaskRunHeartbeat instead - */ - public onTaskHeartbeat: Evt = new Evt(); - public onTaskRunHeartbeat: Evt = new Evt(); - public onExit: Evt<{ code: number | null; signal: NodeJS.Signals | null; pid?: number }> = - new Evt(); - public onIsBeingKilled: Evt = new Evt(); - - public onWaitForDuration: Evt = new Evt(); - public onWaitForTask: Evt = new Evt(); - public onWaitForBatch: Evt = new Evt(); - - public preCheckpointNotification = Evt.create<{ willCheckpointAndRestore: boolean }>(); - - constructor( - private runId: string, - private isTest: boolean, - private path: string, - private env: NodeJS.ProcessEnv, - private metadata: BackgroundWorkerProperties, - private worker: BackgroundWorkerParams, - private messageId?: string - ) {} - - async initialize() { - this._child = fork(this.path, { - stdio: [/*stdin*/ "ignore", /*stdout*/ "pipe", /*stderr*/ "pipe", "ipc"], - env: { - ...(this.isTest ? { TRIGGER_LOG_LEVEL: "debug" } : {}), - ...this.env, - OTEL_RESOURCE_ATTRIBUTES: JSON.stringify({ - [SemanticInternalAttributes.PROJECT_DIR]: this.worker.projectConfig.projectDir, - }), - ...(this.worker.debugOtel ? { OTEL_LOG_LEVEL: "debug" } : {}), - }, - }); - - this._childPid = this._child?.pid; - - this._ipc = new ZodIpcConnection({ - listenSchema: ProdChildToWorkerMessages, - emitSchema: ProdWorkerToChildMessages, - process: this._child, - handlers: { - TASK_RUN_COMPLETED: async (message) => { - const { result, execution } = message; - - const promiseStatus = this._attemptStatuses.get(execution.attempt.id); - - if (promiseStatus !== "PENDING") { - return; - } - - this._attemptStatuses.set(execution.attempt.id, "RESOLVED"); - - const attemptPromise = this._attemptPromises.get(execution.attempt.id); - - if (!attemptPromise) { - return; - } - - const { resolver } = attemptPromise; - - resolver(result); - }, - READY_TO_DISPOSE: async (message) => { - process.exit(0); - }, - TASK_HEARTBEAT: async (message) => { - if (this.messageId) { - this.onTaskRunHeartbeat.post(this.messageId); - } else { - console.error( - "No message id for task heartbeat, falling back to (deprecated) attempt heartbeat", - { id: message.id } - ); - this.onTaskHeartbeat.post(message.id); - } - }, - TASKS_READY: async (message) => {}, - WAIT_FOR_TASK: async (message) => { - this.onWaitForTask.post(message); - }, - WAIT_FOR_BATCH: async (message) => { - this.onWaitForBatch.post(message); - }, - WAIT_FOR_DURATION: async (message) => { - this.onWaitForDuration.post(message); - }, - }, - }); - - this._child.on("exit", this.#handleExit.bind(this)); - this._child.stdout?.on("data", this.#handleLog.bind(this)); - this._child.stderr?.on("data", this.#handleStdErr.bind(this)); - } - - async cancel() { - this._isBeingCancelled = true; - - await this.cleanup(true); - } - - async cleanup(kill = false, gracefulExitTimeoutElapsed = false) { - console.log("cleanup()", { kill, gracefulExitTimeoutElapsed }); - - if (kill && this._isBeingKilled) { - return; - } - - if (kill) { - this._isBeingKilled = true; - this.onIsBeingKilled.post(this); - } - - const killChildProcess = gracefulExitTimeoutElapsed && !!this._currentExecution; - - // Kill parent unless graceful exit timeout has elapsed and we're in the middle of an execution - const killParentProcess = kill && !killChildProcess; - - console.log("Cleaning up task run process", { - killChildProcess, - killParentProcess, - ipc: this._ipc, - childPid: this._childPid, - realChildPid: this._child?.pid, - }); - - try { - await this._ipc?.sendWithAck( - "CLEANUP", - { - flush: true, - kill: killParentProcess, - }, - 30_000 - ); - } catch (error) { - console.error("Error while cleaning up task run process", error); - if (killParentProcess) { - process.exit(0); - } - } - - if (killChildProcess) { - this._gracefulExitTimeoutElapsed = true; - // Kill the child process - await this.kill("SIGKILL"); - } - } - - async executeTaskRun(payload: TaskRunExecutionPayload): Promise { - let resolver: (value: TaskRunExecutionResult) => void; - let rejecter: (err?: any) => void; - - const promise = new Promise((resolve, reject) => { - resolver = resolve; - rejecter = reject; - }); - - this._attemptStatuses.set(payload.execution.attempt.id, "PENDING"); - - // @ts-expect-error - We know that the resolver and rejecter are defined - this._attemptPromises.set(payload.execution.attempt.id, { resolver, rejecter }); - - const { execution, traceContext } = payload; - - this._currentExecution = execution; - - if (this._child?.connected && !this._isBeingKilled && !this._child.killed) { - await this._ipc?.send("EXECUTE_TASK_RUN", { - execution, - traceContext, - metadata: this.metadata, - }); - } - - const result = await promise; - - this._currentExecution = undefined; - - return result; - } - - taskRunCompletedNotification(completion: TaskRunExecutionResult) { - if (!completion.ok && typeof completion.retry !== "undefined") { - console.error( - "Task run completed with error and wants to retry, won't send task run completed notification" - ); - return; - } - - if (!this._child?.connected || this._isBeingKilled || this._child.killed) { - console.error( - "Child process not connected or being killed, can't send task run completed notification" - ); - return; - } - - this._ipc?.send("TASK_RUN_COMPLETED_NOTIFICATION", { - version: "v2", - completion, - }); - } - - waitCompletedNotification() { - if (!this._child?.connected || this._isBeingKilled || this._child.killed) { - console.error( - "Child process not connected or being killed, can't send wait completed notification" - ); - return; - } - - this._ipc?.send("WAIT_COMPLETED_NOTIFICATION", {}); - } - - async #handleExit(code: number | null, signal: NodeJS.Signals | null) { - console.log("handling child exit", { code, signal }); - - // Go through all the attempts currently pending and reject them - for (const [id, status] of this._attemptStatuses.entries()) { - if (status === "PENDING") { - console.log("found pending attempt", { id }); - - this._attemptStatuses.set(id, "REJECTED"); - - const attemptPromise = this._attemptPromises.get(id); - - if (!attemptPromise) { - continue; - } - - const { rejecter } = attemptPromise; - - if (this._isBeingCancelled) { - rejecter(new CancelledProcessError()); - } else if (this._gracefulExitTimeoutElapsed) { - // Order matters, this has to be before the graceful exit timeout - rejecter(new GracefulExitTimeoutError()); - } else if (this._isBeingKilled) { - rejecter(new CleanupProcessError()); - } else { - rejecter( - new UnexpectedExitError( - code ?? -1, - signal, - this._stderr.length ? this._stderr.join("\n") : undefined - ) - ); - } - } - } - - this.onExit.post({ code, signal, pid: this.pid }); - } - - #handleLog(data: Buffer) { - console.log(data.toString()); - } - - #handleStdErr(data: Buffer) { - const text = data.toString(); - console.error(text); - - if (this._stderr.length > 100) { - this._stderr.shift(); - } - this._stderr.push(text); - } - - async kill(signal?: number | NodeJS.Signals, timeoutInMs?: number) { - this._isBeingKilled = true; - - const killTimeout = this.onExit.waitFor(timeoutInMs); - - this.onIsBeingKilled.post(this); - this._child?.kill(signal); - - if (timeoutInMs) { - await killTimeout; - } - } - - get isBeingKilled() { - return this._isBeingKilled || this._child?.killed; - } - - get pid() { - return this._childPid; - } -} diff --git a/packages/cli-v3/src/workers/prod/entry-point.ts b/packages/cli-v3/src/workers/prod/entry-point.ts deleted file mode 100644 index 878b2ddde..000000000 --- a/packages/cli-v3/src/workers/prod/entry-point.ts +++ /dev/null @@ -1,1482 +0,0 @@ -import { - Config, - CoordinatorToProdWorkerMessages, - PostStartCauses, - PreStopCauses, - ProdTaskRunExecution, - ProdWorkerToCoordinatorMessages, - TaskResource, - TaskRunErrorCodes, - TaskRunExecutionResult, - TaskRunFailedExecutionResult, - WaitReason, -} from "@trigger.dev/core/v3"; -import { InferSocketMessageSchema, ZodSocketConnection } from "@trigger.dev/core/v3/zodSocket"; -import { - HttpReply, - getRandomPortNumber, - SimpleLogger, - EXIT_CODE_ALREADY_HANDLED, - EXIT_CODE_CHILD_NONZERO, - ExponentialBackoff, -} from "@trigger.dev/core/v3/apps"; -import { - OnWaitForBatchMessage, - OnWaitForTaskMessage, - ProdBackgroundWorker, -} from "./backgroundWorker.js"; -import { TaskMetadataParseError, UncaughtExceptionError } from "../common/errors.js"; -import { checkpointSafeTimeout, unboundedTimeout } from "@trigger.dev/core/v3/utils/timers"; -import { randomUUID } from "node:crypto"; -import { readFile } from "node:fs/promises"; -import { createServer } from "node:http"; -import { setTimeout as timeout } from "node:timers/promises"; -import { Evt } from "evt"; - -declare const __PROJECT_CONFIG__: Config; - -const HTTP_SERVER_PORT = Number(process.env.HTTP_SERVER_PORT || getRandomPortNumber()); -const COORDINATOR_HOST = process.env.COORDINATOR_HOST || "127.0.0.1"; -const COORDINATOR_PORT = Number(process.env.COORDINATOR_PORT || 50080); -const MACHINE_NAME = process.env.MACHINE_NAME || "local"; -const POD_NAME = process.env.POD_NAME || "some-pod"; -const SHORT_HASH = process.env.TRIGGER_CONTENT_HASH!.slice(0, 9); - -const logger = new SimpleLogger(`[${MACHINE_NAME}][${SHORT_HASH}]`); - -const defaultBackoff = new ExponentialBackoff("FullJitter", { - maxRetries: 5, -}); - -class ProdWorker { - private apiUrl = process.env.TRIGGER_API_URL!; - private apiKey = process.env.TRIGGER_SECRET_KEY!; - private contentHash = process.env.TRIGGER_CONTENT_HASH!; - private projectRef = process.env.TRIGGER_PROJECT_REF!; - private envId = process.env.TRIGGER_ENV_ID!; - private runId = process.env.TRIGGER_RUN_ID || "index-only"; - private deploymentId = process.env.TRIGGER_DEPLOYMENT_ID!; - private deploymentVersion = process.env.TRIGGER_DEPLOYMENT_VERSION!; - private runningInKubernetes = !!process.env.KUBERNETES_PORT; - - private executing = false; - private completed = new Set(); - private paused = false; - private attemptFriendlyId?: string; - private attemptNumber?: number; - - private nextResumeAfter?: WaitReason; - private waitForPostStart = false; - private connectionCount = 0; - - private restoreNotification = Evt.create(); - - private waitForTaskReplay: - | { - idempotencyKey: string; - message: OnWaitForTaskMessage; - attempt: number; - } - | undefined; - private waitForBatchReplay: - | { - idempotencyKey: string; - message: OnWaitForBatchMessage; - attempt: number; - } - | undefined; - private readyForLazyAttemptReplay: - | { - idempotencyKey: string; - } - | undefined; - private durationResumeFallback: - | { - idempotencyKey: string; - } - | undefined; - - #httpPort: number; - #backgroundWorker: ProdBackgroundWorker; - #httpServer: ReturnType; - #coordinatorSocket: ZodSocketConnection< - typeof ProdWorkerToCoordinatorMessages, - typeof CoordinatorToProdWorkerMessages - >; - - constructor( - port: number, - private host = "0.0.0.0" - ) { - process.on("SIGTERM", this.#handleSignal.bind(this, "SIGTERM")); - - this.#coordinatorSocket = this.#createCoordinatorSocket(COORDINATOR_HOST); - this.#backgroundWorker = this.#createBackgroundWorker(); - - this.#httpPort = port; - this.#httpServer = this.#createHttpServer(); - } - - async #handleSignal(signal: NodeJS.Signals) { - logger.log("Received signal", { signal }); - - if (signal === "SIGTERM") { - let gracefulExitTimeoutElapsed = false; - - if (this.executing) { - const terminationGracePeriodSeconds = 60 * 60; - - logger.log("Waiting for attempt to complete before exiting", { - terminationGracePeriodSeconds, - }); - - // Wait for termination grace period minus 5s to give cleanup a chance to complete - await timeout(terminationGracePeriodSeconds * 1000 - 5000); - gracefulExitTimeoutElapsed = true; - - logger.log("Termination timeout reached, exiting gracefully."); - } else { - logger.log("Not executing, exiting immediately."); - } - - await this.#exitGracefully(gracefulExitTimeoutElapsed); - return; - } - - logger.log("Unhandled signal", { signal }); - } - - async #exitGracefully(gracefulExitTimeoutElapsed = false, exitCode = 0) { - await this.#backgroundWorker.close(gracefulExitTimeoutElapsed); - - if (!gracefulExitTimeoutElapsed) { - // TODO: Maybe add a sensible timeout instead of a conditional to avoid zombies - process.exit(exitCode); - } - } - - async #reconnectAfterPostStart() { - this.waitForPostStart = false; - - this.#coordinatorSocket.close(); - this.connectionCount = 0; - - let coordinatorHost = COORDINATOR_HOST; - - try { - if (this.runningInKubernetes) { - coordinatorHost = (await readFile("/etc/taskinfo/coordinator-host", "utf-8")).replace( - "\n", - "" - ); - - logger.log("reconnecting", { - coordinatorHost: { - fromEnv: COORDINATOR_HOST, - fromVolume: coordinatorHost, - current: this.#coordinatorSocket.socket.io.opts.hostname, - }, - }); - } - } catch (error) { - logger.error("taskinfo read error during reconnect", { - error: error instanceof Error ? error.message : error, - }); - } finally { - this.#coordinatorSocket = this.#createCoordinatorSocket(coordinatorHost); - } - } - - // MARK: TASK WAIT - #waitForTaskHandlerFactory(workerId?: string) { - return async (message: OnWaitForTaskMessage, replayIdempotencyKey?: string) => { - logger.log("onWaitForTask", { workerId, message }); - - if (this.nextResumeAfter) { - logger.error("Already waiting for resume, skipping wait for task", { - nextResumeAfter: this.nextResumeAfter, - }); - - return; - } - - const waitForTask = await defaultBackoff.execute(async ({ retry }) => { - logger.log("Wait for task with backoff", { retry }); - - if (!this.attemptFriendlyId) { - logger.error("Failed to send wait message, attempt friendly ID not set", { message }); - - throw new ExponentialBackoff.StopRetrying("No attempt ID"); - } - - return await this.#coordinatorSocket.socket.timeout(20_000).emitWithAck("WAIT_FOR_TASK", { - version: "v2", - friendlyId: message.friendlyId, - attemptFriendlyId: this.attemptFriendlyId, - }); - }); - - if (!waitForTask.success) { - logger.error("Failed to wait for task with backoff", { - cause: waitForTask.cause, - error: waitForTask.error, - }); - - this.#emitUnrecoverableError( - "WaitForTaskFailed", - `${waitForTask.cause}: ${waitForTask.error}` - ); - - return; - } - - const { willCheckpointAndRestore } = waitForTask.result; - - await this.#prepareForWait("WAIT_FOR_TASK", willCheckpointAndRestore); - - if (willCheckpointAndRestore) { - // We need to replay this on next connection if we don't receive RESUME_AFTER_DEPENDENCY within a reasonable time - if (!this.waitForTaskReplay) { - this.waitForTaskReplay = { - message, - attempt: 1, - idempotencyKey: randomUUID(), - }; - } else { - if ( - replayIdempotencyKey && - replayIdempotencyKey !== this.waitForTaskReplay.idempotencyKey - ) { - logger.error( - "wait for task handler called with mismatched idempotency key, won't overwrite replay request" - ); - return; - } - - this.waitForTaskReplay.attempt++; - } - } - }; - } - - // MARK: BATCH WAIT - #waitForBatchHandlerFactory(workerId?: string) { - return async (message: OnWaitForBatchMessage, replayIdempotencyKey?: string) => { - logger.log("onWaitForBatch", { workerId, message }); - - if (this.nextResumeAfter) { - logger.error("Already waiting for resume, skipping wait for batch", { - nextResumeAfter: this.nextResumeAfter, - }); - - return; - } - - const waitForBatch = await defaultBackoff.execute(async ({ retry }) => { - logger.log("Wait for batch with backoff", { retry }); - - if (!this.attemptFriendlyId) { - logger.error("Failed to send wait message, attempt friendly ID not set", { message }); - - throw new ExponentialBackoff.StopRetrying("No attempt ID"); - } - - return await this.#coordinatorSocket.socket.timeout(20_000).emitWithAck("WAIT_FOR_BATCH", { - version: "v2", - batchFriendlyId: message.batchFriendlyId, - runFriendlyIds: message.runFriendlyIds, - attemptFriendlyId: this.attemptFriendlyId, - }); - }); - - if (!waitForBatch.success) { - logger.error("Failed to wait for batch with backoff", { - cause: waitForBatch.cause, - error: waitForBatch.error, - }); - - this.#emitUnrecoverableError( - "WaitForBatchFailed", - `${waitForBatch.cause}: ${waitForBatch.error}` - ); - - return; - } - - const { willCheckpointAndRestore } = waitForBatch.result; - - await this.#prepareForWait("WAIT_FOR_BATCH", willCheckpointAndRestore); - - if (willCheckpointAndRestore) { - // We need to replay this on next connection if we don't receive RESUME_AFTER_DEPENDENCY within a reasonable time - if (!this.waitForBatchReplay) { - this.waitForBatchReplay = { - message, - attempt: 1, - idempotencyKey: randomUUID(), - }; - } else { - if ( - replayIdempotencyKey && - replayIdempotencyKey !== this.waitForBatchReplay.idempotencyKey - ) { - logger.error( - "wait for task handler called with mismatched idempotency key, won't overwrite replay request" - ); - return; - } - - this.waitForBatchReplay.attempt++; - } - } - }; - } - - // MARK: WORKER CREATION - #createBackgroundWorker() { - const workerId = randomUUID(); - - logger.log("Creating background worker", { workerId }); - - const backgroundWorker = new ProdBackgroundWorker("worker.js", { - projectConfig: __PROJECT_CONFIG__, - env: { - ...gatherProcessEnv(), - TRIGGER_API_URL: this.apiUrl, - TRIGGER_SECRET_KEY: this.apiKey, - OTEL_EXPORTER_OTLP_ENDPOINT: - process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318", - }, - contentHash: this.contentHash, - }); - - backgroundWorker.onTaskHeartbeat.attach((attemptFriendlyId) => { - logger.log("onTaskHeartbeat", { - workerId, - attemptFriendlyId, - }); - - this.#coordinatorSocket.socket.volatile.emit("TASK_HEARTBEAT", { - version: "v1", - attemptFriendlyId, - }); - }); - - backgroundWorker.onTaskRunHeartbeat.attach((runId) => { - logger.log("onTaskRunHeartbeat", { - workerId, - runId, - }); - - this.#coordinatorSocket.socket.volatile.emit("TASK_RUN_HEARTBEAT", { version: "v1", runId }); - }); - - backgroundWorker.onCreateTaskRunAttempt.attach(async (message) => { - logger.log("onCreateTaskRunAttempt()", { - workerId, - message, - }); - - const createAttempt = await defaultBackoff.execute(async ({ retry }) => { - logger.log("Create task run attempt with backoff", { retry }); - - return await this.#coordinatorSocket.socket - .timeout(15_000) - .emitWithAck("CREATE_TASK_RUN_ATTEMPT", { - version: "v1", - runId: message.runId, - }); - }); - - if (!createAttempt.success) { - backgroundWorker.attemptCreatedNotification.post({ - success: false, - reason: `Failed to create attempt with backoff due to ${createAttempt.cause}. ${createAttempt.error}`, - }); - return; - } - - if (!createAttempt.result.success) { - backgroundWorker.attemptCreatedNotification.post({ - success: false, - reason: createAttempt.result.reason, - }); - return; - } - - backgroundWorker.attemptCreatedNotification.post({ - success: true, - execution: createAttempt.result.executionPayload.execution, - }); - }); - - backgroundWorker.attemptCreatedNotification.attach((message) => { - logger.log("attemptCreatedNotification", { - workerId, - success: message.success, - ...(message.success - ? { - attempt: message.execution.attempt, - queue: message.execution.queue, - worker: message.execution.worker, - machine: message.execution.machine, - } - : { - reason: message.reason, - }), - }); - - if (!message.success) { - return; - } - - // Workers with lazy attempt support set their friendly ID here - this.attemptFriendlyId = message.execution.attempt.id; - this.attemptNumber = message.execution.attempt.number; - }); - - // MARK: WAIT_FOR_DURATION - backgroundWorker.onWaitForDuration.attach(async (message) => { - logger.log("onWaitForDuration", { - workerId, - ...message, - drift: Date.now() - message.now, - }); - - if (this.nextResumeAfter) { - logger.error("Already waiting for resume, skipping wait for duration", { - nextResumeAfter: this.nextResumeAfter, - }); - - return; - } - - noResume: { - const { ms, waitThresholdInMs } = message; - - const internalTimeout = unboundedTimeout(ms, "internal" as const); - const checkpointSafeInternalTimeout = checkpointSafeTimeout(ms); - - if (ms < waitThresholdInMs) { - await internalTimeout; - break noResume; - } - - const waitForDuration = await defaultBackoff.execute(async ({ retry }) => { - logger.log("Wait for duration with backoff", { retry }); - - if (!this.attemptFriendlyId) { - logger.error("Failed to send wait message, attempt friendly ID not set", { message }); - - throw new ExponentialBackoff.StopRetrying("No attempt ID"); - } - - return await this.#coordinatorSocket.socket - .timeout(20_000) - .emitWithAck("WAIT_FOR_DURATION", { - ...message, - attemptFriendlyId: this.attemptFriendlyId, - }); - }); - - if (!waitForDuration.success) { - logger.error("Failed to wait for duration with backoff", { - cause: waitForDuration.cause, - error: waitForDuration.error, - }); - - this.#emitUnrecoverableError( - "WaitForDurationFailed", - `${waitForDuration.cause}: ${waitForDuration.error}` - ); - - return; - } - - const { willCheckpointAndRestore } = waitForDuration.result; - - if (!willCheckpointAndRestore) { - await internalTimeout; - break noResume; - } - - await this.#prepareForWait("WAIT_FOR_DURATION", willCheckpointAndRestore); - // CHECKPOINTING AFTER THIS LINE - - // internalTimeout acts as a backup and will be accurate if the checkpoint never happens - // checkpointSafeInternalTimeout is accurate even after non-simulated restores - await Promise.race([internalTimeout, checkpointSafeInternalTimeout]); - - const idempotencyKey = randomUUID(); - this.durationResumeFallback = { idempotencyKey }; - - try { - await this.restoreNotification.waitFor(5_000); - } catch (error) { - logger.error("Did not receive restore notification in time", { - error, - }); - } - - try { - // The coordinator should cancel any in-progress checkpoints so we don't end up with race conditions - const { checkpointCanceled } = await this.#coordinatorSocket.socket - .timeout(15_000) - .emitWithAck("CANCEL_CHECKPOINT", { - version: "v2", - reason: "WAIT_FOR_DURATION", - }); - - logger.log("onCancelCheckpoint coordinator response", { checkpointCanceled }); - - if (checkpointCanceled) { - // If the checkpoint was canceled, we will never be resumed externally with RESUME_AFTER_DURATION, so it's safe to immediately resume - break noResume; - } - - logger.log("Waiting for external duration resume as we may have been restored"); - - setTimeout(() => { - if (!this.durationResumeFallback) { - logger.error("Already resumed after duration, skipping fallback"); - return; - } - - if (this.durationResumeFallback.idempotencyKey !== idempotencyKey) { - logger.error("Duration resume idempotency key mismatch, skipping fallback"); - return; - } - - logger.log("Resuming after duration with fallback"); - - this.#resumeAfterDuration(); - }, 15_000); - } catch (error) { - // Just log this for now, but don't automatically resume. Wait for the external checkpoint-based resume. - logger.debug("Checkpoint cancellation timed out", { - workerId, - message, - error, - }); - } - - return; - } - - this.#resumeAfterDuration(); - }); - - backgroundWorker.onWaitForTask.attach(this.#waitForTaskHandlerFactory(workerId).bind(this)); - backgroundWorker.onWaitForBatch.attach(this.#waitForBatchHandlerFactory(workerId).bind(this)); - - return backgroundWorker; - } - - async #prepareForWait(reason: WaitReason, willCheckpointAndRestore: boolean) { - logger.log(`prepare for ${reason}`, { willCheckpointAndRestore }); - - if (this.nextResumeAfter) { - logger.error("Already waiting for resume, skipping prepare for wait", { - nextResumeAfter: this.nextResumeAfter, - params: { - reason, - willCheckpointAndRestore, - }, - }); - - return; - } - - if (!willCheckpointAndRestore) { - return; - } - - this.paused = true; - this.nextResumeAfter = reason; - this.waitForPostStart = true; - - await this.#prepareForCheckpoint(); - } - - // MARK: RETRY PREP - async #prepareForRetry(shouldExit: boolean, exitCode?: number) { - logger.log("prepare for retry", { shouldExit, exitCode }); - - // Graceful shutdown on final attempt - if (shouldExit) { - await this.#exitGracefully(false, exitCode); - return; - } - - // Clear state for next execution - this.paused = false; - this.waitForPostStart = false; - this.executing = false; - this.attemptFriendlyId = undefined; - this.attemptNumber = undefined; - } - - // MARK: CHECKPOINT PREP - async #prepareForCheckpoint(flush = true) { - if (flush) { - // Flush before checkpointing so we don't flush the same spans again after restore - try { - await this.#backgroundWorker.flushTelemetry(); - } catch (error) { - logger.error( - "Failed to flush telemetry while preparing for checkpoint, will proceed anyway", - { error } - ); - } - } - - try { - // Kill the previous worker process to prevent large checkpoints - await this.#backgroundWorker.forceKillOldTaskRunProcesses(); - } catch (error) { - logger.error( - "Failed to kill previous worker while preparing for checkpoint, will proceed anyway", - { error } - ); - } - - this.#readyForCheckpoint(); - } - - #resumeAfterDuration() { - this.paused = false; - this.nextResumeAfter = undefined; - this.waitForPostStart = false; - - this.durationResumeFallback = undefined; - - this.#backgroundWorker.waitCompletedNotification(); - } - - async #readyForLazyAttempt() { - const idempotencyKey = randomUUID(); - - this.readyForLazyAttemptReplay = { - idempotencyKey, - }; - - // Retry if we don't receive EXECUTE_TASK_RUN_LAZY_ATTEMPT in a reasonable time - // ..but we also have to be fast to avoid failing the task due to missing heartbeat - for await (const { delay, retry } of defaultBackoff.min(10).maxRetries(3)) { - if (retry > 0) { - logger.log("retrying ready for lazy attempt", { retry }); - } - - this.#coordinatorSocket.socket.emit("READY_FOR_LAZY_ATTEMPT", { - version: "v1", - runId: this.runId, - totalCompletions: this.completed.size, - }); - - await timeout(delay.milliseconds); - - if (!this.readyForLazyAttemptReplay) { - logger.error("replay ready for lazy attempt cancelled, discarding", { - idempotencyKey, - }); - - return; - } - - if (idempotencyKey !== this.readyForLazyAttemptReplay.idempotencyKey) { - logger.error("replay ready for lazy attempt idempotency key mismatch, discarding", { - idempotencyKey, - newIdempotencyKey: this.readyForLazyAttemptReplay.idempotencyKey, - }); - - return; - } - } - - // Fail the task with a more descriptive message as it likely failed with a generic missing heartbeat error - this.#failRun(this.runId, "Failed to receive execute request in a reasonable time"); - } - - #readyForCheckpoint() { - this.#coordinatorSocket.socket.emit("READY_FOR_CHECKPOINT", { version: "v1" }); - } - - #failRun(anyRunId: string, error: unknown) { - logger.error("Failing run", { anyRunId, error }); - - const completion: TaskRunFailedExecutionResult = { - ok: false, - id: anyRunId, - retry: undefined, - error: - error instanceof Error - ? { - type: "BUILT_IN_ERROR", - name: error.name, - message: error.message, - stackTrace: error.stack ?? "", - } - : { - type: "BUILT_IN_ERROR", - name: "UnknownError", - message: String(error), - stackTrace: "", - }, - }; - - this.#coordinatorSocket.socket.emit("TASK_RUN_FAILED_TO_RUN", { - version: "v1", - completion, - }); - } - - // MARK: ATTEMPT COMPLETION - async #submitAttemptCompletion( - execution: ProdTaskRunExecution, - completion: TaskRunExecutionResult, - replayIdempotencyKey?: string - ) { - const taskRunCompleted = await defaultBackoff.execute(async ({ retry }) => { - logger.log("Submit attempt completion with backoff", { retry }); - - return await this.#coordinatorSocket.socket - .timeout(20_000) - .emitWithAck("TASK_RUN_COMPLETED", { - version: "v2", - execution, - completion, - }); - }); - - if (!taskRunCompleted.success) { - logger.error("Failed to complete lazy attempt with backoff", { - cause: taskRunCompleted.cause, - error: taskRunCompleted.error, - }); - - this.#failRun(execution.run.id, taskRunCompleted.error); - - return; - } - - const { willCheckpointAndRestore, shouldExit } = taskRunCompleted.result; - - logger.log("completion acknowledged", { willCheckpointAndRestore, shouldExit }); - - const exitCode = - !completion.ok && - completion.error.type === "INTERNAL_ERROR" && - completion.error.code === TaskRunErrorCodes.TASK_PROCESS_EXITED_WITH_NON_ZERO_CODE - ? EXIT_CODE_CHILD_NONZERO - : 0; - - await this.#prepareForRetry(shouldExit, exitCode); - - if (willCheckpointAndRestore) { - logger.error("This worker should never be checkpointed between attempts. This is a bug."); - } - } - - #returnValidatedExtraHeaders(headers: Record) { - for (const [key, value] of Object.entries(headers)) { - if (value === undefined) { - throw new Error(`Extra header is undefined: ${key}`); - } - } - - return headers; - } - - // MARK: COORDINATOR SOCKET - #createCoordinatorSocket(host: string) { - const extraHeaders = this.#returnValidatedExtraHeaders({ - "x-machine-name": MACHINE_NAME, - "x-pod-name": POD_NAME, - "x-trigger-content-hash": this.contentHash, - "x-trigger-project-ref": this.projectRef, - "x-trigger-env-id": this.envId, - "x-trigger-deployment-id": this.deploymentId, - "x-trigger-run-id": this.runId, - "x-trigger-deployment-version": this.deploymentVersion, - }); - - if (this.attemptFriendlyId) { - extraHeaders["x-trigger-attempt-friendly-id"] = this.attemptFriendlyId; - } - - if (this.attemptNumber !== undefined) { - extraHeaders["x-trigger-attempt-number"] = String(this.attemptNumber); - } - - logger.log(`connecting to coordinator: ${host}:${COORDINATOR_PORT}`); - logger.debug(`connecting with extra headers`, { extraHeaders }); - - const coordinatorConnection = new ZodSocketConnection({ - namespace: "prod-worker", - host, - port: COORDINATOR_PORT, - clientMessages: ProdWorkerToCoordinatorMessages, - serverMessages: CoordinatorToProdWorkerMessages, - extraHeaders, - ioOptions: { - reconnectionDelay: 1000, - reconnectionDelayMax: 3000, - }, - handlers: { - RESUME_AFTER_DEPENDENCY: async ({ completions }) => { - if (!this.paused) { - logger.error("Failed to resume after dependency: Worker not paused"); - return; - } - - if (completions.length === 0) { - logger.error("Failed to resume after dependency: No completions"); - return; - } - - if ( - this.nextResumeAfter !== "WAIT_FOR_TASK" && - this.nextResumeAfter !== "WAIT_FOR_BATCH" - ) { - logger.error("Failed to resume after dependency: Invalid next resume", { - nextResumeAfter: this.nextResumeAfter, - }); - return; - } - - if (this.nextResumeAfter === "WAIT_FOR_TASK" && completions.length > 1) { - logger.error( - "Failed to resume after dependency: Waiting for single task but got multiple completions", - { - completions: completions, - } - ); - return; - } - - switch (this.nextResumeAfter) { - case "WAIT_FOR_TASK": { - this.waitForTaskReplay = undefined; - break; - } - case "WAIT_FOR_BATCH": { - this.waitForBatchReplay = undefined; - break; - } - } - - this.paused = false; - this.nextResumeAfter = undefined; - this.waitForPostStart = false; - - for (let i = 0; i < completions.length; i++) { - const completion = completions[i]; - - if (!completion) continue; - - this.#backgroundWorker.taskRunCompletedNotification(completion); - } - }, - RESUME_AFTER_DURATION: async (message) => { - if (!this.paused) { - logger.error("worker not paused", { - attemptId: message.attemptId, - }); - return; - } - - if (this.nextResumeAfter !== "WAIT_FOR_DURATION") { - logger.error("not waiting to resume after duration", { - nextResumeAfter: this.nextResumeAfter, - }); - return; - } - - this.#resumeAfterDuration(); - }, - EXECUTE_TASK_RUN: async () => { - // These messages should only be received by old workers that don't support lazy attempts - this.#failRun( - this.runId, - "Received deprecated EXECUTE_TASK_RUN message. Please contact us if you see this error." - ); - }, - EXECUTE_TASK_RUN_LAZY_ATTEMPT: async (message) => { - this.readyForLazyAttemptReplay = undefined; - - if (this.executing) { - logger.error("dropping execute request, already executing"); - return; - } - - const attemptCount = message.lazyPayload.attemptCount ?? 0; - - logger.log("execute attempt counts", { attemptCount, completed: this.completed.size }); - - if (this.completed.size > 0 && this.completed.size >= attemptCount + 1) { - logger.error("dropping execute request, already completed"); - return; - } - - this.executing = true; - - try { - const { completion, execution } = - await this.#backgroundWorker.executeTaskRunLazyAttempt(message.lazyPayload); - - logger.log("completed", completion); - - this.completed.add(execution.attempt.id); - - await this.#submitAttemptCompletion(execution, completion); - } catch (error) { - logger.error("Failed to complete lazy attempt", { - error, - }); - - this.#failRun(message.lazyPayload.runId, error); - } - }, - REQUEST_ATTEMPT_CANCELLATION: async (message) => { - if (!this.executing) { - logger.log("dropping cancel request, not executing", { status: this.#status }); - return; - } - - logger.log("cancelling attempt", { attemptId: message.attemptId, status: this.#status }); - - await this.#backgroundWorker.cancelAttempt(message.attemptId); - }, - REQUEST_EXIT: async (message) => { - if (message.version === "v2" && message.delayInMs) { - logger.log("exit requested with delay", { delayInMs: message.delayInMs }); - await timeout(message.delayInMs); - } - - this.#coordinatorSocket.close(); - process.exit(0); - }, - READY_FOR_RETRY: async (message) => { - if (this.completed.size < 1) { - logger.error("Received READY_FOR_RETRY but no completions yet. This is a bug."); - return; - } - - await this.#readyForLazyAttempt(); - }, - }, - // MARK: ON CONNECTION - onConnection: async (socket, handler, sender, logger) => { - logger.log("connected to coordinator", { - status: this.#status, - connectionCount: ++this.connectionCount, - }); - - // We need to send our current state to the coordinator - socket.emit("SET_STATE", { - version: "v1", - attemptFriendlyId: this.attemptFriendlyId, - attemptNumber: this.attemptNumber ? String(this.attemptNumber) : undefined, - }); - - try { - if (this.waitForPostStart) { - logger.log("skip connection handler, waiting for post start hook"); - return; - } - - if (this.paused) { - if (!this.nextResumeAfter) { - logger.error("Missing next resume reason", { status: this.#status }); - - this.#emitUnrecoverableError( - "NoNextResume", - "Next resume reason not set while resuming from paused state" - ); - - return; - } - - if (!this.attemptFriendlyId) { - logger.error("Missing attempt friendly ID", { status: this.#status }); - - this.#emitUnrecoverableError( - "NoAttemptId", - "Attempt ID not set while resuming from paused state" - ); - - return; - } - - if (!this.attemptNumber) { - logger.error("Missing attempt number", { status: this.#status }); - - this.#emitUnrecoverableError( - "NoAttemptNumber", - "Attempt number not set while resuming from paused state" - ); - - return; - } - - socket.emit("READY_FOR_RESUME", { - version: "v2", - attemptFriendlyId: this.attemptFriendlyId, - attemptNumber: this.attemptNumber, - type: this.nextResumeAfter, - }); - - return; - } - - if (process.env.INDEX_TASKS === "true") { - const failIndex = ( - error: InferSocketMessageSchema< - typeof ProdWorkerToCoordinatorMessages, - "INDEXING_FAILED" - >["error"] - ) => { - socket.emit("INDEXING_FAILED", { - version: "v1", - deploymentId: this.deploymentId, - error, - }); - }; - - process.removeAllListeners("uncaughtException"); - process.on("uncaughtException", (error) => { - console.error("Uncaught exception while indexing", error); - failIndex(error); - }); - - try { - const taskResources = await this.#initializeWorker(); - - const indexTasks = await defaultBackoff.maxRetries(3).execute(async () => { - return await socket.timeout(20_000).emitWithAck("INDEX_TASKS", { - version: "v2", - deploymentId: this.deploymentId, - ...taskResources, - supportsLazyAttempts: true, - }); - }); - - if (!indexTasks.success || !indexTasks.result.success) { - logger.error("indexing failure, shutting down..", { indexTasks }); - process.exit(1); - } else { - logger.info("indexing done, shutting down.."); - process.exit(0); - } - } catch (e) { - const stderr = this.#backgroundWorker.stderr.join("\n"); - - if (e instanceof TaskMetadataParseError) { - logger.error("tasks metadata parse error", { - zodIssues: e.zodIssues, - tasks: e.tasks, - }); - - failIndex({ - name: "TaskMetadataParseError", - message: "There was an error parsing the task metadata", - stack: JSON.stringify({ zodIssues: e.zodIssues, tasks: e.tasks }), - stderr, - }); - } else if (e instanceof UncaughtExceptionError) { - const error = { - name: e.originalError.name, - message: e.originalError.message, - stack: e.originalError.stack, - stderr, - }; - - logger.error("uncaught exception", { originalError: error }); - - failIndex(error); - } else if (e instanceof Error) { - const error = { - name: e.name, - message: e.message, - stack: e.stack, - stderr, - }; - - logger.error("error", { error }); - - failIndex(error); - } else if (typeof e === "string") { - logger.error("string error", { error: { message: e } }); - - failIndex({ - name: "Error", - message: e, - stderr, - }); - } else { - logger.error("unknown error", { error: e }); - - failIndex({ - name: "Error", - message: "Unknown error", - stderr, - }); - } - - await timeout(1000); - - process.exit(EXIT_CODE_ALREADY_HANDLED); - } - } - - if (this.executing) { - return; - } - - process.removeAllListeners("uncaughtException"); - process.on("uncaughtException", (error) => { - console.error("Uncaught exception during run", error); - this.#failRun(this.runId, error); - }); - - await this.#readyForLazyAttempt(); - } catch (error) { - logger.error("connection handler error", { error }); - } finally { - if (this.connectionCount === 1) { - // Skip replays if this is the first connection, including post start - return; - } - - // This is a reconnect, so handle replays - this.#handleReplays(); - } - }, - onError: async (socket, err, logger) => { - logger.error("onError", { - error: { - name: err.name, - message: err.message, - }, - }); - }, - }); - - return coordinatorConnection; - } - - // MARK: REPLAYS - async #handleReplays() { - const backoff = new ExponentialBackoff().type("FullJitter").maxRetries(3); - const replayCancellationDelay = 20_000; - - if (this.waitForTaskReplay) { - logger.log("replaying wait for task", { ...this.waitForTaskReplay }); - - const { idempotencyKey, message, attempt } = this.waitForTaskReplay; - - // Give the platform some time to send RESUME_AFTER_DEPENDENCY - await timeout(replayCancellationDelay); - - if (!this.waitForTaskReplay) { - logger.error("wait for task replay cancelled, discarding", { - originalMessage: { idempotencyKey, message, attempt }, - }); - - return; - } - - if (idempotencyKey !== this.waitForTaskReplay.idempotencyKey) { - logger.error("wait for task replay idempotency key mismatch, discarding", { - originalMessage: { idempotencyKey, message, attempt }, - newMessage: this.waitForTaskReplay, - }); - - return; - } - - try { - await backoff.wait(attempt + 1); - - await this.#waitForTaskHandlerFactory("replay")(message, idempotencyKey); - } catch (error) { - if (error instanceof ExponentialBackoff.RetryLimitExceeded) { - logger.error("wait for task replay retry limit exceeded", { error }); - } else { - logger.error("wait for task replay error", { error }); - } - } - - return; - } - - if (this.waitForBatchReplay) { - logger.log("replaying wait for batch", { - ...this.waitForBatchReplay, - cancellationDelay: replayCancellationDelay, - }); - - const { idempotencyKey, message, attempt } = this.waitForBatchReplay; - - // Give the platform some time to send RESUME_AFTER_DEPENDENCY - await timeout(replayCancellationDelay); - - if (!this.waitForBatchReplay) { - logger.error("wait for batch replay cancelled, discarding", { - originalMessage: { idempotencyKey, message, attempt }, - }); - - return; - } - - if (idempotencyKey !== this.waitForBatchReplay.idempotencyKey) { - logger.error("wait for batch replay idempotency key mismatch, discarding", { - originalMessage: { idempotencyKey, message, attempt }, - newMessage: this.waitForBatchReplay, - }); - - return; - } - - try { - await backoff.wait(attempt + 1); - - await this.#waitForBatchHandlerFactory("replay")(message, idempotencyKey); - } catch (error) { - if (error instanceof ExponentialBackoff.RetryLimitExceeded) { - logger.error("wait for batch replay retry limit exceeded", { error }); - } else { - logger.error("wait for batch replay error", { error }); - } - } - - return; - } - } - - // MARK: HTTP SERVER - #createHttpServer() { - const httpServer = createServer(async (req, res) => { - logger.log(`[${req.method}]`, req.url); - const reply = new HttpReply(res); - - try { - const url = new URL(req.url ?? "", `http://${req.headers.host}`); - - switch (url.pathname) { - case "/health": { - return reply.text("ok"); - } - - case "/status": { - return reply.json(this.#status); - } - - case "/connect": { - this.#coordinatorSocket.connect(); - - return reply.text("Connected to coordinator"); - } - - case "/close": { - this.#coordinatorSocket.close(); - this.connectionCount = 0; - - return reply.text("Disconnected from coordinator"); - } - - case "/test": { - await this.#coordinatorSocket.socket.timeout(10_000).emitWithAck("TEST", { - version: "v1", - }); - - return reply.text("Received ACK from coordinator"); - } - - case "/preStop": { - const cause = PreStopCauses.safeParse(url.searchParams.get("cause")); - - if (!cause.success) { - logger.error("Failed to parse cause", { cause }); - return reply.text("Failed to parse cause", 400); - } - - switch (cause.data) { - case "terminate": { - break; - } - default: { - logger.error("Unhandled cause", { cause: cause.data }); - break; - } - } - - return reply.text("preStop ok"); - } - - case "/postStart": { - const cause = PostStartCauses.safeParse(url.searchParams.get("cause")); - - if (!cause.success) { - logger.error("Failed to parse cause", { cause }); - return reply.text("Failed to parse cause", 400); - } - - switch (cause.data) { - case "index": { - break; - } - case "create": { - break; - } - case "restore": { - await this.#reconnectAfterPostStart(); - this.restoreNotification.post(); - break; - } - default: { - logger.error("Unhandled cause", { cause: cause.data }); - break; - } - } - - return reply.text("postStart ok"); - } - - default: { - return reply.empty(404); - } - } - } catch (error) { - logger.error("HTTP server error", { error }); - reply.empty(500); - return; - } - }); - - httpServer.on("clientError", (err, socket) => { - socket.end("HTTP/1.1 400 Bad Request\r\n\r\n"); - }); - - httpServer.on("listening", () => { - logger.log("http server listening on port", this.#httpPort); - }); - - httpServer.on("error", async (error) => { - // @ts-expect-error - if (error.code != "EADDRINUSE") { - return; - } - - logger.error(`port ${this.#httpPort} already in use, retrying with random port..`); - - this.#httpPort = getRandomPortNumber(); - - await timeout(100); - this.start(); - }); - - return httpServer; - } - - async #initializeWorker() { - // Make an API call for the env vars - // Don't use ApiClient again - // Pass those into this.#backgroundWorker.initialize() - const envVars = await this.#fetchEnvironmentVariables(); - - await this.#backgroundWorker.initialize({ env: envVars }); - - let packageVersion: string | undefined; - - const taskResources: Array = []; - - if (!this.#backgroundWorker.tasks || this.#backgroundWorker.tasks.length === 0) { - throw new Error( - `Background Worker started without tasks. Searched in: ${__PROJECT_CONFIG__.triggerDirectories?.join( - ", " - )}` - ); - } - - for (const task of this.#backgroundWorker.tasks) { - taskResources.push(task); - - packageVersion = task.packageVersion; - } - - if (!packageVersion) { - throw new Error(`Background Worker started without package version`); - } - - return { - packageVersion, - tasks: taskResources, - }; - } - - async #fetchEnvironmentVariables(): Promise> { - const response = await fetch(`${this.apiUrl}/api/v1/projects/${this.projectRef}/envvars`, { - method: "GET", - headers: { - Authorization: `Bearer ${this.apiKey}`, - }, - }); - - if (!response.ok) { - return {}; - } - - const data = await response.json(); - - return data?.variables ?? {}; - } - - get #status() { - return { - executing: this.executing, - paused: this.paused, - completed: this.completed.size, - nextResumeAfter: this.nextResumeAfter, - waitForPostStart: this.waitForPostStart, - attemptFriendlyId: this.attemptFriendlyId, - attemptNumber: this.attemptNumber, - waitForTaskReplay: this.waitForTaskReplay, - waitForBatchReplay: this.waitForBatchReplay, - }; - } - - #emitUnrecoverableError(name: string, message: string) { - this.#coordinatorSocket.socket.emit("UNRECOVERABLE_ERROR", { - version: "v1", - error: { - name, - message, - }, - }); - } - - start() { - this.#httpServer.listen(this.#httpPort, this.host); - } -} - -const prodWorker = new ProdWorker(HTTP_SERVER_PORT); -prodWorker.start(); - -function gatherProcessEnv() { - const env = { - NODE_ENV: process.env.NODE_ENV ?? "production", - PATH: process.env.PATH, - USER: process.env.USER, - SHELL: process.env.SHELL, - LANG: process.env.LANG, - TERM: process.env.TERM, - NODE_PATH: process.env.NODE_PATH, - HOME: process.env.HOME, - NODE_EXTRA_CA_CERTS: process.env.NODE_EXTRA_CA_CERTS, - }; - - // Filter out undefined values - return Object.fromEntries(Object.entries(env).filter(([key, value]) => value !== undefined)); -} diff --git a/packages/cli-v3/src/workers/prod/worker-facade.ts b/packages/cli-v3/src/workers/prod/worker-facade.ts deleted file mode 100644 index ef2a6a292..000000000 --- a/packages/cli-v3/src/workers/prod/worker-facade.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { - Config, - HandleErrorFunction, - LogLevel, - ProdChildToWorkerMessages, - ProdWorkerToChildMessages, - ProjectConfig, - clock, - taskCatalog, -} from "@trigger.dev/core/v3"; -import { - ConsoleInterceptor, - DevUsageManager, - DurableClock, - OtelTaskLogger, - ProdUsageManager, - TaskExecutor, - getEnvVar, - logLevels, - usage, - type TracingSDK, -} from "@trigger.dev/core/v3/workers"; -import { ZodIpcConnection } from "@trigger.dev/core/v3/zodIpc"; -import { ZodSchemaParsedError } from "@trigger.dev/core/v3/zodMessageHandler"; -import "source-map-support/register.js"; - -__WORKER_SETUP__; -declare const __WORKER_SETUP__: unknown; - -__IMPORTED_PROJECT_CONFIG__; -declare const __IMPORTED_PROJECT_CONFIG__: unknown; -declare const importedConfig: ProjectConfig | undefined; -declare const handleError: HandleErrorFunction | undefined; - -declare const __PROJECT_CONFIG__: Config; -declare const tracingSDK: TracingSDK; -declare const otelTracer: Tracer; -declare const otelLogger: Logger; - -import type { Tracer } from "@opentelemetry/api"; -import type { Logger } from "@opentelemetry/api-logs"; -import { - TaskRunErrorCodes, - TaskRunExecution, - TriggerTracer, - logger, - runtime, -} from "@trigger.dev/core/v3"; -import { ProdRuntimeManager } from "@trigger.dev/core/v3/prod"; - -const heartbeatIntervalMs = getEnvVar("USAGE_HEARTBEAT_INTERVAL_MS"); -const usageEventUrl = getEnvVar("USAGE_EVENT_URL"); -const triggerJWT = getEnvVar("TRIGGER_JWT"); - -const prodUsageManager = new ProdUsageManager(new DevUsageManager(), { - heartbeatIntervalMs: heartbeatIntervalMs ? parseInt(heartbeatIntervalMs, 10) : undefined, - url: usageEventUrl, - jwt: triggerJWT, -}); - -usage.setGlobalUsageManager(prodUsageManager); - -const durableClock = new DurableClock(); -clock.setGlobalClock(durableClock); - -const tracer = new TriggerTracer({ tracer: otelTracer, logger: otelLogger }); -const consoleInterceptor = new ConsoleInterceptor(otelLogger, true); - -const triggerLogLevel = getEnvVar("TRIGGER_LOG_LEVEL"); - -const configLogLevel = triggerLogLevel - ? triggerLogLevel - : importedConfig - ? importedConfig.logLevel - : __PROJECT_CONFIG__.logLevel; - -const otelTaskLogger = new OtelTaskLogger({ - logger: otelLogger, - tracer: tracer, - level: logLevels.includes(configLogLevel as any) ? (configLogLevel as LogLevel) : "info", -}); - -logger.setGlobalTaskLogger(otelTaskLogger); - -type TaskFileImport = Record; - -const TaskFileImports: Record = {}; -const TaskFiles: Record = {}; - -__TASKS__; -declare const __TASKS__: Record; - -// Register the task file metadata (fileName and exportName) for each task -(() => { - for (const [importName, taskFile] of Object.entries(TaskFiles)) { - const fileImports = TaskFileImports[importName]; - - for (const [exportName, task] of Object.entries(fileImports ?? {})) { - if ( - typeof task === "object" && - task !== null && - "id" in task && - typeof task.id === "string" - ) { - if (taskCatalog.taskExists(task.id)) { - taskCatalog.registerTaskFileMetadata(task.id, { - exportName, - filePath: (taskFile as any).filePath, - }); - } - } - } - } -})(); - -let _execution: TaskRunExecution | undefined; -let _isRunning = false; - -const zodIpc = new ZodIpcConnection({ - listenSchema: ProdWorkerToChildMessages, - emitSchema: ProdChildToWorkerMessages, - process, - handlers: { - EXECUTE_TASK_RUN: async ({ execution, traceContext, metadata }, sender) => { - if (_isRunning) { - console.error("Worker is already running a task"); - - await sender.send("TASK_RUN_COMPLETED", { - execution, - result: { - ok: false, - id: execution.run.id, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.TASK_ALREADY_RUNNING, - }, - }, - }); - - return; - } - process.title = `trigger-prod-worker: ${execution.task.id} ${execution.run.id}`; - - const task = taskCatalog.getTask(execution.task.id); - - if (!task) { - console.error(`Could not find task ${execution.task.id}`); - - await sender.send("TASK_RUN_COMPLETED", { - execution, - result: { - ok: false, - id: execution.run.id, - error: { - type: "INTERNAL_ERROR", - code: TaskRunErrorCodes.COULD_NOT_FIND_EXECUTOR, - }, - }, - }); - - return; - } - - const executor = new TaskExecutor(task, { - tracer, - tracingSDK, - consoleInterceptor, - projectConfig: __PROJECT_CONFIG__, - importedConfig, - handleErrorFn: handleError, - }); - - try { - _execution = execution; - _isRunning = true; - - const measurement = usage.start(); - - const { result } = await executor.execute(execution, metadata, traceContext, measurement); - - const usageSample = usage.stop(measurement); - - return await sender.send("TASK_RUN_COMPLETED", { - execution, - result: { - ...result, - usage: { - durationMs: usageSample.cpuTime, - }, - }, - }); - } finally { - _execution = undefined; - _isRunning = false; - } - }, - TASK_RUN_COMPLETED_NOTIFICATION: async ({ completion }) => { - prodRuntimeManager.resumeTask(completion); - }, - WAIT_COMPLETED_NOTIFICATION: async () => { - prodRuntimeManager.resumeAfterDuration(); - }, - CLEANUP: async ({ flush, kill }, sender) => { - if (kill) { - await flushAll(); - // Now we need to exit the process - await sender.send("READY_TO_DISPOSE", undefined); - } else { - if (flush) { - await flushAll(); - } - } - }, - }, -}); - -async function flushAll(timeoutInMs: number = 10_000) { - const now = performance.now(); - - console.log(`Flushing at ${now}`); - - await Promise.all([flushUsage(), flushTracingSDK()]); - - const duration = performance.now() - now; - - console.log(`Flushed in ${duration}ms`); -} - -async function flushUsage() { - const now = performance.now(); - - console.log(`Flushing usage at ${now}`); - - await prodUsageManager.flush(); - - const duration = performance.now() - now; - - console.log(`Flushed usage in ${duration}ms`); -} - -async function flushTracingSDK() { - const now = performance.now(); - - console.log(`Flushing tracingSDK at ${now}`); - - await tracingSDK.flush(); - - const duration = performance.now() - now; - - console.log(`Flushed tracingSDK in ${duration}ms`); -} - -// Ignore SIGTERM, handled by entry point -process.on("SIGTERM", async () => {}); - -const prodRuntimeManager = new ProdRuntimeManager(zodIpc, { - waitThresholdInMs: parseInt(process.env.TRIGGER_RUNTIME_WAIT_THRESHOLD_IN_MS ?? "30000", 10), -}); - -runtime.setGlobalRuntimeManager(prodRuntimeManager); - -let taskMetadata = taskCatalog.getAllTaskMetadata(); - -if (typeof importedConfig?.machine === "string") { - // Set the machine preset on all tasks that don't have it - taskMetadata = taskMetadata.map((task) => { - if (typeof task.machine?.preset !== "string") { - return { - ...task, - machine: { - preset: importedConfig.machine, - }, - }; - } - - return task; - }); -} - -zodIpc.send("TASKS_READY", { tasks: taskMetadata }).catch((err) => { - if (err instanceof ZodSchemaParsedError) { - zodIpc.send("TASKS_FAILED_TO_PARSE", { zodIssues: err.error.issues, tasks: taskMetadata }); - } else { - console.error("Failed to send TASKS_READY message", err); - } -}); - -process.title = "trigger-prod-worker"; - -async function asyncHeartbeat(initialDelayInSeconds: number = 30, intervalInSeconds: number = 20) { - async function _doHeartbeat() { - while (true) { - if (_isRunning && _execution) { - try { - // The attempt ID will only be used to heartbeat if the message (run) ID isn't set on the TaskRunProcess - await zodIpc.send("TASK_HEARTBEAT", { id: _execution.attempt.id }); - } catch (err) { - console.error("Failed to send HEARTBEAT message", err); - } - } - - await new Promise((resolve) => setTimeout(resolve, 1000 * intervalInSeconds)); - } - } - - // Wait for the initial delay - await new Promise((resolve) => setTimeout(resolve, 1000 * initialDelayInSeconds)); - - // Wait for 5 seconds before the next execution - return _doHeartbeat(); -} - -// Start the async interval after initial delay -asyncHeartbeat(5).catch((err) => { - console.error("Failed to start asyncHeartbeat", err); -}); diff --git a/packages/cli-v3/src/workers/prod/worker-setup.ts b/packages/cli-v3/src/workers/prod/worker-setup.ts deleted file mode 100644 index 91478ee3b..000000000 --- a/packages/cli-v3/src/workers/prod/worker-setup.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Tracer } from "@opentelemetry/api"; -import * as packageJson from "../../../package.json"; -import { ProjectConfig, taskCatalog } from "@trigger.dev/core/v3"; -import { - TracingDiagnosticLogLevel, - TracingSDK, - StandardTaskCatalog, -} from "@trigger.dev/core/v3/workers"; -import type { Logger } from "@opentelemetry/api-logs"; - -__SETUP_IMPORTED_PROJECT_CONFIG__; -declare const __SETUP_IMPORTED_PROJECT_CONFIG__: unknown; -declare const setupImportedConfig: ProjectConfig | undefined; - -export const tracingSDK = new TracingSDK({ - url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318", - instrumentations: setupImportedConfig?.instrumentations ?? [], - diagLogLevel: (process.env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none", - forceFlushTimeoutMillis: process.env.OTEL_FORCE_FLUSH_TIMEOUT - ? parseInt(process.env.OTEL_FORCE_FLUSH_TIMEOUT, 10) - : 5_000, -}); - -export const otelTracer: Tracer = tracingSDK.getTracer("trigger-prod-worker", packageJson.version); -export const otelLogger: Logger = tracingSDK.getLogger("trigger-prod-worker", packageJson.version); - -taskCatalog.setGlobalTaskCatalog(new StandardTaskCatalog()); diff --git a/packages/core/package.json b/packages/core/package.json index 9f2258965..3c1b48db7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -28,6 +28,7 @@ "./types": "./src/types.ts", "./versions": "./src/versions.ts", "./v3": "./src/v3/index.ts", + "./v3/build": "./src/v3/build/index.ts", "./v3/apps": "./src/v3/apps/index.ts", "./v3/errors": "./src/v3/errors.ts", "./v3/logger-api": "./src/v3/logger-api.ts", @@ -79,7 +80,7 @@ "humanize-duration": "^3.27.3", "socket.io-client": "4.7.5", "superjson": "^2.2.1", - "zod": "3.22.3", + "zod": "3.23.8", "zod-error": "1.5.0", "zod-validation-error": "^1.5.0", "execa": "^8.0.1" @@ -92,7 +93,10 @@ "rimraf": "^3.0.2", "socket.io": "4.7.4", "tshy": "^3.0.2", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "esbuild": "^0.23.0", + "defu": "^6.1.4", + "ts-essentials": "10.0.1" }, "engines": { "node": ">=18.20.0" @@ -231,6 +235,17 @@ "default": "./dist/commonjs/v3/index.js" } }, + "./v3/build": { + "import": { + "triggerdotdev-source": "./src/v3/build/index.ts", + "types": "./dist/esm/v3/build/index.d.ts", + "default": "./dist/esm/v3/build/index.js" + }, + "require": { + "types": "./dist/commonjs/v3/build/index.d.ts", + "default": "./dist/commonjs/v3/build/index.js" + } + }, "./v3/apps": { "import": { "triggerdotdev-source": "./src/v3/apps/index.ts", @@ -464,4 +479,4 @@ } }, "type": "module" -} \ No newline at end of file +} diff --git a/packages/core/src/consts.ts b/packages/core/src/consts.ts new file mode 100644 index 000000000..d66037787 --- /dev/null +++ b/packages/core/src/consts.ts @@ -0,0 +1 @@ +export const VERSION = "0.0.1"; // This is replaced by the build script diff --git a/packages/core/src/v3/apiClient/index.ts b/packages/core/src/v3/apiClient/index.ts index 26fc0e6b8..97664d4ee 100644 --- a/packages/core/src/v3/apiClient/index.ts +++ b/packages/core/src/v3/apiClient/index.ts @@ -1,6 +1,5 @@ import { context, propagation } from "@opentelemetry/api"; import { z } from "zod"; -import { version } from "../../../package.json"; import { AddTagsRequestBody, BatchTaskRunExecutionResult, @@ -44,6 +43,7 @@ import { ListRunsQueryParams, UpdateEnvironmentVariableParams, } from "./types.js"; +import { VERSION } from "../../consts.js"; export type { CreateEnvironmentVariableParams, @@ -503,7 +503,7 @@ export class ApiClient { const headers: Record = { "Content-Type": "application/json", Authorization: `Bearer ${this.accessToken}`, - "trigger-version": version, + "trigger-version": VERSION, }; // Only inject the context if we are inside a task diff --git a/packages/core/src/v3/build/extensions.ts b/packages/core/src/v3/build/extensions.ts new file mode 100644 index 000000000..be8e7ab0a --- /dev/null +++ b/packages/core/src/v3/build/extensions.ts @@ -0,0 +1,51 @@ +import { BuildManifest, BuildTarget } from "../schemas/build.js"; +import type { Plugin } from "esbuild"; +import { ResolvedConfig } from "./resolvedConfig.js"; + +export interface BuildExtension { + name: string; + externalsForTarget?: (target: BuildTarget) => string[] | undefined; + onBuildStart?: (context: BuildContext) => Promise | void; + onBuildComplete?: ( + context: BuildContext, + manifest: BuildManifest + ) => Promise | undefined | void; +} + +export interface BuildContext { + target: BuildTarget; + config: ResolvedConfig; + workingDir: string; + + addLayer(layer: BuildLayer): void; + registerPlugin(plugin: Plugin, options?: RegisterPluginOptions): void; + + /* + * Resolve a path relative to the working directory + */ + resolvePath(path: string): Promise; +} + +export interface BuildLayer { + id: string; + commands?: string[]; + files?: Record; + build?: { + env?: Record; + }; + deploy?: { + env?: Record; + }; + dependencies?: Record; +} + +export type PluginPlacement = "first" | "last"; + +export type RegisterPluginOptions = { + target?: BuildTarget; + placement?: PluginPlacement; +}; + +export type RegisteredPlugin = RegisterPluginOptions & { + plugin: Plugin; +}; \ No newline at end of file diff --git a/packages/core/src/v3/build/index.ts b/packages/core/src/v3/build/index.ts new file mode 100644 index 000000000..bf09ae87c --- /dev/null +++ b/packages/core/src/v3/build/index.ts @@ -0,0 +1,3 @@ +export * from "./extensions.js"; +export * from "./resolvedConfig.js" +export * from "./runtime.js"; \ No newline at end of file diff --git a/packages/core/src/v3/build/resolvedConfig.ts b/packages/core/src/v3/build/resolvedConfig.ts new file mode 100644 index 000000000..9c9f1406d --- /dev/null +++ b/packages/core/src/v3/build/resolvedConfig.ts @@ -0,0 +1,27 @@ +import { type Defu } from "defu"; +import type { Prettify } from "ts-essentials"; +import { TriggerConfig } from "../config.js"; +import { BuildRuntime } from "../schemas/config.js"; + +export type ResolvedConfig = Prettify< + Defu< + TriggerConfig, + [ + {}, + { + runtime: BuildRuntime; + dirs: string[]; + tsconfig: string; + build: { + jsx: { factory: string; fragment: string; automatic: true }; + } & Omit, "jsx">; + }, + ] + > & { + workingDir: string; + workspaceDir: string; + packageJsonPath: string; + lockfilePath: string; + configFile?: string; + } +>; \ No newline at end of file diff --git a/packages/core/src/v3/build/runtime.ts b/packages/core/src/v3/build/runtime.ts new file mode 100644 index 000000000..12f8c6bc9 --- /dev/null +++ b/packages/core/src/v3/build/runtime.ts @@ -0,0 +1,14 @@ +import { BuildRuntime } from "../schemas/config.js"; + +export const DEFAULT_RUNTIME: BuildRuntime = "node20"; + +export function binaryForRuntime(runtime: BuildRuntime): string { + switch (runtime) { + case "node20": + return "node"; + case "bun": + return "bun"; + default: + throw new Error(`Unsupported runtime ${runtime}`); + } +} \ No newline at end of file diff --git a/packages/core/src/v3/types/config.ts b/packages/core/src/v3/config.ts similarity index 58% rename from packages/core/src/v3/types/config.ts rename to packages/core/src/v3/config.ts index cc8d50b5c..54e33fe86 100644 --- a/packages/core/src/v3/types/config.ts +++ b/packages/core/src/v3/config.ts @@ -1,47 +1,29 @@ -import { FailureFnParams, InitFnParams, StartFnParams, SuccessFnParams } from "./index.js"; -import { LogLevel } from "../logger/taskLogger.js"; -import { MachinePresetName, RetryOptions } from "../schemas/index.js"; -import type { Instrumentation } from "@opentelemetry/instrumentation"; +import { Instrumentation } from "@opentelemetry/instrumentation"; +import { BuildRuntime } from "./schemas/config.js"; +import { BuildExtension } from "./build/extensions.js"; +import { MachinePresetName } from "./schemas/common.js"; +import { LogLevel } from "./logger/taskLogger.js"; +import { FailureFnParams, InitFnParams, StartFnParams, SuccessFnParams } from "./types/index.js"; +import { RetryOptions } from "./index.js"; -export interface ProjectConfig { +export type TriggerConfig = { + /** + * @default "node20" + */ + runtime?: BuildRuntime; project: string; - triggerDirectories?: string | string[]; - triggerUrl?: string; + dirs?: string[]; + instrumentations?: Array; + tsconfig?: string; retries?: { enabledInDev?: boolean; default?: RetryOptions; }; - additionalPackages?: string[]; - /** * The default machine preset to use for your deployed trigger.dev tasks. You can override this on a per-task basis. * @default "small-1x" */ machine?: MachinePresetName; - - /** - * List of additional files to include in your trigger.dev bundle. e.g. ["./prisma/schema.prisma"] - * - * Supports glob patterns. - * - * Note: The path separator for glob patterns is `/`, even on Windows! - */ - additionalFiles?: string[]; - /** - * List of patterns that determine if a module is included in your trigger.dev bundle. This is needed when consuming ESM only packages, since the trigger.dev bundle is currently built as a CJS module. - */ - dependenciesToBundle?: Array; - - /** - * The path to your project's tsconfig.json file. Will use tsconfig.json in the project directory if not provided. - */ - tsconfigPath?: string; - - /** - * The OpenTelemetry instrumentations to enable - */ - instrumentations?: Instrumentation[]; - /** * Set the log level for the logger. Defaults to "info", so you will see "log", "info", "warn", and "error" messages, but not "debug" messages. * @@ -55,6 +37,64 @@ export interface ProjectConfig { * Enable console logging while running the dev CLI. This will print out logs from console.log, console.warn, and console.error. By default all logs are sent to the trigger.dev backend, and not logged to the console. */ enableConsoleLogging?: boolean; + build?: { + extensions?: BuildExtension[]; + external?: string[]; + jsx?: { + /** + * @default "React.createElement" + */ + factory?: string; + /** + * @default "React.Fragment" + */ + fragment?: string; + + /** + * @default true + * @description Set the esbuild jsx option to automatic. Set this to false if you aren't using React. + * @see https://esbuild.github.io/api/#jsx + */ + automatic?: boolean; + }; + }; + deploy?: { + env?: Record; + }; + + /** + * @deprecated Use `dirs` instead + */ + triggerDirectories?: string[]; + + /** + * @deprecated Use the `additionalPackages` extension instead. + */ + additionalPackages?: string[]; + + /** + * @deprecated Use the `additionalFiles` extension instead. + */ + additionalFiles?: string[]; + + /** + * @deprecated Dependencies are now bundled by default. If you want to exclude some dependencies from the bundle, use the `build.external` option. + */ + dependenciesToBundle?: Array; + + /** + * @deprecated Use `tsconfig` instead. + */ + tsconfigPath?: string; + + /** + * CA Cert file to be added to NODE_EXTRA_CA_CERT environment variable in, useful in use with self signed cert in the trigger.dev environment. + * + * @example "./certs/ca.crt" + * Note: must start with "./" and be relative to the project root. + * + */ + extraCACerts?: string; /** * Run before a task is executed, for all tasks. This is useful for setting up any global state that is needed for all tasks. @@ -77,18 +117,7 @@ export interface ProjectConfig { onStart?: (payload: unknown, params: StartFnParams) => Promise; /** - * postInstall will run during the deploy build step, after all the dependencies have been installed. - * - * @example "prisma generate" + * @deprecated Use a custom build extension to add post install commands */ postInstall?: string; - - /** - * CA Cert file to be added to NODE_EXTRA_CA_CERT environment variable in, useful in use with self signed cert in the trigger.dev environment. - * - * @example "./certs/ca.crt" - * Note: must start with "./" and be relative to the project root. - * - */ - extraCACerts?: string; -} +}; diff --git a/packages/core/src/v3/index.ts b/packages/core/src/v3/index.ts index 7d0a88b87..812e3eed2 100644 --- a/packages/core/src/v3/index.ts +++ b/packages/core/src/v3/index.ts @@ -44,7 +44,6 @@ export { } from "./utils/retries.js"; export { accessoryAttributes } from "./utils/styleAttributes.js"; -export { detectDependencyVersion } from "./utils/detectDependencyVersion.js"; export { conditionallyExportPacket, conditionallyImportPacket, @@ -56,3 +55,5 @@ export { stringifyIO, type IOPacket, } from "./utils/ioSerialization.js"; + +export * from "./config.js"; diff --git a/packages/core/src/v3/otel/tracingSDK.ts b/packages/core/src/v3/otel/tracingSDK.ts index 09fb40f86..55a8a70dd 100644 --- a/packages/core/src/v3/otel/tracingSDK.ts +++ b/packages/core/src/v3/otel/tracingSDK.ts @@ -40,7 +40,7 @@ import { TaskContextSpanProcessor, } from "../taskContext/otelProcessors.js"; import { getEnvVar } from "../utils/getEnv.js"; -import { version } from "../../../package.json"; +import { VERSION } from "../../consts.js"; class AsyncResourceDetector implements DetectorSync { private _promise: Promise; @@ -112,7 +112,7 @@ export class TracingSDK { new Resource({ [SemanticResourceAttributes.CLOUD_PROVIDER]: "trigger.dev", [SemanticInternalAttributes.TRIGGER]: true, - [SemanticInternalAttributes.CLI_VERSION]: version, + [SemanticInternalAttributes.CLI_VERSION]: VERSION, }) ) .merge(config.resource ?? new Resource({})) diff --git a/packages/core/src/v3/schemas/build.ts b/packages/core/src/v3/schemas/build.ts new file mode 100644 index 000000000..ae4e2caa6 --- /dev/null +++ b/packages/core/src/v3/schemas/build.ts @@ -0,0 +1,170 @@ +import { z } from "zod"; +import { ConfigManifest } from "./config.js"; + +export const TaskFile = z.object({ + entry: z.string(), + out: z.string(), +}); + +export type TaskFile = z.infer; + +export const BuildExternal = z.object({ + name: z.string(), + version: z.string(), +}); + +export type BuildExternal = z.infer; + +export const BuildTarget = z.enum(["dev", "deploy"]); + +export type BuildTarget = z.infer; + +export const BuildRuntime = z.enum(["node20", "bun"]); + +export type BuildRuntime = z.infer; + +export const BuildManifest = z.object({ + target: BuildTarget, + runtime: BuildRuntime, + config: ConfigManifest, + files: z.array(TaskFile), + outputPath: z.string(), + workerEntryPath: z.string(), + workerForkPath: z.string(), + loaderPath: z.string().optional(), + configPath: z.string(), + externals: BuildExternal.array().optional(), + build: z.object({ + env: z.record(z.string()).optional(), + commands: z.array(z.string()).optional(), + }), + deploy: z.object({ + env: z.record(z.string()).optional(), + }), +}); + +export type BuildManifest = z.infer; + +export const IndexMessage = z.object({ + type: z.literal("index"), + data: z.object({ + build: BuildManifest, + }), +}); + +export type IndexMessage = z.infer; + +export const TaskManifest = z.object({ + id: z.string(), + exportName: z.string(), + file: TaskFile, +}); + +export type TaskManifest = z.infer; + +export const ExecuteTaskMessage = z.object({ + type: z.literal("execute-task"), + data: z.object({ + task: TaskManifest, + payload: z.unknown(), + projectRef: z.string(), + configPath: z.string(), + }), +}); + +export type ExecuteTaskMessage = z.infer; + +export const RunExecution = z.object({ + task: TaskManifest, + payload: z.unknown(), + projectRef: z.string(), + configPath: z.string(), + entryPath: z.string(), + loaderPath: z.string().optional(), + env: z.record(z.string()), + cwd: z.string().optional(), +}); + +export type RunExecution = z.infer; + +export const ParentToChildMessages = z.discriminatedUnion("type", [ + IndexMessage, + ExecuteTaskMessage, +]); + +export type ParentToChildMessages = z.infer; + +export const WorkerManifest = z.object({ + tasks: TaskManifest.array(), +}); + +export type WorkerManifest = z.infer; + +export const WorkerManifestMessage = z.object({ + type: z.literal("worker-manifest"), + data: z.object({ + manifest: WorkerManifest, + }), +}); + +export type WorkerManifestMessage = z.infer; + +export const FailedTaskCompletion = z.object({ + ok: z.literal(false), + error: z.object({ + message: z.string(), + stack: z.string().optional(), + name: z.string().optional(), + }), +}); + +export type FailedTaskCompletion = z.infer; + +export const SuccessfulTaskCompletion = z.object({ + ok: z.literal(true), + output: z.unknown(), +}); + +export type SuccessfulTaskCompletion = z.infer; + +export const TaskCompletion = z.discriminatedUnion("ok", [ + FailedTaskCompletion, + SuccessfulTaskCompletion, +]); + +export type TaskCompletion = z.infer; + +export const CompletedTask = z.object({ + id: z.string(), + completion: TaskCompletion, + spans: z.array(z.any()), +}); + +export type CompletedTask = z.infer; + +export const CompletedTaskMessage = z.object({ + type: z.literal("completed-task"), + data: CompletedTask, +}); + +export type CompletedTaskMessage = z.infer; + +export const ChildToParentMessages = z.discriminatedUnion("type", [ + WorkerManifestMessage, + CompletedTaskMessage, +]); + +export type ChildToParentMessages = z.infer; + +export const TriggerTaskResult = z.discriminatedUnion("ok", [ + z.object({ + ok: z.literal(true), + output: z.unknown(), + }), + z.object({ + ok: z.literal(false), + error: z.string(), + }), +]); + +export type TriggerTaskResult = z.infer; diff --git a/packages/core/src/v3/schemas/config.ts b/packages/core/src/v3/schemas/config.ts new file mode 100644 index 000000000..4be06e7b3 --- /dev/null +++ b/packages/core/src/v3/schemas/config.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; + +export const ConfigManifest = z.object({ + projectRef: z.string(), + dirs: z.string().array(), + external: z.string().array().optional(), +}); + +export type ConfigManifest = z.infer; + +export const BuildRuntime = z.enum(["node20", "bun"]); + +export type BuildRuntime = z.infer; diff --git a/packages/core/src/v3/schemas/index.ts b/packages/core/src/v3/schemas/index.ts index 2de70154f..4d8b8eae0 100644 --- a/packages/core/src/v3/schemas/index.ts +++ b/packages/core/src/v3/schemas/index.ts @@ -8,3 +8,4 @@ export * from "./style.js"; export * from "./fetch.js"; export * from "./eventFilter.js"; export * from "./openTelemetry.js"; +export * from "./config.js"; diff --git a/packages/core/src/v3/types/index.ts b/packages/core/src/v3/types/index.ts index 3afb9af05..8d67d2889 100644 --- a/packages/core/src/v3/types/index.ts +++ b/packages/core/src/v3/types/index.ts @@ -7,7 +7,6 @@ import { import { Prettify } from "./utils.js"; export * from "./utils.js"; -export * from "./config.js"; export type InitOutput = Record | void | undefined; diff --git a/packages/core/src/v3/utils/detectDependencyVersion.ts b/packages/core/src/v3/utils/detectDependencyVersion.ts deleted file mode 100644 index fa67fb21a..000000000 --- a/packages/core/src/v3/utils/detectDependencyVersion.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { dependencies } from "../../../package.json" - -export function detectDependencyVersion(dependency: string): string | undefined { - return (dependencies as Record)[dependency] -} \ No newline at end of file diff --git a/packages/core/src/v3/workers/taskExecutor.ts b/packages/core/src/v3/workers/taskExecutor.ts index 1e11bedcb..7aefc37e6 100644 --- a/packages/core/src/v3/workers/taskExecutor.ts +++ b/packages/core/src/v3/workers/taskExecutor.ts @@ -14,7 +14,7 @@ import { import { SemanticInternalAttributes } from "../semanticInternalAttributes.js"; import { taskContext } from "../task-context-api.js"; import { TriggerTracer } from "../tracer.js"; -import { HandleErrorFunction, ProjectConfig, TaskMetadataWithFunctions } from "../types/index.js"; +import { HandleErrorFunction, TaskMetadataWithFunctions } from "../types/index.js"; import { conditionallyExportPacket, conditionallyImportPacket, @@ -26,13 +26,14 @@ import { calculateNextRetryDelay } from "../utils/retries.js"; import { accessoryAttributes } from "../utils/styleAttributes.js"; import { UsageMeasurement } from "../usage/types.js"; import { ApiError, RateLimitError } from "../apiClient/errors.js"; +import { TriggerConfig } from "../index.js"; export type TaskExecutorOptions = { tracingSDK: TracingSDK; tracer: TriggerTracer; consoleInterceptor: ConsoleInterceptor; projectConfig: Config; - importedConfig: ProjectConfig | undefined; + importedConfig: TriggerConfig | undefined; handleErrorFn: HandleErrorFunction | undefined; }; @@ -41,7 +42,7 @@ export class TaskExecutor { private _tracer: TriggerTracer; private _consoleInterceptor: ConsoleInterceptor; private _config: Config; - private _importedConfig: ProjectConfig | undefined; + private _importedConfig: TriggerConfig | undefined; private _handleErrorFn: HandleErrorFunction | undefined; constructor( diff --git a/packages/emails/package.json b/packages/emails/package.json index 9f312bf85..51497c66d 100644 --- a/packages/emails/package.json +++ b/packages/emails/package.json @@ -15,7 +15,7 @@ "react-email": "^2.1.1", "resend": "^3.2.0", "tiny-invariant": "^1.2.0", - "zod": "3.22.3" + "zod": "3.23.8" }, "devDependencies": { "@trigger.dev/tsconfig": "workspace:*", diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index 63846950c..8cc84ec67 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -48,7 +48,7 @@ "ulid": "^2.3.0", "uuid": "^9.0.0", "ws": "^8.11.0", - "zod": "3.22.3", + "zod": "3.23.8", "msw": "^2.2.1" }, "devDependencies": { diff --git a/packages/trigger-sdk/src/v3/config.ts b/packages/trigger-sdk/src/v3/config.ts index a3ef9c324..3bf32ac66 100644 --- a/packages/trigger-sdk/src/v3/config.ts +++ b/packages/trigger-sdk/src/v3/config.ts @@ -1,8 +1,15 @@ +import type { TriggerConfig } from "@trigger.dev/core/v3"; + export type { - ProjectConfig as TriggerConfig, HandleErrorArgs, HandleErrorFunction, ResolveEnvironmentVariablesFunction, ResolveEnvironmentVariablesParams, ResolveEnvironmentVariablesResult, } from "@trigger.dev/core/v3"; + +export function defineConfig(config: TriggerConfig): TriggerConfig { + return config; +} + +export type { TriggerConfig }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f638bd8d4..7c585c0a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -171,8 +171,8 @@ importers: specifier: ^2.2.1 version: 2.2.1 zod: - specifier: 3.22.3 - version: 3.22.3 + specifier: 3.23.8 + version: 3.23.8 zod-error: specifier: 1.5.0 version: 1.5.0 @@ -230,7 +230,7 @@ importers: version: 0.6.1(react@18.2.0) '@conform-to/zod': specifier: ^0.6.1 - version: 0.6.1(@conform-to/dom@0.6.1)(zod@3.22.3) + version: 0.6.1(@conform-to/dom@0.6.1)(zod@3.23.8) '@depot/sdk-node': specifier: ^0.5.0 version: 0.5.0 @@ -566,7 +566,7 @@ importers: version: 0.3.1(@remix-run/react@2.1.0)(@remix-run/server-runtime@2.1.0)(react@18.2.0) remix-utils: specifier: ^7.1.0 - version: 7.1.0(@remix-run/node@2.1.0)(@remix-run/react@2.1.0)(@remix-run/router@1.15.3)(intl-parse-accept-language@1.0.0)(react@18.2.0)(zod@3.22.3) + version: 7.1.0(@remix-run/node@2.1.0)(@remix-run/react@2.1.0)(@remix-run/router@1.15.3)(intl-parse-accept-language@1.0.0)(react@18.2.0)(zod@3.23.8) seedrandom: specifier: ^3.0.5 version: 3.0.5 @@ -622,14 +622,14 @@ importers: specifier: ^8.11.0 version: 8.12.0 zod: - specifier: 3.22.3 - version: 3.22.3 + specifier: 3.23.8 + version: 3.23.8 zod-error: specifier: 1.5.0 version: 1.5.0 zod-validation-error: specifier: ^1.5.0 - version: 1.5.0(zod@3.22.3) + version: 1.5.0(zod@3.23.8) devDependencies: '@remix-run/dev': specifier: 2.1.0 @@ -851,7 +851,7 @@ importers: dependencies: '@anatine/esbuild-decorators': specifier: ^0.2.19 - version: 0.2.19(esbuild@0.19.11) + version: 0.2.19(esbuild@0.23.0) '@clack/prompts': specifier: ^0.7.0 version: 0.7.0 @@ -900,6 +900,12 @@ importers: '@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 @@ -912,15 +918,18 @@ importers: commander: specifier: ^9.4.1 version: 9.5.0 + defu: + specifier: ^6.1.4 + version: 6.1.4 degit: specifier: ^2.8.4 version: 2.8.4 dotenv: - specifier: ^16.4.4 - version: 16.4.4 + specifier: ^16.4.5 + version: 16.4.5 esbuild: - specifier: ^0.19.11 - version: 0.19.11 + specifier: ^0.23.0 + version: 0.23.0 evt: specifier: ^2.4.13 version: 2.4.13 @@ -931,14 +940,23 @@ importers: specifier: ^7.0.0 version: 7.0.0 glob: - specifier: ^10.3.10 - version: 10.3.10 + specifier: ^11.0.0 + version: 11.0.0 + glob-to-regexp: + specifier: ^0.4.1 + version: 0.4.1 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 import-meta-resolve: - specifier: ^4.0.0 - version: 4.0.0 + specifier: ^4.1.0 + version: 4.1.0 ink: specifier: ^4.4.1 version: 4.4.1(@types/react@18.2.48)(react@18.2.0) @@ -948,6 +966,12 @@ importers: liquidjs: specifier: ^10.9.2 version: 10.9.3 + magicast: + specifier: ^0.3.4 + version: 0.3.4 + mlly: + specifier: ^1.7.1 + version: 1.7.1 mock-fs: specifier: ^5.2.0 version: 5.2.0 @@ -966,9 +990,15 @@ importers: p-throttle: specifier: ^6.1.0 version: 6.1.0 + package-json-from-dist: + specifier: ^1.0.0 + version: 1.0.0 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 @@ -978,14 +1008,20 @@ importers: react-error-boundary: specifier: ^4.0.12 version: 4.0.12(react@18.2.0) + 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 + specifier: 0.5.21 version: 0.5.21 terminal-link: specifier: ^3.0.0 @@ -999,6 +1035,9 @@ importers: 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 @@ -1009,11 +1048,11 @@ importers: specifier: ^8.12.0 version: 8.12.0 zod: - specifier: 3.22.3 - version: 3.22.3 + specifier: 3.23.8 + version: 3.23.8 zod-validation-error: specifier: ^1.5.0 - version: 1.5.0(zod@3.22.3) + version: 1.5.0(zod@3.23.8) devDependencies: '@types/gradient-string': specifier: ^1.1.2 @@ -1054,6 +1093,9 @@ importers: rimraf: specifier: ^5.0.7 version: 5.0.7 + ts-essentials: + specifier: 10.0.1 + version: 10.0.1(typescript@5.4.5) tshy: specifier: ^3.0.2 version: 3.0.2 @@ -1118,14 +1160,14 @@ importers: specifier: ^2.2.1 version: 2.2.1 zod: - specifier: 3.22.3 - version: 3.22.3 + specifier: 3.23.8 + version: 3.23.8 zod-error: specifier: 1.5.0 version: 1.5.0 zod-validation-error: specifier: ^1.5.0 - version: 1.5.0(zod@3.22.3) + version: 1.5.0(zod@3.23.8) devDependencies: '@types/humanize-duration': specifier: ^3.27.1 @@ -1136,12 +1178,21 @@ importers: '@types/readable-stream': specifier: ^4.0.14 version: 4.0.14 + defu: + specifier: ^6.1.4 + version: 6.1.4 + esbuild: + specifier: ^0.23.0 + version: 0.23.0 rimraf: specifier: ^3.0.2 version: 3.0.2 socket.io: specifier: 4.7.4 version: 4.7.4 + ts-essentials: + specifier: 10.0.1 + version: 10.0.1(typescript@5.5.4) tshy: specifier: ^3.0.2 version: 3.0.2 @@ -1186,8 +1237,8 @@ importers: specifier: ^1.2.0 version: 1.3.1 zod: - specifier: 3.22.3 - version: 3.22.3 + specifier: 3.23.8 + version: 3.23.8 devDependencies: '@trigger.dev/tsconfig': specifier: workspace:* @@ -1237,7 +1288,7 @@ importers: version: 1.167.3 tsup: specifier: ^8.0.1 - version: 8.0.1(patch_hash=a5ztaafw5l4qfghy2hjjuynb34)(postcss@8.4.38)(ts-node@10.9.2)(typescript@5.3.3) + version: 8.0.1(patch_hash=a5ztaafw5l4qfghy2hjjuynb34)(postcss@8.4.38)(typescript@5.3.3) typescript: specifier: ^5.3.0 version: 5.3.3 @@ -1296,8 +1347,8 @@ importers: specifier: ^8.11.0 version: 8.12.0 zod: - specifier: 3.22.3 - version: 3.22.3 + specifier: 3.23.8 + version: 3.23.8 devDependencies: '@types/debug': specifier: ^4.1.7 @@ -1355,15 +1406,12 @@ importers: version: 2.2.1 '@t3-oss/env-nextjs': specifier: ^0.10.1 - version: 0.10.1(typescript@5.3.3)(zod@3.22.3) + version: 0.10.1(typescript@5.5.4)(zod@3.23.8) '@traceloop/instrumentation-openai': specifier: ^0.3.9 version: 0.3.9(@opentelemetry/api@1.4.1) - '@trigger.dev/core': - specifier: workspace:^3.0.0-beta.0 - version: link:../../packages/core '@trigger.dev/sdk': - specifier: workspace:^3.0.0-beta.0 + specifier: workspace:* version: link:../../packages/trigger-sdk dotenv: specifier: ^16.4.5 @@ -1373,7 +1421,7 @@ importers: version: 8.0.1 msw: specifier: ^2.2.1 - version: 2.2.1(typescript@5.3.3) + version: 2.2.1(typescript@5.5.4) openai: specifier: ^4.47.0 version: 4.47.1 @@ -1402,8 +1450,8 @@ importers: specifier: ^2.3.12 version: 2.3.12 zod: - specifier: 3.22.3 - version: 3.22.3 + specifier: 3.23.8 + version: 3.23.8 devDependencies: '@opentelemetry/core': specifier: ^1.22.0 @@ -1447,9 +1495,6 @@ importers: '@opentelemetry/semantic-conventions': specifier: ^1.22.0 version: 1.22.0 - '@trigger.dev/tsconfig': - specifier: workspace:* - version: link:../../config-packages/tsconfig '@types/node': specifier: 20.4.2 version: 20.4.2 @@ -1464,16 +1509,13 @@ importers: version: link:../../packages/cli-v3 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.4.2)(typescript@5.3.3) + version: 10.9.2(@types/node@20.4.2)(typescript@5.5.4) tsconfig-paths: specifier: ^4.2.0 version: 4.2.0 - tsup: - specifier: ^8.0.1 - version: 8.0.1(patch_hash=a5ztaafw5l4qfghy2hjjuynb34)(postcss@8.4.38)(ts-node@10.9.2)(typescript@5.3.3) typescript: - specifier: ^5.3.0 - version: 5.3.3 + specifier: ^5.5.4 + version: 5.5.4 packages: @@ -1501,12 +1543,12 @@ packages: '@jridgewell/trace-mapping': 0.3.19 dev: true - /@anatine/esbuild-decorators@0.2.19(esbuild@0.19.11): + /@anatine/esbuild-decorators@0.2.19(esbuild@0.23.0): resolution: {integrity: sha512-pyj6ULyMacyzpDqlnbS2OCkOqxcVgk8IqiTMRJ5CrsF8Yl1azvlX/AM6xWR8UzHKUYDlWOw5mOpos3+7KKR0Lw==} peerDependencies: esbuild: ~0.14.29 dependencies: - esbuild: 0.19.11 + esbuild: 0.23.0 dev: false /@ariakit/core@0.4.6: @@ -2083,7 +2125,7 @@ packages: resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-builder-binary-assignment-operator-visitor@7.18.9: @@ -2091,7 +2133,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/helper-explode-assignable-expression': 7.18.6 - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-compilation-targets@7.22.15: @@ -2206,7 +2248,7 @@ packages: resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-function-name@7.22.5: @@ -2214,7 +2256,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.22.15 - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-function-name@7.23.0: @@ -2222,7 +2264,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.22.15 - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-function-name@7.24.7: @@ -2237,7 +2279,7 @@ packages: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-hoist-variables@7.24.7: @@ -2251,21 +2293,21 @@ packages: resolution: {integrity: sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-member-expression-to-functions@7.23.0: resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-module-imports@7.22.15: resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-module-transforms@7.22.17(@babel/core@7.22.17): @@ -2279,21 +2321,21 @@ packages: '@babel/helper-module-imports': 7.22.15 '@babel/helper-simple-access': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.24.7 dev: true /@babel/helper-optimise-call-expression@7.18.6: resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-optimise-call-expression@7.22.5: resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-plugin-utils@7.22.5: @@ -2316,7 +2358,7 @@ packages: '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-wrap-function': 7.20.5 - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 transitivePeerDependencies: - supports-color dev: true @@ -2330,7 +2372,7 @@ packages: '@babel/helper-optimise-call-expression': 7.22.5 '@babel/template': 7.22.15 '@babel/traverse': 7.24.7 - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 transitivePeerDependencies: - supports-color dev: true @@ -2351,28 +2393,28 @@ packages: resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-skip-transparent-expression-wrappers@7.20.0: resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-skip-transparent-expression-wrappers@7.22.5: resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/helper-split-export-declaration@7.24.7: @@ -2382,23 +2424,13 @@ packages: '@babel/types': 7.24.7 dev: true - /@babel/helper-string-parser@7.23.4: - resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} - engines: {node: '>=6.9.0'} - /@babel/helper-string-parser@7.24.7: resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} engines: {node: '>=6.9.0'} - dev: true - - /@babel/helper-validator-identifier@7.22.20: - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} - engines: {node: '>=6.9.0'} /@babel/helper-validator-identifier@7.24.7: resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-option@7.22.15: resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==} @@ -2412,7 +2444,7 @@ packages: '@babel/helper-function-name': 7.23.0 '@babel/template': 7.22.15 '@babel/traverse': 7.24.7 - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 transitivePeerDependencies: - supports-color dev: true @@ -2423,7 +2455,7 @@ packages: dependencies: '@babel/template': 7.22.15 '@babel/traverse': 7.24.7 - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 transitivePeerDependencies: - supports-color dev: true @@ -2432,7 +2464,7 @@ packages: resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 @@ -2459,7 +2491,7 @@ packages: engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: false /@babel/parser@7.24.7: @@ -2467,8 +2499,7 @@ packages: engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.24.0 - dev: true + '@babel/types': 7.24.7 /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.22.17): resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} @@ -3109,7 +3140,7 @@ packages: '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-module-transforms': 7.22.17(@babel/core@7.22.17) '@babel/helper-plugin-utils': 7.24.0 - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.24.7 dev: true /@babel/plugin-transform-modules-umd@7.18.6(@babel/core@7.22.17): @@ -3206,7 +3237,7 @@ packages: '@babel/helper-module-imports': 7.22.15 '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.17) - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/plugin-transform-react-pure-annotations@7.18.6(@babel/core@7.22.17): @@ -3439,7 +3470,7 @@ packages: '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.22.17) '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.22.17) - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 esutils: 2.0.3 dev: true @@ -3497,7 +3528,7 @@ packages: dependencies: '@babel/code-frame': 7.22.13 '@babel/parser': 7.24.7 - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@babel/template@7.24.7: @@ -3549,9 +3580,10 @@ packages: resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.23.4 - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-string-parser': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + dev: true /@babel/types@7.24.7: resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} @@ -3560,7 +3592,6 @@ packages: '@babel/helper-string-parser': 7.24.7 '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - dev: true /@bcoe/v8-coverage@0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -3966,14 +3997,14 @@ packages: react: 18.2.0 dev: false - /@conform-to/zod@0.6.1(@conform-to/dom@0.6.1)(zod@3.22.3): + /@conform-to/zod@0.6.1(@conform-to/dom@0.6.1)(zod@3.23.8): resolution: {integrity: sha512-VYu44VfVeP0VyMrc2sNBagFAS66luZMIeFOfkHndGs1ep+LcR3Z4D+EOEqLZ2ECexmg9Y6nrX8Y2d5UUigEXCg==} peerDependencies: '@conform-to/dom': 0.6.1 zod: ^3.21.0 dependencies: '@conform-to/dom': 0.6.1 - zod: 3.22.3 + zod: 3.23.8 dev: false /@connectrpc/connect-node@0.13.2(@bufbuild/protobuf@1.7.2): @@ -4181,6 +4212,14 @@ packages: dev: true optional: true + /@esbuild/aix-ppc64@0.23.0: + resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + optional: true + /@esbuild/android-arm64@0.16.17: resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} engines: {node: '>=12'} @@ -4243,6 +4282,14 @@ packages: dev: true optional: true + /@esbuild/android-arm64@0.23.0: + resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + /@esbuild/android-arm@0.15.18: resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} engines: {node: '>=12'} @@ -4314,6 +4361,14 @@ packages: dev: true optional: true + /@esbuild/android-arm@0.23.0: + resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + /@esbuild/android-x64@0.16.17: resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} engines: {node: '>=12'} @@ -4376,6 +4431,14 @@ packages: dev: true optional: true + /@esbuild/android-x64@0.23.0: + resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + requiresBuild: true + optional: true + /@esbuild/darwin-arm64@0.16.17: resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} engines: {node: '>=12'} @@ -4438,6 +4501,14 @@ packages: dev: true optional: true + /@esbuild/darwin-arm64@0.23.0: + resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + /@esbuild/darwin-x64@0.16.17: resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} engines: {node: '>=12'} @@ -4500,6 +4571,14 @@ packages: dev: true optional: true + /@esbuild/darwin-x64@0.23.0: + resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + /@esbuild/freebsd-arm64@0.16.17: resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} engines: {node: '>=12'} @@ -4562,6 +4641,14 @@ packages: dev: true optional: true + /@esbuild/freebsd-arm64@0.23.0: + resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + /@esbuild/freebsd-x64@0.16.17: resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} engines: {node: '>=12'} @@ -4624,6 +4711,14 @@ packages: dev: true optional: true + /@esbuild/freebsd-x64@0.23.0: + resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + /@esbuild/linux-arm64@0.16.17: resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} engines: {node: '>=12'} @@ -4686,6 +4781,14 @@ packages: dev: true optional: true + /@esbuild/linux-arm64@0.23.0: + resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-arm@0.16.17: resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} engines: {node: '>=12'} @@ -4748,6 +4851,14 @@ packages: dev: true optional: true + /@esbuild/linux-arm@0.23.0: + resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-ia32@0.16.17: resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} engines: {node: '>=12'} @@ -4810,6 +4921,14 @@ packages: dev: true optional: true + /@esbuild/linux-ia32@0.23.0: + resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-loong64@0.15.18: resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} engines: {node: '>=12'} @@ -4881,6 +5000,14 @@ packages: dev: true optional: true + /@esbuild/linux-loong64@0.23.0: + resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-mips64el@0.16.17: resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} engines: {node: '>=12'} @@ -4943,6 +5070,14 @@ packages: dev: true optional: true + /@esbuild/linux-mips64el@0.23.0: + resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-ppc64@0.16.17: resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} engines: {node: '>=12'} @@ -5005,6 +5140,14 @@ packages: dev: true optional: true + /@esbuild/linux-ppc64@0.23.0: + resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-riscv64@0.16.17: resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} engines: {node: '>=12'} @@ -5067,6 +5210,14 @@ packages: dev: true optional: true + /@esbuild/linux-riscv64@0.23.0: + resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-s390x@0.16.17: resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} engines: {node: '>=12'} @@ -5129,6 +5280,14 @@ packages: dev: true optional: true + /@esbuild/linux-s390x@0.23.0: + resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-x64@0.16.17: resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} engines: {node: '>=12'} @@ -5191,6 +5350,14 @@ packages: dev: true optional: true + /@esbuild/linux-x64@0.23.0: + resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/netbsd-x64@0.16.17: resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} engines: {node: '>=12'} @@ -5253,6 +5420,22 @@ packages: dev: true optional: true + /@esbuild/netbsd-x64@0.23.0: + resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + optional: true + + /@esbuild/openbsd-arm64@0.23.0: + resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + requiresBuild: true + optional: true + /@esbuild/openbsd-x64@0.16.17: resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} engines: {node: '>=12'} @@ -5315,6 +5498,14 @@ packages: dev: true optional: true + /@esbuild/openbsd-x64@0.23.0: + resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + optional: true + /@esbuild/sunos-x64@0.16.17: resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} engines: {node: '>=12'} @@ -5377,6 +5568,14 @@ packages: dev: true optional: true + /@esbuild/sunos-x64@0.23.0: + resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + requiresBuild: true + optional: true + /@esbuild/win32-arm64@0.16.17: resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} engines: {node: '>=12'} @@ -5439,6 +5638,14 @@ packages: dev: true optional: true + /@esbuild/win32-arm64@0.23.0: + resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + /@esbuild/win32-ia32@0.16.17: resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} engines: {node: '>=12'} @@ -5501,6 +5708,14 @@ packages: dev: true optional: true + /@esbuild/win32-ia32@0.23.0: + resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + /@esbuild/win32-x64@0.16.17: resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} engines: {node: '>=12'} @@ -5563,6 +5778,14 @@ packages: dev: true optional: true + /@esbuild/win32-x64@0.23.0: + resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.31.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -12089,7 +12312,7 @@ packages: defer-to-connect: 1.1.3 dev: true - /@t3-oss/env-core@0.10.1(typescript@5.3.3)(zod@3.22.3): + /@t3-oss/env-core@0.10.1(typescript@5.5.4)(zod@3.23.8): resolution: {integrity: sha512-GcKZiCfWks5CTxhezn9k5zWX3sMDIYf6Kaxy2Gx9YEQftFcz8hDRN56hcbylyAO3t4jQnQ5ifLawINsNgCDpOg==} peerDependencies: typescript: '>=5.0.0' @@ -12098,11 +12321,11 @@ packages: typescript: optional: true dependencies: - typescript: 5.3.3 - zod: 3.22.3 + typescript: 5.5.4 + zod: 3.23.8 dev: false - /@t3-oss/env-nextjs@0.10.1(typescript@5.3.3)(zod@3.22.3): + /@t3-oss/env-nextjs@0.10.1(typescript@5.5.4)(zod@3.23.8): resolution: {integrity: sha512-iy2qqJLnFh1RjEWno2ZeyTu0ufomkXruUsOZludzDIroUabVvHsrSjtkHqwHp1/pgPUzN3yBRHMILW162X7x2Q==} peerDependencies: typescript: '>=5.0.0' @@ -12111,9 +12334,9 @@ packages: typescript: optional: true dependencies: - '@t3-oss/env-core': 0.10.1(typescript@5.3.3)(zod@3.22.3) - typescript: 5.3.3 - zod: 3.22.3 + '@t3-oss/env-core': 0.10.1(typescript@5.5.4)(zod@3.23.8) + typescript: 5.5.4 + zod: 3.23.8 dev: false /@tabler/icons-react@2.40.0(react@18.2.0): @@ -12179,7 +12402,7 @@ packages: dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.6.0) graphql: 16.6.0 - zod: 3.22.3 + zod: 3.23.8 dev: false /@testing-library/dom@8.19.1: @@ -12277,7 +12500,7 @@ packages: resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==} dependencies: '@babel/parser': 7.24.7 - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.20.6 @@ -12286,20 +12509,20 @@ packages: /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: '@babel/parser': 7.24.7 - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@types/babel__traverse@7.20.6: resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: true /@types/bcryptjs@2.4.2: @@ -13104,7 +13327,7 @@ packages: find-up: 5.0.0 javascript-stringify: 2.1.0 lodash: 4.17.21 - mlly: 1.4.2 + mlly: 1.7.1 outdent: 0.8.0 vite: 4.4.9(@types/node@18.11.18) vite-node: 0.28.5(@types/node@18.11.18) @@ -13441,6 +13664,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + /acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + /acorn@8.8.1: resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==} engines: {node: '>=0.4.0'} @@ -13835,6 +14063,10 @@ 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==} @@ -13996,7 +14228,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/template': 7.22.15 - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 '@types/babel__core': 7.20.1 '@types/babel__traverse': 7.20.6 dev: true @@ -14007,7 +14239,7 @@ packages: dependencies: '@babel/runtime': 7.22.5 cosmiconfig: 7.1.0 - resolve: 1.22.4 + resolve: 1.22.8 dev: true /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.22.17): @@ -14145,7 +14377,7 @@ packages: resolution: {integrity: sha512-fdRxJkQ9MUSEi4jH2DcV3FAPFktk0wefilxrwNyUuWpoWawQGN7G7cB+fOYTtFfI6XNkFgwqJ/D3G18BoJJ/jg==} engines: {node: '>= 10.0.0'} dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 dev: false /bail@2.0.2: @@ -14420,6 +14652,29 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + /c12@1.11.1(magicast@0.3.4): + resolution: {integrity: sha512-KDU0TvSvVdaYcQKQ6iPHATGz/7p/KiVjPg4vQrB6Jg/wX9R0yl5RZxWm9IoZqaIHD2+6PZd81+KMGwRr/lRIUg==} + peerDependencies: + magicast: ^0.3.4 + peerDependenciesMeta: + magicast: + optional: true + dependencies: + chokidar: 3.6.0 + confbox: 0.1.7 + defu: 6.1.4 + dotenv: 16.4.5 + giget: 1.2.3 + jiti: 1.21.6 + magicast: 0.3.4 + mlly: 1.7.1 + ohash: 1.1.3 + pathe: 1.1.2 + perfect-debounce: 1.0.0 + pkg-types: 1.1.3 + rc9: 2.1.2 + dev: false + /cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -14473,7 +14728,7 @@ packages: /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: - function-bind: 1.1.1 + function-bind: 1.1.2 get-intrinsic: 1.1.3 /call-bind@1.0.7: @@ -14680,6 +14935,12 @@ packages: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} + /citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + dependencies: + consola: 3.2.3 + dev: false + /cjs-module-lexer@1.2.3: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} @@ -14985,6 +15246,9 @@ packages: kind-of: 3.2.2 dev: false + /confbox@0.1.7: + resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + /config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} dependencies: @@ -14992,6 +15256,11 @@ packages: proto-list: 1.2.4 dev: false + /consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + dev: false + /content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -15665,6 +15934,9 @@ packages: resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} dev: false + /defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + /degenerator@5.0.1: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} @@ -15698,6 +15970,10 @@ packages: engines: {node: '>=6'} dev: true + /destr@2.0.3: + resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + dev: false + /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -15829,6 +16105,7 @@ packages: /dotenv@16.4.4: resolution: {integrity: sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==} engines: {node: '>=12'} + dev: true /dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} @@ -16011,7 +16288,7 @@ packages: call-bind: 1.0.2 es-set-tostringtag: 2.0.1 es-to-primitive: 1.2.1 - function-bind: 1.1.1 + function-bind: 1.1.2 function.prototype.name: 1.1.5 get-intrinsic: 1.1.3 get-symbol-description: 1.0.0 @@ -16597,6 +16874,37 @@ packages: '@esbuild/win32-x64': 0.20.2 dev: true + /esbuild@0.23.0: + resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.0 + '@esbuild/android-arm': 0.23.0 + '@esbuild/android-arm64': 0.23.0 + '@esbuild/android-x64': 0.23.0 + '@esbuild/darwin-arm64': 0.23.0 + '@esbuild/darwin-x64': 0.23.0 + '@esbuild/freebsd-arm64': 0.23.0 + '@esbuild/freebsd-x64': 0.23.0 + '@esbuild/linux-arm': 0.23.0 + '@esbuild/linux-arm64': 0.23.0 + '@esbuild/linux-ia32': 0.23.0 + '@esbuild/linux-loong64': 0.23.0 + '@esbuild/linux-mips64el': 0.23.0 + '@esbuild/linux-ppc64': 0.23.0 + '@esbuild/linux-riscv64': 0.23.0 + '@esbuild/linux-s390x': 0.23.0 + '@esbuild/linux-x64': 0.23.0 + '@esbuild/netbsd-x64': 0.23.0 + '@esbuild/openbsd-arm64': 0.23.0 + '@esbuild/openbsd-x64': 0.23.0 + '@esbuild/sunos-x64': 0.23.0 + '@esbuild/win32-arm64': 0.23.0 + '@esbuild/win32-ia32': 0.23.0 + '@esbuild/win32-x64': 0.23.0 + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -16673,7 +16981,7 @@ packages: dependencies: debug: 3.2.7(supports-color@5.5.0) is-core-module: 2.13.0 - resolve: 1.22.4 + resolve: 1.22.8 transitivePeerDependencies: - supports-color dev: true @@ -16887,7 +17195,7 @@ packages: eslint-utils: 2.1.0 ignore: 5.2.4 minimatch: 3.1.2 - resolve: 1.22.2 + resolve: 1.22.8 semver: 6.3.1 dev: true @@ -17866,7 +18174,7 @@ packages: resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - minipass: 7.0.3 + minipass: 7.1.2 dev: true /fs.realpath@1.0.0: @@ -17902,7 +18210,6 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: true /function.prototype.name@1.1.5: resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} @@ -17952,7 +18259,7 @@ packages: /get-intrinsic@1.1.3: resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==} dependencies: - function-bind: 1.1.1 + function-bind: 1.1.2 has: 1.0.3 has-symbols: 1.0.3 @@ -18063,6 +18370,20 @@ packages: assert-plus: 1.0.0 dev: false + /giget@1.2.3: + resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} + hasBin: true + dependencies: + citty: 0.1.6 + consola: 3.2.3 + defu: 6.1.4 + node-fetch-native: 1.6.4 + nypm: 0.3.9 + ohash: 1.1.3 + pathe: 1.1.2 + tar: 6.2.1 + dev: false + /git-remote-origin-url@4.0.0: resolution: {integrity: sha512-EAxDksNdjuWgmVW9pVvA9jQDi/dmTaiDONktIy7qiRRhBZUI4FQK1YvBvteuTSX24aNKg9lfgxNYJEeeSXe6DA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -18115,7 +18436,7 @@ packages: foreground-child: 3.1.1 jackspeak: 2.3.6 minimatch: 9.0.3 - minipass: 7.0.3 + minipass: 7.1.2 path-scurry: 1.10.1 /glob@10.3.4: @@ -18141,10 +18462,10 @@ 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==} + deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -18166,6 +18487,7 @@ packages: /glob@8.0.3: resolution: {integrity: sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -18457,7 +18779,6 @@ packages: engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - dev: true /hast-util-to-estree@2.1.0: resolution: {integrity: sha512-Vwch1etMRmm89xGgz+voWXvVHba2iiMdGMKmaMfYt35rbVtFDq8JNwwAIvi8zHMkO6Gvqo9oTMwJTmzVRfXh4g==} @@ -18497,6 +18818,11 @@ 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==} @@ -18706,6 +19032,15 @@ packages: module-details-from-path: 1.0.3 dev: true + /import-in-the-middle@1.9.1: + resolution: {integrity: sha512-E+3tEOutU1MV0mxhuCwfSPNNWRkbTJ3/YyL5be+blNIbHwZc53uYHQfuIhAU77xWR0BoF2eT7cqDJ6VlU5APPg==} + dependencies: + acorn: 8.10.0 + acorn-import-attributes: 1.9.5(acorn@8.10.0) + cjs-module-lexer: 1.2.3 + module-details-from-path: 1.0.3 + dev: false + /import-local@3.1.0: resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} engines: {node: '>=8'} @@ -18715,8 +19050,8 @@ packages: resolve-cwd: 3.0.0 dev: true - /import-meta-resolve@4.0.0: - resolution: {integrity: sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==} + /import-meta-resolve@4.1.0: + resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} dev: false /imurmurhash@0.1.4: @@ -18982,7 +19317,6 @@ packages: engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 - dev: true /is-data-descriptor@0.1.4: resolution: {integrity: sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==} @@ -19439,7 +19773,6 @@ 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==} @@ -19806,7 +20139,7 @@ packages: '@babel/generator': 7.22.15 '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.17) '@babel/plugin-syntax-typescript': 7.21.4(@babel/core@7.22.17) - '@babel/types': 7.24.0 + '@babel/types': 7.24.7 '@jest/expect-utils': 29.6.2 '@jest/transform': 29.6.2 '@jest/types': 29.6.1 @@ -19907,6 +20240,11 @@ packages: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true + /jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true + dev: false + /joi@17.7.0: resolution: {integrity: sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==} dependencies: @@ -20045,6 +20383,7 @@ packages: /jsonc-parser@3.2.1: resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + dev: false /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -20237,8 +20576,8 @@ packages: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} dependencies: - mlly: 1.4.2 - pkg-types: 1.0.3 + mlly: 1.7.1 + pkg-types: 1.1.3 dev: true /locate-path@5.0.0: @@ -20356,7 +20695,6 @@ 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==} @@ -20411,6 +20749,14 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /magicast@0.3.4: + resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==} + dependencies: + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + source-map-js: 1.2.0 + dev: false + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -21016,7 +21362,7 @@ packages: workerd: 1.20240512.0 ws: 8.16.0 youch: 3.3.3 - zod: 3.22.3 + zod: 3.23.8 transitivePeerDependencies: - bufferutil - supports-color @@ -21036,7 +21382,6 @@ packages: engines: {node: 20 || >=22} dependencies: brace-expansion: 2.0.1 - dev: true /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -21113,9 +21458,15 @@ packages: resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} engines: {node: '>=8'} + /minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + dev: false + /minipass@7.0.3: resolution: {integrity: sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==} engines: {node: '>=16 || 14 >=14.17'} + dev: true /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} @@ -21169,14 +21520,13 @@ packages: hasBin: true dev: true - /mlly@1.4.2: - resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} + /mlly@1.7.1: + resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} dependencies: - acorn: 8.10.0 - pathe: 1.1.1 - pkg-types: 1.0.3 - ufo: 1.3.0 - dev: true + acorn: 8.12.1 + pathe: 1.1.2 + pkg-types: 1.1.3 + ufo: 1.5.4 /mock-fs@5.2.0: resolution: {integrity: sha512-2dF2R6YMSZbpip1V1WHKGLNjr/k48uQClqMVb5H3MOvwc9qhYis3/IWbj02qIg/Y8MDXKFF4c5v0rxx2o6xTZw==} @@ -21216,37 +21566,6 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /msw@2.2.1(typescript@5.3.3): - resolution: {integrity: sha512-DCsZAQwan+2onEcpD86fiEnCKW4IvYzqcwDq/2TIoeNrmBqNp/mJW4wHQyxcoYrRPwgujin7wDFflqiSO1iT/w==} - engines: {node: '>=18'} - hasBin: true - requiresBuild: true - peerDependencies: - typescript: '>= 4.7.x <= 5.3.x' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@bundled-es-modules/cookie': 2.0.0 - '@bundled-es-modules/statuses': 1.0.1 - '@inquirer/confirm': 3.0.0 - '@mswjs/cookies': 1.1.0 - '@mswjs/interceptors': 0.25.16 - '@open-draft/until': 2.1.0 - '@types/cookie': 0.6.0 - '@types/statuses': 2.0.4 - chalk: 4.1.2 - graphql: 16.8.1 - headers-polyfill: 4.0.2 - is-node-process: 1.2.0 - outvariant: 1.4.2 - path-to-regexp: 6.2.1 - strict-event-emitter: 0.5.1 - type-fest: 4.10.3 - typescript: 5.3.3 - yargs: 17.7.2 - dev: false - /msw@2.2.1(typescript@5.5.4): resolution: {integrity: sha512-DCsZAQwan+2onEcpD86fiEnCKW4IvYzqcwDq/2TIoeNrmBqNp/mJW4wHQyxcoYrRPwgujin7wDFflqiSO1iT/w==} engines: {node: '>=18'} @@ -21436,6 +21755,10 @@ packages: lodash: 4.17.21 dev: false + /node-fetch-native@1.6.4: + resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} + dev: false + /node-fetch@2.6.12: resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} engines: {node: 4.x || >=6.0.0} @@ -21545,7 +21868,7 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: hosted-git-info: 6.1.1 - is-core-module: 2.13.0 + is-core-module: 2.14.0 semver: 7.5.4 validate-npm-package-license: 3.0.4 dev: true @@ -21642,6 +21965,19 @@ packages: resolution: {integrity: sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==} dev: false + /nypm@0.3.9: + resolution: {integrity: sha512-BI2SdqqTHg2d4wJh8P9A1W+bslg33vOE9IZDY6eR2QC+Pu1iNBVZUqczrd43rJb+fMzHU7ltAYKsEFY/kHMFcw==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + dependencies: + citty: 0.1.6 + consola: 3.2.3 + execa: 8.0.1 + pathe: 1.1.2 + pkg-types: 1.1.3 + ufo: 1.5.4 + dev: false + /oauth-sign@0.9.0: resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} dev: false @@ -22164,7 +22500,6 @@ 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==} @@ -22319,7 +22654,7 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: lru-cache: 10.0.1 - minipass: 7.0.3 + minipass: 7.1.2 /path-scurry@2.0.0: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} @@ -22327,7 +22662,6 @@ packages: dependencies: lru-cache: 11.0.0 minipass: 7.1.2 - dev: true /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -22353,6 +22687,9 @@ packages: resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} dev: true + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + /pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true @@ -22373,6 +22710,10 @@ packages: through2: 2.0.5 dev: true + /perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + dev: false + /performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} dev: false @@ -22521,13 +22862,12 @@ packages: find-up: 6.3.0 dev: true - /pkg-types@1.0.3: - resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + /pkg-types@1.1.3: + resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} dependencies: - jsonc-parser: 3.2.1 - mlly: 1.4.2 - pathe: 1.1.1 - dev: true + confbox: 0.1.7 + mlly: 1.7.1 + pathe: 1.1.2 /playwright-core@1.37.0: resolution: {integrity: sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA==} @@ -22577,7 +22917,7 @@ packages: postcss: 8.4.27 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.4 + resolve: 1.22.8 dev: false /postcss-import@15.1.0(postcss@8.4.31): @@ -22589,7 +22929,7 @@ packages: postcss: 8.4.31 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.4 + resolve: 1.22.8 /postcss-import@16.0.1(postcss@8.4.38): resolution: {integrity: sha512-i2Pci0310NaLHr/5JUFSw1j/8hf1CzwMY13g6ZDxgOavmRHQi2ba3PmUHoihO+sjaum+KmCNzskNsw7JDrg03g==} @@ -22681,7 +23021,7 @@ packages: ts-node: 10.9.1(@swc/core@1.3.26)(@types/node@18.11.18)(typescript@5.2.2) yaml: 2.3.1 - /postcss-load-config@4.0.1(postcss@8.4.38)(ts-node@10.9.2): + /postcss-load-config@4.0.1(postcss@8.4.38): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} engines: {node: '>= 14'} peerDependencies: @@ -22695,7 +23035,6 @@ packages: dependencies: lilconfig: 2.1.0 postcss: 8.4.38 - ts-node: 10.9.2(@types/node@20.4.2)(typescript@5.3.3) yaml: 2.3.1 /postcss-loader@8.1.1(postcss@8.4.38)(typescript@5.2.2)(webpack@5.88.2): @@ -22898,7 +23237,7 @@ packages: dependencies: nanoid: 3.3.6 picocolors: 1.0.0 - source-map-js: 1.0.2 + source-map-js: 1.2.0 dev: false /postcss@8.4.29: @@ -23398,6 +23737,13 @@ packages: iconv-lite: 0.4.24 unpipe: 1.0.0 + /rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + dependencies: + defu: 6.1.4 + destr: 2.0.3 + dev: false + /rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -24206,7 +24552,7 @@ packages: react: 18.2.0 dev: false - /remix-utils@7.1.0(@remix-run/node@2.1.0)(@remix-run/react@2.1.0)(@remix-run/router@1.15.3)(intl-parse-accept-language@1.0.0)(react@18.2.0)(zod@3.22.3): + /remix-utils@7.1.0(@remix-run/node@2.1.0)(@remix-run/react@2.1.0)(@remix-run/router@1.15.3)(intl-parse-accept-language@1.0.0)(react@18.2.0)(zod@3.23.8): resolution: {integrity: sha512-cceintceWvmNvgLLFeAUkWRcdWuOHGDLaWh0aeL0bLGWnMPBilIyT74Rira1az/ImS9owfh8tjLL4w/22AXJiA==} engines: {node: '>=18.0.0'} peerDependencies: @@ -24248,7 +24594,7 @@ packages: intl-parse-accept-language: 1.0.0 react: 18.2.0 type-fest: 4.6.0 - zod: 3.22.3 + zod: 3.23.8 dev: false /remove-accents@0.5.0: @@ -24372,24 +24718,6 @@ packages: engines: {node: '>=10'} dev: true - /resolve@1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} - hasBin: true - dependencies: - is-core-module: 2.13.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - - /resolve@1.22.2: - resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} - hasBin: true - dependencies: - is-core-module: 2.13.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - /resolve@1.22.4: resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} hasBin: true @@ -24402,7 +24730,7 @@ packages: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true dependencies: - is-core-module: 2.13.0 + is-core-module: 2.14.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -24410,7 +24738,7 @@ packages: resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} hasBin: true dependencies: - is-core-module: 2.13.0 + is-core-module: 2.14.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true @@ -25262,7 +25590,7 @@ packages: resolution: {integrity: sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - minipass: 7.0.3 + minipass: 7.1.2 dev: true /stack-generator@2.0.10: @@ -25731,7 +26059,7 @@ packages: postcss-value-parser: 4.2.0 pretty-hrtime: 1.0.3 reduce-css-calc: 2.1.8 - resolve: 1.22.4 + resolve: 1.22.8 dev: false /tailwindcss@3.4.0: @@ -25830,6 +26158,18 @@ packages: mkdirp: 1.0.4 yallist: 4.0.0 + /tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + dev: false + /tdigest@0.1.2: resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} dependencies: @@ -26116,6 +26456,28 @@ 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: + typescript: '>=4.5.0' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.5.4 + dev: true + /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -26185,7 +26547,7 @@ packages: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - /ts-node@10.9.2(@types/node@20.4.2)(typescript@5.3.3): + /ts-node@10.9.2(@types/node@20.4.2)(typescript@5.5.4): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true peerDependencies: @@ -26211,7 +26573,7 @@ packages: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.3.3 + typescript: 5.5.4 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -26312,47 +26674,6 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - /tsup@8.0.1(patch_hash=a5ztaafw5l4qfghy2hjjuynb34)(postcss@8.4.38)(ts-node@10.9.2)(typescript@5.3.3): - resolution: {integrity: sha512-hvW7gUSG96j53ZTSlT4j/KL0q1Q2l6TqGBFc6/mu/L46IoNWqLLUzLRLP1R8Q7xrJTmkDxxDoojV5uCVs1sVOg==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - dependencies: - bundle-require: 4.0.1(esbuild@0.19.11) - cac: 6.7.14 - chokidar: 3.5.3 - debug: 4.3.4 - esbuild: 0.19.11 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss: 8.4.38 - postcss-load-config: 4.0.1(postcss@8.4.38)(ts-node@10.9.2) - resolve-from: 5.0.0 - rollup: 4.13.2 - source-map: 0.8.0-beta.0 - sucrase: 3.32.0 - tree-kill: 1.2.2 - typescript: 5.3.3 - transitivePeerDependencies: - - supports-color - - ts-node - dev: true - patched: true - /tsup@8.0.1(patch_hash=a5ztaafw5l4qfghy2hjjuynb34)(postcss@8.4.38)(typescript@5.3.2): resolution: {integrity: sha512-hvW7gUSG96j53ZTSlT4j/KL0q1Q2l6TqGBFc6/mu/L46IoNWqLLUzLRLP1R8Q7xrJTmkDxxDoojV5uCVs1sVOg==} engines: {node: '>=18'} @@ -26381,7 +26702,7 @@ packages: globby: 11.1.0 joycon: 3.1.1 postcss: 8.4.38 - postcss-load-config: 4.0.1(postcss@8.4.38)(ts-node@10.9.2) + postcss-load-config: 4.0.1(postcss@8.4.38) resolve-from: 5.0.0 rollup: 4.13.2 source-map: 0.8.0-beta.0 @@ -26394,6 +26715,47 @@ packages: dev: false patched: true + /tsup@8.0.1(patch_hash=a5ztaafw5l4qfghy2hjjuynb34)(postcss@8.4.38)(typescript@5.3.3): + resolution: {integrity: sha512-hvW7gUSG96j53ZTSlT4j/KL0q1Q2l6TqGBFc6/mu/L46IoNWqLLUzLRLP1R8Q7xrJTmkDxxDoojV5uCVs1sVOg==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + dependencies: + bundle-require: 4.0.1(esbuild@0.19.11) + cac: 6.7.14 + chokidar: 3.5.3 + debug: 4.3.4 + esbuild: 0.19.11 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss: 8.4.38 + postcss-load-config: 4.0.1(postcss@8.4.38) + resolve-from: 5.0.0 + rollup: 4.13.2 + source-map: 0.8.0-beta.0 + sucrase: 3.32.0 + tree-kill: 1.2.2 + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + patched: true + /tsutils@3.21.0(typescript@5.2.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -26709,7 +27071,7 @@ packages: pg: 8.11.5 reflect-metadata: 0.2.2 sha.js: 2.4.11 - ts-node: 10.9.2(@types/node@20.4.2)(typescript@5.3.3) + ts-node: 10.9.2(@types/node@20.4.2)(typescript@5.5.4) tslib: 2.6.2 uuid: 9.0.0 yargs: 17.7.2 @@ -26748,21 +27110,20 @@ packages: resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} engines: {node: '>=14.17'} hasBin: true + dev: true /typescript@5.4.5: resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} hasBin: true - dev: false /typescript@5.5.4: resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true - /ufo@1.3.0: - resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==} - dev: true + /ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} /uid2@1.0.0: resolution: {integrity: sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==} @@ -26927,6 +27288,16 @@ 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'} @@ -27240,7 +27611,7 @@ packages: dependencies: cac: 6.7.14 debug: 4.3.4 - mlly: 1.4.2 + mlly: 1.7.1 pathe: 1.1.1 picocolors: 1.0.0 source-map: 0.6.1 @@ -27264,7 +27635,7 @@ packages: dependencies: cac: 6.7.14 debug: 4.3.4 - mlly: 1.4.2 + mlly: 1.7.1 pathe: 1.1.1 picocolors: 1.0.0 source-map: 0.6.1 @@ -27362,7 +27733,7 @@ packages: '@types/node': 18.19.20 esbuild: 0.16.17 postcss: 8.4.29 - resolve: 1.22.1 + resolve: 1.22.8 rollup: 3.10.0 optionalDependencies: fsevents: 2.3.3 @@ -27396,7 +27767,7 @@ packages: '@types/node': 20.14.14 esbuild: 0.16.17 postcss: 8.4.29 - resolve: 1.22.1 + resolve: 1.22.8 rollup: 3.10.0 optionalDependencies: fsevents: 2.3.3 @@ -27773,6 +28144,10 @@ 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'} @@ -28245,20 +28620,24 @@ packages: /zod-error@1.5.0: resolution: {integrity: sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ==} dependencies: - zod: 3.22.3 + zod: 3.23.8 dev: false - /zod-validation-error@1.5.0(zod@3.22.3): + /zod-validation-error@1.5.0(zod@3.23.8): resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==} engines: {node: '>=16.0.0'} peerDependencies: zod: ^3.18.0 dependencies: - zod: 3.22.3 + zod: 3.23.8 dev: false /zod@3.22.3: resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} + dev: false + + /zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} diff --git a/references/v3-catalog/package.json b/references/v3-catalog/package.json index a3cb087b1..9ce49fd25 100644 --- a/references/v3-catalog/package.json +++ b/references/v3-catalog/package.json @@ -24,8 +24,7 @@ "@sindresorhus/slugify": "^2.2.1", "@t3-oss/env-nextjs": "^0.10.1", "@traceloop/instrumentation-openai": "^0.3.9", - "@trigger.dev/core": "workspace:^3.0.0-beta.0", - "@trigger.dev/sdk": "workspace:^3.0.0-beta.0", + "@trigger.dev/sdk": "workspace:*", "dotenv": "^16.4.5", "execa": "^8.0.1", "msw": "^2.2.1", @@ -38,7 +37,7 @@ "stripe": "^12.14.0", "typeorm": "^0.3.20", "yt-dlp-wrap": "^2.3.12", - "zod": "3.22.3" + "zod": "3.23.8" }, "devDependencies": { "@opentelemetry/api": "^1.8.0", @@ -56,14 +55,12 @@ "@opentelemetry/sdk-trace-base": "^1.22.0", "@opentelemetry/sdk-trace-node": "^1.22.0", "@opentelemetry/semantic-conventions": "^1.22.0", - "@trigger.dev/tsconfig": "workspace:*", "@types/node": "20.4.2", "@types/react": "^18.3.1", "esbuild": "^0.19.11", "trigger.dev": "workspace:*", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "tsup": "^8.0.1", - "typescript": "^5.3.0" + "typescript": "^5.5.4" } } \ No newline at end of file diff --git a/references/v3-catalog/trigger.config.ts b/references/v3-catalog/trigger.config.ts index 3f4294bca..b5ac16875 100644 --- a/references/v3-catalog/trigger.config.ts +++ b/references/v3-catalog/trigger.config.ts @@ -1,7 +1,6 @@ -import type { TriggerConfig, ResolveEnvironmentVariablesFunction } from "@trigger.dev/sdk/v3"; -import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai"; -import { AppDataSource } from "@/trigger/orm"; import { InfisicalClient } from "@infisical/sdk"; +import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai"; +import { defineConfig, type ResolveEnvironmentVariablesFunction } from "@trigger.dev/sdk/v3"; export { handleError } from "./src/handleError"; @@ -32,7 +31,7 @@ export const resolveEnvVars: ResolveEnvironmentVariablesFunction = async ({ }; }; -export const config: TriggerConfig = { +export default defineConfig({ project: "yubjwjsfkxnylobaqvqz", machine: "small-2x", retries: { @@ -53,13 +52,9 @@ export const config: TriggerConfig = { logLevel: "info", postInstall: "echo '========== config.postInstall'", onStart: async (payload, { ctx }) => { - if (ctx.organization.id === "clsylhs0v0002dyx75xx4pod1") { - console.log("Initializing the app data source"); - - await AppDataSource.initialize(); - } + console.log(`Task ${ctx.task.id} started ${ctx.run.id}`); }, onFailure: async (payload, error, { ctx }) => { console.log(`Task ${ctx.task.id} failed ${ctx.run.id}`); }, -}; +}); diff --git a/references/v3-catalog/tsconfig.json b/references/v3-catalog/tsconfig.json index 41857dbb0..ee519e26d 100644 --- a/references/v3-catalog/tsconfig.json +++ b/references/v3-catalog/tsconfig.json @@ -1,19 +1,17 @@ { - "extends": "@trigger.dev/tsconfig/node18.json", - "include": ["./src/**/*.ts", "trigger.config.ts", "src/trigger/email.tsx"], "compilerOptions": { - "jsx": "react-jsx", - "baseUrl": ".", - "lib": ["DOM", "DOM.Iterable"], - "paths": { - "@/*": ["./src/*"], - "@trigger.dev/core/v3": ["../../packages/core/src/v3/index"], - "@trigger.dev/core/v3/*": ["../../packages/core/src/v3/*"], - "@trigger.dev/sdk/v3": ["../../packages/trigger-sdk/src/v3/index"], - "@trigger.dev/sdk/v3/*": ["../../packages/trigger-sdk/src/v3/*"] - }, + "target": "esnext", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "strict": true, + "outDir": "dist", + "skipLibCheck": true, + "customConditions": ["triggerdotdev-source"], + "jsx": "preserve", "emitDecoratorMetadata": true, "experimentalDecorators": true, - "allowJs": true - } + "lib": ["DOM", "DOM.Iterable"] + }, + "include": ["./src/**/*.ts", "trigger.config.ts", "src/trigger/email.tsx"] }