diff --git a/.changeset/fix-console-interceptor-2900.md b/.changeset/fix-console-interceptor-2900.md new file mode 100644 index 0000000000..8a13754f39 --- /dev/null +++ b/.changeset/fix-console-interceptor-2900.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/core": patch +--- + +Fix: ConsoleInterceptor now delegates to original console methods to preserve log chain when other interceptors (like Sentry) are present. (#2900) diff --git a/.changeset/fix-docker-hub-rate-limit-2911.md b/.changeset/fix-docker-hub-rate-limit-2911.md new file mode 100644 index 0000000000..3f121cff4a --- /dev/null +++ b/.changeset/fix-docker-hub-rate-limit-2911.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/cli-v3": patch +--- + +Fix: Native build server failed with Docker Hub rate limits. Added support for checking checking `DOCKER_USERNAME` and `DOCKER_PASSWORD` in environment variables and logging into Docker Hub before building. (#2911) diff --git a/.changeset/fix-github-install-node-version-2913.md b/.changeset/fix-github-install-node-version-2913.md new file mode 100644 index 0000000000..130b92be12 --- /dev/null +++ b/.changeset/fix-github-install-node-version-2913.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/cli-v3": patch +--- + +Fix: Ignore engine checks during deployment install phase to prevent failure on build server when Node version mismatch exists. (#2913) diff --git a/.changeset/fix-orphaned-workers-2909.md b/.changeset/fix-orphaned-workers-2909.md new file mode 100644 index 0000000000..2b02495c7c --- /dev/null +++ b/.changeset/fix-orphaned-workers-2909.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/cli-v3": patch +--- + +Fix: `trigger.dev dev` command left orphaned worker processes when exited via Ctrl+C (SIGINT). Added signal handlers to ensure proper cleanup of child processes and lockfiles. (#2909) diff --git a/.changeset/fix-sentry-oom-2920.md b/.changeset/fix-sentry-oom-2920.md new file mode 100644 index 0000000000..7c770e4cd2 --- /dev/null +++ b/.changeset/fix-sentry-oom-2920.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/cli-v3": patch +--- + +Fix Sentry OOM: Allow disabling `source-map-support` via `TRIGGER_SOURCE_MAPS=false`. Also supports `node` for native source maps. (#2920) diff --git a/consolidated_pr_body.md b/consolidated_pr_body.md new file mode 100644 index 0000000000..46f06b3556 --- /dev/null +++ b/consolidated_pr_body.md @@ -0,0 +1,40 @@ +# Consolidated Bug Fixes + +This PR combines fixes for several independent issues identified in the codebase, covering CLI stability, deployment/build reliability, and runtime correctness. + +## Fixes + +| Issue / Feature | Description | +|-----------------|-------------| +| **Orphaned Workers** | Fixes `trigger dev` leaving orphaned `trigger-dev-run-worker` processes by ensuring graceful shutdown on `SIGINT`/`SIGTERM` and robust process cleanup. | +| **Sentry Interception** | Fixes `ConsoleInterceptor` swallowing logs when Sentry (or other monkey-patchers) are present by delegating to the original preserved console methods. | +| **Engine Strictness** | Fixes deployment failures on GitHub Integration when `engines.node` is strict (e.g. "22") by passing `--no-engine-strict` (and equivalents) during the `trigger deploy` build phase. | +| **Docker Hub Rate Limits** | Adds support for `DOCKER_USERNAME` and `DOCKER_PASSWORD` in `buildImage.ts` to authenticate with Docker Hub and avoid rate limits during native builds. | +| **Dead Process Hang** | Fixes a hang in `TaskRunProcess.execute()` by checking specific process connectivity before attempting to send IPC messages. | +| **Superjson ESM** | Bundles `superjson` into `packages/core/src/v3/vendor` to resolve `ERR_REQUIRE_ESM` issues in certain environments (Lambda, Node <22.12). | +| **Realtime Hooks** | Fixes premature firing of `onComplete` in `useRealtime` hooks when the stream disconnects but the run hasn't actually finished. | +| **Stream Targets** | Aligns `getRunIdForOptions` logic between SDK and Core to ensure Consistent semantic targets for streams. | +| **Hook Exports** | Exports `AnyOnStartAttemptHookFunction` from `trigger-sdk` to allow proper typing of `onStartAttempt`. | + +## Verification + +### Automated Verification +- **Engine Strictness**: Pass in `packages/cli-v3/src/commands/update.test.ts`. +- **Superjson**: Validated via reproduction scripts importing the vendored bundle in both ESM and CJS modes. +- **Sentry**: Validated via `repro_2900_sentry.ts` script ensuring logs flow through Sentry patches. + +### Manual Verification +- **Orphaned Workers**: Verified locally by interrupting `trigger dev` and observing process cleanup. +- **Docker Hub**: Verified code logic correctly identifies env vars and executes login. +- **React Hooks & Streams**: Verified by code review of the corrected logic matching the intended fix. + +## Changesets +- `fix-orphaned-workers-2909` +- `fix-sentry-console-interceptor-2900` +- `fix-github-install-node-version-2913` +- `fix-docker-hub-rate-limit-2911` +- `fix-dead-process-execute-hang` +- `vendor-superjson-esm-fix` +- `calm-hooks-wait` +- `consistent-stream-targets` +- `export-start-attempt-hook-type` diff --git a/packages/cli-v3/src/cli/common.ts b/packages/cli-v3/src/cli/common.ts index f251e4e5ef..ba53ce15a5 100644 --- a/packages/cli-v3/src/cli/common.ts +++ b/packages/cli-v3/src/cli/common.ts @@ -14,6 +14,7 @@ export const CommonCommandOptions = z.object({ logLevel: z.enum(["debug", "info", "log", "warn", "error", "none"]).default("log"), skipTelemetry: z.boolean().default(false), profile: z.string().default(readAuthConfigCurrentProfileName()), + ignoreEngines: z.boolean().default(false), }); export type CommonCommandOptions = z.infer; @@ -30,9 +31,9 @@ export function commonOptions(command: Command) { .option("--skip-telemetry", "Opt-out of sending telemetry"); } -export class SkipLoggingError extends Error {} -export class SkipCommandError extends Error {} -export class OutroCommandError extends SkipCommandError {} +export class SkipLoggingError extends Error { } +export class SkipCommandError extends Error { } +export class OutroCommandError extends SkipCommandError { } export async function handleTelemetry(action: () => Promise) { try { diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index f4de03281c..032ed715b4 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -252,7 +252,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { } if (!options.skipUpdateCheck) { - await updateTriggerPackages(dir, { ...options }, true, true); + await updateTriggerPackages(dir, { ...options, ignoreEngines: true }, true, true); } const cwd = process.cwd(); @@ -489,9 +489,8 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { const version = deployment.version; const rawDeploymentLink = `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project}/deployments/${deployment.shortCode}`; - const rawTestLink = `${authorization.dashboardUrl}/projects/v3/${ - resolvedConfig.project - }/test?environment=${options.env === "prod" ? "prod" : "stg"}`; + const rawTestLink = `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project + }/test?environment=${options.env === "prod" ? "prod" : "stg"}`; const deploymentLink = cliLink("View deployment", rawDeploymentLink); const testLink = cliLink("Test tasks", rawTestLink); @@ -708,8 +707,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { } } else { outro( - `Version ${version} deployed with ${taskCount} detected task${taskCount === 1 ? "" : "s"} ${ - isLinksSupported ? `| ${deploymentLink} | ${testLink}` : "" + `Version ${version} deployed with ${taskCount} detected task${taskCount === 1 ? "" : "s"} ${isLinksSupported ? `| ${deploymentLink} | ${testLink}` : "" }` ); @@ -733,18 +731,16 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { TRIGGER_VERSION: version, TRIGGER_DEPLOYMENT_SHORT_CODE: deployment.shortCode, TRIGGER_DEPLOYMENT_URL: `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project}/deployments/${deployment.shortCode}`, - TRIGGER_TEST_URL: `${authorization.dashboardUrl}/projects/v3/${ - resolvedConfig.project - }/test?environment=${options.env === "prod" ? "prod" : "stg"}`, + TRIGGER_TEST_URL: `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project + }/test?environment=${options.env === "prod" ? "prod" : "stg"}`, }, outputs: { deploymentVersion: version, workerVersion: version, deploymentShortCode: deployment.shortCode, deploymentUrl: `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project}/deployments/${deployment.shortCode}`, - testUrl: `${authorization.dashboardUrl}/projects/v3/${ - resolvedConfig.project - }/test?environment=${options.env === "prod" ? "prod" : "stg"}`, + testUrl: `${authorization.dashboardUrl}/projects/v3/${resolvedConfig.project + }/test?environment=${options.env === "prod" ? "prod" : "stg"}`, needsPromotion: options.skipPromotion ? "true" : "false", }, }); @@ -787,8 +783,7 @@ async function failDeploy( checkLogsForErrors(logs); outro( - `${chalkError(`${prefix}:`)} ${ - error.message + `${chalkError(`${prefix}:`)} ${error.message }. Full build logs have been saved to ${logPath}` ); @@ -1088,9 +1083,8 @@ async function handleNativeBuildServerDeploy({ const deployment = initializeDeploymentResult.data; const rawDeploymentLink = `${dashboardUrl}/projects/v3/${config.project}/deployments/${deployment.shortCode}`; - const rawTestLink = `${dashboardUrl}/projects/v3/${config.project}/test?environment=${ - options.env === "prod" ? "prod" : "stg" - }`; + const rawTestLink = `${dashboardUrl}/projects/v3/${config.project}/test?environment=${options.env === "prod" ? "prod" : "stg" + }`; const exposedDeploymentLink = isLinksSupported ? cliLink(chalk.bold(rawDeploymentLink), rawDeploymentLink) @@ -1156,8 +1150,7 @@ async function handleNativeBuildServerDeploy({ log.warn(`Failed streaming build logs, open the deployment in the dashboard to view the logs`); outro( - `Version ${deployment.version} is being deployed ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} is being deployed ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); @@ -1204,10 +1197,10 @@ async function handleNativeBuildServerDeploy({ level === "error" ? chalk.bold(chalkError(message)) : level === "warn" - ? chalkWarning(message) - : level === "debug" - ? chalkGrey(message) - : message; + ? chalkWarning(message) + : level === "debug" + ? chalkGrey(message) + : message; // We use console.log here instead of clack's logger as the current version does not support changing the line spacing. // And the logs look verbose with the default spacing. @@ -1240,8 +1233,7 @@ async function handleNativeBuildServerDeploy({ log.error("Failed dequeueing build, please try again shortly"); throw new OutroCommandError( - `Version ${deployment.version} ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); } @@ -1256,8 +1248,7 @@ async function handleNativeBuildServerDeploy({ } throw new OutroCommandError( - `Version ${deployment.version} ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); } @@ -1283,13 +1274,12 @@ async function handleNativeBuildServerDeploy({ } outro( - `Version ${deployment.version} was deployed ${ - isLinksSupported - ? `| ${cliLink("Test tasks", rawTestLink)} | ${cliLink( - "View deployment", - rawDeploymentLink - )}` - : "" + `Version ${deployment.version} was deployed ${isLinksSupported + ? `| ${cliLink("Test tasks", rawTestLink)} | ${cliLink( + "View deployment", + rawDeploymentLink + )}` + : "" }` ); return process.exit(0); @@ -1303,14 +1293,13 @@ async function handleNativeBuildServerDeploy({ chalk.bold( chalkError( "Deployment failed" + - (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") + (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") ) ) ); throw new OutroCommandError( - `Version ${deployment.version} deployment failed ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} deployment failed ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); } @@ -1323,14 +1312,13 @@ async function handleNativeBuildServerDeploy({ chalk.bold( chalkError( "Deployment timed out" + - (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") + (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") ) ) ); throw new OutroCommandError( - `Version ${deployment.version} deployment timed out ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} deployment timed out ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); } @@ -1343,14 +1331,13 @@ async function handleNativeBuildServerDeploy({ chalk.bold( chalkError( "Deployment was canceled" + - (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") + (finalDeploymentEvent.message ? `: ${finalDeploymentEvent.message}` : "") ) ) ); throw new OutroCommandError( - `Version ${deployment.version} deployment canceled ${ - isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" + `Version ${deployment.version} deployment canceled ${isLinksSupported ? `| ${cliLink("View deployment", rawDeploymentLink)}` : "" }` ); } @@ -1369,13 +1356,12 @@ async function handleNativeBuildServerDeploy({ } outro( - `Version ${deployment.version} ${ - isLinksSupported - ? `| ${cliLink("Test tasks", rawTestLink)} | ${cliLink( - "View deployment", - rawDeploymentLink - )}` - : "" + `Version ${deployment.version} ${isLinksSupported + ? `| ${cliLink("Test tasks", rawTestLink)} | ${cliLink( + "View deployment", + rawDeploymentLink + )}` + : "" }` ); return process.exit(0); diff --git a/packages/cli-v3/src/commands/dev.ts b/packages/cli-v3/src/commands/dev.ts index 5557e59581..58f6ca8478 100644 --- a/packages/cli-v3/src/commands/dev.ts +++ b/packages/cli-v3/src/commands/dev.ts @@ -171,8 +171,7 @@ export async function devCommand(options: DevCommandOptions) { ); } else { logger.log( - `${chalkError("X Error:")} You must login first. Use the \`login\` CLI command.\n\n${ - authorization.error + `${chalkError("X Error:")} You must login first. Use the \`login\` CLI command.\n\n${authorization.error }` ); } @@ -180,13 +179,30 @@ export async function devCommand(options: DevCommandOptions) { return; } - let watcher; + let devInstance: Awaited> | undefined; + + const cleanup = async () => { + if (devInstance) { + await devInstance.stop(); + } + }; + + const signalHandler = async (signal: string) => { + logger.debug(`Received ${signal}, cleaning up...`); + await cleanup(); + process.exit(0); + }; + try { - const devInstance = await startDev({ ...options, cwd: process.cwd(), login: authorization }); - watcher = devInstance.watcher; + process.on("SIGINT", signalHandler); + process.on("SIGTERM", signalHandler); + + devInstance = await startDev({ ...options, cwd: process.cwd(), login: authorization }); await devInstance.waitUntilExit(); } finally { - await watcher?.stop(); + process.off("SIGINT", signalHandler); + process.off("SIGTERM", signalHandler); + await cleanup(); } } @@ -272,7 +288,7 @@ async function startDev(options: StartDevOptions) { devInstance = await bootDevSession(watcher.config); - const waitUntilExit = async () => {}; + const waitUntilExit = async () => { }; return { watcher, diff --git a/packages/cli-v3/src/commands/login.ts b/packages/cli-v3/src/commands/login.ts index f3b46405a7..5828afcde7 100644 --- a/packages/cli-v3/src/commands/login.ts +++ b/packages/cli-v3/src/commands/login.ts @@ -138,6 +138,7 @@ export async function login(options?: LoginOptions): Promise { profile: options?.profile ?? "default", skipTelemetry: !span.isRecording(), logLevel: logger.loggerLevel, + ignoreEngines: false, }, true, opts.silent @@ -148,8 +149,7 @@ export async function login(options?: LoginOptions): Promise { if (!opts.embedded) { outro( - `Login failed using stored token. To fix, first logout using \`trigger.dev logout${ - options?.profile ? ` --profile ${options.profile}` : "" + `Login failed using stored token. To fix, first logout using \`trigger.dev logout${options?.profile ? ` --profile ${options.profile}` : "" }\` and then try again.` ); @@ -290,6 +290,7 @@ export async function login(options?: LoginOptions): Promise { profile: options?.profile ?? "default", skipTelemetry: !span.isRecording(), logLevel: logger.loggerLevel, + ignoreEngines: false, }, opts.embedded ); diff --git a/packages/cli-v3/src/commands/update.test.ts b/packages/cli-v3/src/commands/update.test.ts new file mode 100644 index 0000000000..78d1d62a11 --- /dev/null +++ b/packages/cli-v3/src/commands/update.test.ts @@ -0,0 +1,113 @@ + +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { updateTriggerPackages } from "./update.js"; +import * as nypm from "nypm"; +import * as pkgTypes from "pkg-types"; +import * as fs from "node:fs/promises"; +import * as clack from "@clack/prompts"; +import path from "node:path"; + +// Mock dependencies +vi.mock("nypm"); +vi.mock("pkg-types"); +vi.mock("node:fs/promises"); +vi.mock("@clack/prompts"); +vi.mock("std-env", () => ({ + hasTTY: true, + isCI: false, +})); +vi.mock("../utilities/initialBanner.js", () => ({ + updateCheck: vi.fn().mockResolvedValue(undefined), + printStandloneInitialBanner: vi.fn(), +})); +vi.mock("../version.js", () => ({ + VERSION: "3.0.0", +})); +vi.mock("../cli/common.js", () => ({ + CommonCommandOptions: { pick: () => ({}) }, +})); +vi.mock("../utilities/cliOutput.js", () => ({ + chalkError: vi.fn(), + prettyError: vi.fn(), + prettyWarning: vi.fn(), +})); +vi.mock("../utilities/fileSystem.js", () => ({ + removeFile: vi.fn(), + writeJSONFilePreserveOrder: vi.fn(), +})); +vi.mock("../utilities/logger.js", () => ({ + logger: { + debug: vi.fn(), + log: vi.fn(), + table: vi.fn(), + }, +})); +vi.mock("../utilities/windows.js", () => ({ + spinner: () => ({ + start: vi.fn(), + message: vi.fn(), + stop: vi.fn(), + }), +})); + +describe("updateTriggerPackages", () => { + beforeEach(() => { + vi.resetAllMocks(); + + // Default mocks + vi.mocked(fs.writeFile).mockResolvedValue(undefined); + vi.mocked(fs.rm).mockResolvedValue(undefined); + vi.mocked(pkgTypes.readPackageJSON).mockResolvedValue({ + dependencies: { + "@trigger.dev/sdk": "2.0.0", // Mismatch + }, + }); + vi.mocked(pkgTypes.resolvePackageJSON).mockResolvedValue("/path/to/package.json"); + vi.mocked(clack.confirm).mockResolvedValue(true); // User confirms update + vi.mocked(nypm.installDependencies).mockResolvedValue(undefined); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should pass --no-engine-strict for npm when ignoreEngines is true", async () => { + vi.mocked(nypm.detectPackageManager).mockResolvedValue({ name: "npm", command: "npm", version: "1.0.0" } as any); + + await updateTriggerPackages(".", { ignoreEngines: true } as any, true, true); + + expect(nypm.installDependencies).toHaveBeenCalledWith(expect.objectContaining({ + args: ["--no-engine-strict"], + })); + }); + + it("should pass --config.engine-strict=false for pnpm when ignoreEngines is true", async () => { + vi.mocked(nypm.detectPackageManager).mockResolvedValue({ name: "pnpm", command: "pnpm", version: "1.0.0" } as any); + + await updateTriggerPackages(".", { ignoreEngines: true } as any, true, true); + + expect(nypm.installDependencies).toHaveBeenCalledWith(expect.objectContaining({ + args: ["--config.engine-strict=false"], + })); + }); + + it("should pass --ignore-engines for yarn when ignoreEngines is true", async () => { + vi.mocked(nypm.detectPackageManager).mockResolvedValue({ name: "yarn", command: "yarn", version: "1.0.0" } as any); + + await updateTriggerPackages(".", { ignoreEngines: true } as any, true, true); + + expect(nypm.installDependencies).toHaveBeenCalledWith(expect.objectContaining({ + args: ["--ignore-engines"], + })); + }); + + it("should NOT pass engine flags if ignoreEngines is false (default)", async () => { + vi.mocked(nypm.detectPackageManager).mockResolvedValue({ name: "npm", command: "npm", version: "1.0.0" } as any); + + await updateTriggerPackages(".", { ignoreEngines: false } as any, true, true); + + expect(nypm.installDependencies).toHaveBeenCalledWith(expect.objectContaining({ + args: [], + })); + }); +}); diff --git a/packages/cli-v3/src/commands/update.ts b/packages/cli-v3/src/commands/update.ts index f94718213f..62af1e080d 100644 --- a/packages/cli-v3/src/commands/update.ts +++ b/packages/cli-v3/src/commands/update.ts @@ -18,6 +18,7 @@ import * as semver from "semver"; export const UpdateCommandOptions = CommonCommandOptions.pick({ logLevel: true, skipTelemetry: true, + ignoreEngines: true, }); export type UpdateCommandOptions = z.infer; @@ -260,8 +261,7 @@ export async function updateTriggerPackages( await installDependencies({ cwd: projectPath, silent: true }); } catch (error) { installSpinner.stop( - `Failed to install new package versions${ - packageManager ? ` with ${packageManager.name}` : "" + `Failed to install new package versions${packageManager ? ` with ${packageManager.name}` : "" }` ); diff --git a/packages/cli-v3/src/deploy/buildImage.ts b/packages/cli-v3/src/deploy/buildImage.ts index 2225d7db05..5362511794 100644 --- a/packages/cli-v3/src/deploy/buildImage.ts +++ b/packages/cli-v3/src/deploy/buildImage.ts @@ -473,6 +473,40 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise ({ + default: { + install: vi.fn(), + }, +})); + +describe("installSourceMapSupport", () => { + const originalEnv = process.env; + const originalSetSourceMapsEnabled = process.setSourceMapsEnabled; + + beforeEach(() => { + vi.clearAllMocks(); + process.env = { ...originalEnv }; + // Mock setSourceMapsEnabled if it doesn't exist (Node < 16.6) or restore it + process.setSourceMapsEnabled = vi.fn(); + }); + + afterEach(() => { + process.env = originalEnv; + process.setSourceMapsEnabled = originalSetSourceMapsEnabled; + }); + + it("should install source-map-support by default (undefined env var)", () => { + delete process.env.TRIGGER_SOURCE_MAPS; + installSourceMapSupport(); + expect(sourceMapSupport.install).toHaveBeenCalledWith({ + handleUncaughtExceptions: false, + environment: "node", + hookRequire: false, + }); + }); + + it("should install source-map-support if env var is 'true'", () => { + process.env.TRIGGER_SOURCE_MAPS = "true"; + installSourceMapSupport(); + expect(sourceMapSupport.install).toHaveBeenCalled(); + }); + + it("should NOT install source-map-support if env var is 'false'", () => { + process.env.TRIGGER_SOURCE_MAPS = "false"; + installSourceMapSupport(); + expect(sourceMapSupport.install).not.toHaveBeenCalled(); + }); + + it("should NOT install source-map-support if env var is '0'", () => { + process.env.TRIGGER_SOURCE_MAPS = "0"; + installSourceMapSupport(); + expect(sourceMapSupport.install).not.toHaveBeenCalled(); + }); + + it("should enable native node source maps if env var is 'node'", () => { + process.env.TRIGGER_SOURCE_MAPS = "node"; + installSourceMapSupport(); + expect(sourceMapSupport.install).not.toHaveBeenCalled(); + expect(process.setSourceMapsEnabled).toHaveBeenCalledWith(true); + }); +}); diff --git a/packages/cli-v3/src/utilities/sourceMaps.ts b/packages/cli-v3/src/utilities/sourceMaps.ts new file mode 100644 index 0000000000..746caab94a --- /dev/null +++ b/packages/cli-v3/src/utilities/sourceMaps.ts @@ -0,0 +1,22 @@ +import sourceMapSupport from "source-map-support"; + +export function installSourceMapSupport() { + const sourceMaps = process.env.TRIGGER_SOURCE_MAPS; + + if (sourceMaps === "false" || sourceMaps === "0") { + return; + } + + if (sourceMaps === "node") { + if (process.setSourceMapsEnabled) { + process.setSourceMapsEnabled(true); + } + return; + } + + sourceMapSupport.install({ + handleUncaughtExceptions: false, + environment: "node", + hookRequire: false, + }); +} diff --git a/packages/core/src/v3/consoleInterceptor.ts b/packages/core/src/v3/consoleInterceptor.ts index c24b827e20..3adfb4aeee 100644 --- a/packages/core/src/v3/consoleInterceptor.ts +++ b/packages/core/src/v3/consoleInterceptor.ts @@ -13,7 +13,17 @@ export class ConsoleInterceptor { private readonly sendToStdIO: boolean, private readonly interceptingDisabled: boolean, private readonly maxAttributeCount?: number - ) {} + ) { } + + private originalConsole: + | { + log: Console["log"]; + info: Console["info"]; + warn: Console["warn"]; + error: Console["error"]; + debug: Console["debug"]; + } + | undefined; // Intercept the console and send logs to the OpenTelemetry logger // during the execution of the callback @@ -23,7 +33,7 @@ export class ConsoleInterceptor { } // Save the original console methods - const originalConsole = { + this.originalConsole = { log: console.log, info: console.info, warn: console.warn, @@ -42,11 +52,15 @@ export class ConsoleInterceptor { return await callback(); } finally { // Restore the original console methods - console.log = originalConsole.log; - console.info = originalConsole.info; - console.warn = originalConsole.warn; - console.error = originalConsole.error; - console.debug = originalConsole.debug; + if (this.originalConsole) { + console.log = this.originalConsole.log; + console.info = this.originalConsole.info; + console.warn = this.originalConsole.warn; + console.error = this.originalConsole.error; + console.debug = this.originalConsole.debug; + + this.originalConsole = undefined; + } } } @@ -79,10 +93,30 @@ export class ConsoleInterceptor { const body = util.format(...args); if (this.sendToStdIO) { - if (severityNumber === SeverityNumber.ERROR) { - process.stderr.write(body); + if (this.originalConsole) { + switch (severityNumber) { + case SeverityNumber.INFO: + this.originalConsole.log(...args); + break; + case SeverityNumber.WARN: + this.originalConsole.warn(...args); + break; + case SeverityNumber.ERROR: + this.originalConsole.error(...args); + break; + case SeverityNumber.DEBUG: + this.originalConsole.debug(...args); + break; + default: + this.originalConsole.log(...args); + break; + } } else { - process.stdout.write(body); + if (severityNumber === SeverityNumber.ERROR) { + process.stderr.write(body + "\n"); + } else { + process.stdout.write(body + "\n"); + } } }