From 844ffbd567c3b0c5a521d52fdae3de8ee7ee6d2a Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 21 Feb 2025 17:24:38 +0100 Subject: [PATCH 1/7] fix(core): Don't crash on recoverable CLI command error --- packages/bundler-plugin-core/src/index.ts | 4 +- .../error-no-handler/error-no-handler.test.ts | 47 ++++++++ .../fixtures/error-no-handler/fakeSentry.js | 13 ++ .../fixtures/error-no-handler/input/bundle.js | 2 + .../fixtures/error-no-handler/setup.ts | 0 .../utils/create-cjs-bundles-promise.ts | 112 ++++++++++++++++++ 6 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 packages/integration-tests/fixtures/error-no-handler/error-no-handler.test.ts create mode 100644 packages/integration-tests/fixtures/error-no-handler/fakeSentry.js create mode 100644 packages/integration-tests/fixtures/error-no-handler/input/bundle.js create mode 100644 packages/integration-tests/fixtures/error-no-handler/setup.ts create mode 100644 packages/integration-tests/utils/create-cjs-bundles-promise.ts diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 159444bc..2ef7a9da 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -170,8 +170,10 @@ export function sentryUnpluginFactory({ throw e; } } else { + // setting the session to "crashed" b/c from a plugin perspective this run failed. + // However, we're intentionally not rethrowing the error to avoid breaking the user build. sentrySession.status = "crashed"; - throw unknownError; + logger.error("An error occurred. Couldn't finish all operations:", unknownError); } } finally { endSession(); diff --git a/packages/integration-tests/fixtures/error-no-handler/error-no-handler.test.ts b/packages/integration-tests/fixtures/error-no-handler/error-no-handler.test.ts new file mode 100644 index 00000000..07ba7053 --- /dev/null +++ b/packages/integration-tests/fixtures/error-no-handler/error-no-handler.test.ts @@ -0,0 +1,47 @@ +/* eslint-disable jest/no-standalone-expect */ +/* eslint-disable jest/expect-expect */ +import path from "path"; +import { createCjsBundles } from "../../utils/create-cjs-bundles-promise"; +import { spawn } from "child_process"; + +describe("Doesn't crash when Sentry responds with HTTP errors during upload and release creation", () => { + test("webpack4", async () => { + const FAKE_SENTRY_PORT = "9876"; + + const sentryServer = spawn("node", ["fakeSentry.js"], { + stdio: "inherit", + env: { ...process.env, FAKE_SENTRY_PORT }, + }); + + await new Promise((resolve) => + sentryServer.on("spawn", () => { + resolve(); + }) + ); + + const outputDir = path.resolve(__dirname, "out"); + + for (const bundler of ["webpack4", "webpack5", "esbuild", "rollup", "vite"]) { + await expect( + createCjsBundles( + { + bundle: path.resolve(__dirname, "input", "bundle.js"), + }, + outputDir, + { + url: `http://localhost:${FAKE_SENTRY_PORT}`, + authToken: "fake-auth", + org: "fake-org", + project: "fake-project", + release: { + name: "1.0.0", + }, + }, + [bundler] + ) + ).resolves.not.toThrow(); + } + + sentryServer.kill(); + }); +}); diff --git a/packages/integration-tests/fixtures/error-no-handler/fakeSentry.js b/packages/integration-tests/fixtures/error-no-handler/fakeSentry.js new file mode 100644 index 00000000..bca9200b --- /dev/null +++ b/packages/integration-tests/fixtures/error-no-handler/fakeSentry.js @@ -0,0 +1,13 @@ +import { createServer } from "http"; + +const port = process.env["FAKE_SENTRY_PORT"] || 3000; + +const server = createServer((req, res) => { + res.statusCode = 503; + res.end("Error: Santry unreachable"); +}); + +server.listen(port, () => { + // eslint-disable-next-line no-console + console.log(`Santry running on http://localhost:${port}/`); +}); diff --git a/packages/integration-tests/fixtures/error-no-handler/input/bundle.js b/packages/integration-tests/fixtures/error-no-handler/input/bundle.js new file mode 100644 index 00000000..aa70f660 --- /dev/null +++ b/packages/integration-tests/fixtures/error-no-handler/input/bundle.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-console +console.log("whatever"); diff --git a/packages/integration-tests/fixtures/error-no-handler/setup.ts b/packages/integration-tests/fixtures/error-no-handler/setup.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/integration-tests/utils/create-cjs-bundles-promise.ts b/packages/integration-tests/utils/create-cjs-bundles-promise.ts new file mode 100644 index 00000000..43a908a6 --- /dev/null +++ b/packages/integration-tests/utils/create-cjs-bundles-promise.ts @@ -0,0 +1,112 @@ +import * as vite from "vite"; +import * as path from "path"; +import * as rollup from "rollup"; +import { default as webpack4 } from "webpack4"; +import { webpack as webpack5 } from "webpack5"; +import * as esbuild from "esbuild"; +import { Options } from "@sentry/bundler-plugin-core"; +import { sentryVitePlugin } from "@sentry/vite-plugin"; +import { sentryWebpackPlugin } from "@sentry/webpack-plugin"; +import { sentryEsbuildPlugin } from "@sentry/esbuild-plugin"; +import { sentryRollupPlugin } from "@sentry/rollup-plugin"; + +// eslint-disable-next-line @typescript-eslint/no-non-null-assertion +const nodejsMajorversion = process.version.split(".")[0]!.slice(1); + +export async function createCjsBundles( + entrypoints: { [name: string]: string }, + outFolder: string, + sentryUnpluginOptions: Options, + plugins: string[] = [] +): Promise { + if (plugins.length === 0 || plugins.includes("vite")) { + await vite.build({ + clearScreen: false, + build: { + sourcemap: true, + outDir: path.join(outFolder, "vite"), + rollupOptions: { + input: entrypoints, + output: { + format: "cjs", + entryFileNames: "[name].js", + }, + }, + }, + plugins: [sentryVitePlugin(sentryUnpluginOptions)], + }); + } + if (plugins.length === 0 || plugins.includes("rollup")) { + await rollup + .rollup({ + input: entrypoints, + plugins: [sentryRollupPlugin(sentryUnpluginOptions)], + }) + .then((bundle) => + bundle.write({ + sourcemap: true, + dir: path.join(outFolder, "rollup"), + format: "cjs", + exports: "named", + }) + ); + } + + if (plugins.length === 0 || plugins.includes("esbuild")) { + await esbuild.build({ + sourcemap: true, + entryPoints: entrypoints, + outdir: path.join(outFolder, "esbuild"), + plugins: [sentryEsbuildPlugin(sentryUnpluginOptions)], + minify: true, + bundle: true, + format: "cjs", + }); + } + + // Webpack 4 doesn't work on Node.js versions >= 18 + if (parseInt(nodejsMajorversion) < 18 && (plugins.length === 0 || plugins.includes("webpack4"))) { + webpack4( + { + devtool: "source-map", + mode: "production", + entry: entrypoints, + cache: false, + output: { + path: path.join(outFolder, "webpack4"), + libraryTarget: "commonjs", + }, + target: "node", // needed for webpack 4 so we can access node api + plugins: [sentryWebpackPlugin(sentryUnpluginOptions)], + }, + (err) => { + if (err) { + throw err; + } + } + ); + } + + if (plugins.length === 0 || plugins.includes("webpack5")) { + webpack5( + { + devtool: "source-map", + cache: false, + entry: entrypoints, + output: { + path: path.join(outFolder, "webpack5"), + library: { + type: "commonjs", + }, + }, + mode: "production", + plugins: [sentryWebpackPlugin(sentryUnpluginOptions)], + }, + (err) => { + if (err) { + throw err; + } + } + ); + } +} From f852b555d02a8b0f64db226bfab40e3f219a5989 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 25 Feb 2025 16:35:52 +0100 Subject: [PATCH 2/7] add `throwByDefault` flag and require it on every `handleRecoverableError` call --- .../bundler-plugin-core/src/debug-id-upload.ts | 5 +++-- packages/bundler-plugin-core/src/index.ts | 18 ++++++++++++++++-- .../src/plugins/release-management.ts | 6 +++--- .../src/plugins/sourcemap-deletion.ts | 7 +++++-- packages/bundler-plugin-core/src/types.ts | 2 ++ 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/bundler-plugin-core/src/debug-id-upload.ts b/packages/bundler-plugin-core/src/debug-id-upload.ts index d5c7358d..87e35800 100644 --- a/packages/bundler-plugin-core/src/debug-id-upload.ts +++ b/packages/bundler-plugin-core/src/debug-id-upload.ts @@ -12,6 +12,7 @@ import { stripQueryAndHashFromPath } from "./utils"; import { setMeasurement, spanToTraceHeader, startSpan } from "@sentry/core"; import { getDynamicSamplingContextFromSpan, Scope } from "@sentry/core"; import { Client } from "@sentry/types"; +import { HandleRecoverableErrorFn } from "./types"; interface RewriteSourcesHook { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -25,7 +26,7 @@ interface DebugIdUploadPluginOptions { releaseName?: string; dist?: string; rewriteSourcesHook?: RewriteSourcesHook; - handleRecoverableError: (error: unknown) => void; + handleRecoverableError: HandleRecoverableErrorFn; sentryScope: Scope; sentryClient: Client; sentryCliOptions: { @@ -187,7 +188,7 @@ export function createDebugIdUploadFunction({ } } catch (e) { sentryScope.captureException('Error in "debugIdUploadPlugin" writeBundle hook'); - handleRecoverableError(e); + handleRecoverableError(e, false); } finally { if (folderToCleanUp) { void startSpan({ name: "cleanup", scope: sentryScope }, async () => { diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 2ef7a9da..61aeb52d 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -155,7 +155,16 @@ export function sentryUnpluginFactory({ "SENTRY_PIPELINE" ] = `${unpluginMetaContext.framework}-plugin/${__PACKAGE_VERSION__}`; - function handleRecoverableError(unknownError: unknown) { + /** + * Handles errors caught and emitted in various areas of the plugin. + * + * Also sets the sentry session status according to the error handling. + * + * If users specify their custom `errorHandler` we'll leave the decision to throw + * or continue up to them. By default, @param throwByDefault controls if the plugin + * should throw an error (which causes a build fail in most bundlers) or continue. + */ + function handleRecoverableError(unknownError: unknown, throwByDefault: boolean) { sentrySession.status = "abnormal"; try { if (options.errorHandler) { @@ -173,6 +182,9 @@ export function sentryUnpluginFactory({ // setting the session to "crashed" b/c from a plugin perspective this run failed. // However, we're intentionally not rethrowing the error to avoid breaking the user build. sentrySession.status = "crashed"; + if (throwByDefault) { + throw unknownError; + } logger.error("An error occurred. Couldn't finish all operations:", unknownError); } } finally { @@ -181,8 +193,10 @@ export function sentryUnpluginFactory({ } if (!validateOptions(options, logger)) { + // Throwing by default to avoid a misconfigured plugin going unnoticed. handleRecoverableError( - new Error("Options were not set correctly. See output above for more details.") + new Error("Options were not set correctly. See output above for more details."), + true ); } diff --git a/packages/bundler-plugin-core/src/plugins/release-management.ts b/packages/bundler-plugin-core/src/plugins/release-management.ts index e3f2cabf..8abac956 100644 --- a/packages/bundler-plugin-core/src/plugins/release-management.ts +++ b/packages/bundler-plugin-core/src/plugins/release-management.ts @@ -3,7 +3,7 @@ import { Scope } from "@sentry/core"; import { UnpluginOptions } from "unplugin"; import { Logger } from "../sentry/logger"; import { safeFlushTelemetry } from "../sentry/telemetry"; -import { IncludeEntry } from "../types"; +import { HandleRecoverableErrorFn, IncludeEntry } from "../types"; import { arrayify } from "../utils"; import { Client } from "@sentry/types"; @@ -16,7 +16,7 @@ interface ReleaseManagementPluginOptions { setCommitsOption?: SentryCliCommitsOptions; deployOptions?: SentryCliNewDeployOptions; dist?: string; - handleRecoverableError: (error: unknown) => void; + handleRecoverableError: HandleRecoverableErrorFn; sentryScope: Scope; sentryClient: Client; sentryCliOptions: { @@ -100,7 +100,7 @@ export function releaseManagementPlugin({ } catch (e) { sentryScope.captureException('Error in "releaseManagementPlugin" writeBundle hook'); await safeFlushTelemetry(sentryClient); - handleRecoverableError(e); + handleRecoverableError(e, false); } finally { freeGlobalDependencyOnSourcemapFiles(); freeWriteBundleInvocationDependencyOnSourcemapFiles(); diff --git a/packages/bundler-plugin-core/src/plugins/sourcemap-deletion.ts b/packages/bundler-plugin-core/src/plugins/sourcemap-deletion.ts index 334282ea..e6efd08d 100644 --- a/packages/bundler-plugin-core/src/plugins/sourcemap-deletion.ts +++ b/packages/bundler-plugin-core/src/plugins/sourcemap-deletion.ts @@ -5,9 +5,10 @@ import { safeFlushTelemetry } from "../sentry/telemetry"; import fs from "fs"; import { Scope } from "@sentry/core"; import { Client } from "@sentry/types"; +import { HandleRecoverableErrorFn } from "../types"; interface FileDeletionPlugin { - handleRecoverableError: (error: unknown) => void; + handleRecoverableError: HandleRecoverableErrorFn; waitUntilSourcemapFileDependenciesAreFreed: () => Promise; sentryScope: Scope; sentryClient: Client; @@ -59,7 +60,9 @@ export function fileDeletionPlugin({ } catch (e) { sentryScope.captureException('Error in "sentry-file-deletion-plugin" buildEnd hook'); await safeFlushTelemetry(sentryClient); - handleRecoverableError(e); + // We throw by default if we get here b/c not being able to delete + // source maps could leak them to production + handleRecoverableError(e, true); } }, }; diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index e6153fff..61db9049 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -552,3 +552,5 @@ type DeployOptions = { */ url?: string; }; + +export type HandleRecoverableErrorFn = (error: unknown, throwByDefault: boolean) => void; From 10a3c53d2a73efb1227d16dfaeb4ed489b8a670c Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 26 Feb 2025 17:03:39 +0100 Subject: [PATCH 3/7] integration tests --- .../src/debug-id-upload.ts | 2 +- .../error-no-handler/error-no-handler.test.ts | 47 -------- .../fixtures/error-no-handler/setup.ts | 0 .../fixtures/errorhandling/build-esbuild.ts | 18 +++ .../fixtures/errorhandling/build-rollup.ts | 24 ++++ .../fixtures/errorhandling/build-vite.ts | 27 +++++ .../fixtures/errorhandling/build-webpack4.ts | 26 ++++ .../fixtures/errorhandling/build-webpack5.ts | 27 +++++ .../errorhandling/error-no-handler.test.ts | 52 ++++++++ .../fakeSentry.js | 6 +- .../input/bundle.js | 0 .../errorhandling/plugin-options-wrong.ts | 10 ++ .../fixtures/errorhandling/plugin-options.ts | 10 ++ .../utils/create-cjs-bundles-promise.ts | 112 ------------------ 14 files changed, 199 insertions(+), 162 deletions(-) delete mode 100644 packages/integration-tests/fixtures/error-no-handler/error-no-handler.test.ts delete mode 100644 packages/integration-tests/fixtures/error-no-handler/setup.ts create mode 100644 packages/integration-tests/fixtures/errorhandling/build-esbuild.ts create mode 100644 packages/integration-tests/fixtures/errorhandling/build-rollup.ts create mode 100644 packages/integration-tests/fixtures/errorhandling/build-vite.ts create mode 100644 packages/integration-tests/fixtures/errorhandling/build-webpack4.ts create mode 100644 packages/integration-tests/fixtures/errorhandling/build-webpack5.ts create mode 100644 packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts rename packages/integration-tests/fixtures/{error-no-handler => errorhandling}/fakeSentry.js (52%) rename packages/integration-tests/fixtures/{error-no-handler => errorhandling}/input/bundle.js (100%) create mode 100644 packages/integration-tests/fixtures/errorhandling/plugin-options-wrong.ts create mode 100644 packages/integration-tests/fixtures/errorhandling/plugin-options.ts delete mode 100644 packages/integration-tests/utils/create-cjs-bundles-promise.ts diff --git a/packages/bundler-plugin-core/src/debug-id-upload.ts b/packages/bundler-plugin-core/src/debug-id-upload.ts index 87e35800..d201b3a0 100644 --- a/packages/bundler-plugin-core/src/debug-id-upload.ts +++ b/packages/bundler-plugin-core/src/debug-id-upload.ts @@ -168,7 +168,7 @@ export function createDebugIdUploadFunction({ }); await cliInstance.releases.uploadSourceMaps( - releaseName ?? "undefined", // unfortunetly this needs a value for now but it will not matter since debug IDs overpower releases anyhow + releaseName ?? "undefined", // unfortunately this needs a value for now but it will not matter since debug IDs overpower releases anyhow { include: [ { diff --git a/packages/integration-tests/fixtures/error-no-handler/error-no-handler.test.ts b/packages/integration-tests/fixtures/error-no-handler/error-no-handler.test.ts deleted file mode 100644 index 07ba7053..00000000 --- a/packages/integration-tests/fixtures/error-no-handler/error-no-handler.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* eslint-disable jest/no-standalone-expect */ -/* eslint-disable jest/expect-expect */ -import path from "path"; -import { createCjsBundles } from "../../utils/create-cjs-bundles-promise"; -import { spawn } from "child_process"; - -describe("Doesn't crash when Sentry responds with HTTP errors during upload and release creation", () => { - test("webpack4", async () => { - const FAKE_SENTRY_PORT = "9876"; - - const sentryServer = spawn("node", ["fakeSentry.js"], { - stdio: "inherit", - env: { ...process.env, FAKE_SENTRY_PORT }, - }); - - await new Promise((resolve) => - sentryServer.on("spawn", () => { - resolve(); - }) - ); - - const outputDir = path.resolve(__dirname, "out"); - - for (const bundler of ["webpack4", "webpack5", "esbuild", "rollup", "vite"]) { - await expect( - createCjsBundles( - { - bundle: path.resolve(__dirname, "input", "bundle.js"), - }, - outputDir, - { - url: `http://localhost:${FAKE_SENTRY_PORT}`, - authToken: "fake-auth", - org: "fake-org", - project: "fake-project", - release: { - name: "1.0.0", - }, - }, - [bundler] - ) - ).resolves.not.toThrow(); - } - - sentryServer.kill(); - }); -}); diff --git a/packages/integration-tests/fixtures/error-no-handler/setup.ts b/packages/integration-tests/fixtures/error-no-handler/setup.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/integration-tests/fixtures/errorhandling/build-esbuild.ts b/packages/integration-tests/fixtures/errorhandling/build-esbuild.ts new file mode 100644 index 00000000..7e2a90cc --- /dev/null +++ b/packages/integration-tests/fixtures/errorhandling/build-esbuild.ts @@ -0,0 +1,18 @@ +import { sentryEsbuildPlugin } from "@sentry/esbuild-plugin"; +import { build } from "esbuild"; +import pluginOptions from "./plugin-options"; +import path from "path"; + +build({ + entryPoints: [path.join(__dirname, "input", "bundle.js")], + outdir: "./out/esbuild", + plugins: [sentryEsbuildPlugin(pluginOptions)], + minify: true, + bundle: true, + format: "cjs", + sourcemap: true, +}).catch((e) => { + // eslint-disable-next-line no-console + console.error(e); + process.exit(1); +}); diff --git a/packages/integration-tests/fixtures/errorhandling/build-rollup.ts b/packages/integration-tests/fixtures/errorhandling/build-rollup.ts new file mode 100644 index 00000000..4fed29ea --- /dev/null +++ b/packages/integration-tests/fixtures/errorhandling/build-rollup.ts @@ -0,0 +1,24 @@ +import { sentryRollupPlugin } from "@sentry/rollup-plugin"; +import * as path from "path"; +import * as rollup from "rollup"; +import pluginOptions from "./plugin-options"; + +rollup + .rollup({ + input: { + index: path.join(__dirname, "input", "bundle.js"), + }, + plugins: [sentryRollupPlugin(pluginOptions)], + }) + .then((bundle) => + bundle.write({ + dir: path.join(__dirname, "out", "rollup"), + format: "cjs", + exports: "named", + }) + ) + .catch((e) => { + // eslint-disable-next-line no-console + console.error(e); + process.exit(1); + }); diff --git a/packages/integration-tests/fixtures/errorhandling/build-vite.ts b/packages/integration-tests/fixtures/errorhandling/build-vite.ts new file mode 100644 index 00000000..a5489beb --- /dev/null +++ b/packages/integration-tests/fixtures/errorhandling/build-vite.ts @@ -0,0 +1,27 @@ +import { sentryVitePlugin } from "@sentry/vite-plugin"; +import * as path from "path"; +import * as vite from "vite"; +import pluginOptions from "./plugin-options"; + +vite + .build({ + clearScreen: false, + build: { + outDir: path.join(__dirname, "out", "vite"), + rollupOptions: { + input: { + index: path.join(__dirname, "input", "bundle.js"), + }, + output: { + format: "cjs", + entryFileNames: "[name].js", + }, + }, + }, + plugins: [sentryVitePlugin(pluginOptions)], + }) + .catch((e) => { + // eslint-disable-next-line no-console + console.error(e); + process.exit(1); + }); diff --git a/packages/integration-tests/fixtures/errorhandling/build-webpack4.ts b/packages/integration-tests/fixtures/errorhandling/build-webpack4.ts new file mode 100644 index 00000000..46db8686 --- /dev/null +++ b/packages/integration-tests/fixtures/errorhandling/build-webpack4.ts @@ -0,0 +1,26 @@ +import { sentryWebpackPlugin } from "@sentry/webpack-plugin"; +import * as path from "path"; +import { default as webpack4 } from "webpack4"; +import pluginOptions from "./plugin-options"; + +webpack4( + { + devtool: "source-map", + cache: false, + entry: { + index: path.join(__dirname, "input", "bundle.js"), + }, + output: { + path: path.join(__dirname, "out", "webpack4"), + libraryTarget: "commonjs", + }, + mode: "production", + target: "node", // needed for webpack 4 so we can access node api + plugins: [sentryWebpackPlugin(pluginOptions)], + }, + (err) => { + if (err) { + process.exit(1); + } + } +); diff --git a/packages/integration-tests/fixtures/errorhandling/build-webpack5.ts b/packages/integration-tests/fixtures/errorhandling/build-webpack5.ts new file mode 100644 index 00000000..c2c31a70 --- /dev/null +++ b/packages/integration-tests/fixtures/errorhandling/build-webpack5.ts @@ -0,0 +1,27 @@ +import { sentryWebpackPlugin } from "@sentry/webpack-plugin"; +import * as path from "path"; +import { webpack as webpack5 } from "webpack5"; +import pluginOptions from "./plugin-options"; + +webpack5( + { + devtool: "source-map", + cache: false, + entry: { + index: path.join(__dirname, "input", "bundle.js"), + }, + output: { + path: path.join(__dirname, "out", "webpack5"), + library: { + type: "commonjs", + }, + }, + mode: "production", + plugins: [sentryWebpackPlugin(pluginOptions)], + }, + (err) => { + if (err) { + process.exit(1); + } + } +); diff --git a/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts b/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts new file mode 100644 index 00000000..3f6a0bba --- /dev/null +++ b/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts @@ -0,0 +1,52 @@ +/* eslint-disable jest/no-standalone-expect */ +/* eslint-disable jest/expect-expect */ +import path from "path"; +import { spawn } from "child_process"; + +jest.setTimeout(10_000); + +describe("Error throwing by default (no errorHandler)", () => { + const FAKE_SENTRY_PORT = "9876"; + + const sentryServer = spawn("node", ["fakeSentry.js"], { + cwd: __dirname, + stdio: "ignore", // <-- set to "inherit" to get server logs. Deactivated to avoid test logs. + env: { ...process.env, FAKE_SENTRY_PORT }, + }); + + beforeAll(async () => { + await new Promise((resolve) => + sentryServer.on("spawn", () => { + resolve(); + }) + ); + }); + + afterAll(() => { + sentryServer.kill(); + }); + + test.each(["vite", "rollup", "webpack5", "webpack4", "esbuild"])( + "doesn't throw when Sentry server responds with error code for %s", + async (bundler) => { + const buildProcess = spawn("yarn", ["ts-node", path.join(__dirname, `build-${bundler}.ts`)], { + env: { ...process.env, FAKE_SENTRY_PORT }, + stdio: "ignore", // <-- set to "inherit" to get build output. Deactivated to avoid spamming test logs. + }); + + const exitCode = await new Promise((resolve, reject) => { + buildProcess.on("exit", (code) => { + resolve(code ?? 99); + }); + + buildProcess.on("error", (err) => { + reject(err); + }); + }); + + expect(exitCode).toBe(0); + + buildProcess.kill(); + } + ); +}); diff --git a/packages/integration-tests/fixtures/error-no-handler/fakeSentry.js b/packages/integration-tests/fixtures/errorhandling/fakeSentry.js similarity index 52% rename from packages/integration-tests/fixtures/error-no-handler/fakeSentry.js rename to packages/integration-tests/fixtures/errorhandling/fakeSentry.js index bca9200b..c4ca721f 100644 --- a/packages/integration-tests/fixtures/error-no-handler/fakeSentry.js +++ b/packages/integration-tests/fixtures/errorhandling/fakeSentry.js @@ -1,13 +1,15 @@ -import { createServer } from "http"; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { createServer } = require("http"); const port = process.env["FAKE_SENTRY_PORT"] || 3000; const server = createServer((req, res) => { + console.log("[SANTRY] incoming request", req.url); res.statusCode = 503; res.end("Error: Santry unreachable"); }); server.listen(port, () => { // eslint-disable-next-line no-console - console.log(`Santry running on http://localhost:${port}/`); + console.log(`[SANTRY] running on http://localhost:${port}/`); }); diff --git a/packages/integration-tests/fixtures/error-no-handler/input/bundle.js b/packages/integration-tests/fixtures/errorhandling/input/bundle.js similarity index 100% rename from packages/integration-tests/fixtures/error-no-handler/input/bundle.js rename to packages/integration-tests/fixtures/errorhandling/input/bundle.js diff --git a/packages/integration-tests/fixtures/errorhandling/plugin-options-wrong.ts b/packages/integration-tests/fixtures/errorhandling/plugin-options-wrong.ts new file mode 100644 index 00000000..fb5960b1 --- /dev/null +++ b/packages/integration-tests/fixtures/errorhandling/plugin-options-wrong.ts @@ -0,0 +1,10 @@ +export default { + url: `http://localhost:${process.env["FAKE_SENTRY_PORT"] || 3000}`, + authToken: "fake-auth", + org: "fake-org", + project: "fake-project", + release: { + name: "1.0.0", + }, + debug: true, +}; diff --git a/packages/integration-tests/fixtures/errorhandling/plugin-options.ts b/packages/integration-tests/fixtures/errorhandling/plugin-options.ts new file mode 100644 index 00000000..fb5960b1 --- /dev/null +++ b/packages/integration-tests/fixtures/errorhandling/plugin-options.ts @@ -0,0 +1,10 @@ +export default { + url: `http://localhost:${process.env["FAKE_SENTRY_PORT"] || 3000}`, + authToken: "fake-auth", + org: "fake-org", + project: "fake-project", + release: { + name: "1.0.0", + }, + debug: true, +}; diff --git a/packages/integration-tests/utils/create-cjs-bundles-promise.ts b/packages/integration-tests/utils/create-cjs-bundles-promise.ts deleted file mode 100644 index 43a908a6..00000000 --- a/packages/integration-tests/utils/create-cjs-bundles-promise.ts +++ /dev/null @@ -1,112 +0,0 @@ -import * as vite from "vite"; -import * as path from "path"; -import * as rollup from "rollup"; -import { default as webpack4 } from "webpack4"; -import { webpack as webpack5 } from "webpack5"; -import * as esbuild from "esbuild"; -import { Options } from "@sentry/bundler-plugin-core"; -import { sentryVitePlugin } from "@sentry/vite-plugin"; -import { sentryWebpackPlugin } from "@sentry/webpack-plugin"; -import { sentryEsbuildPlugin } from "@sentry/esbuild-plugin"; -import { sentryRollupPlugin } from "@sentry/rollup-plugin"; - -// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -const nodejsMajorversion = process.version.split(".")[0]!.slice(1); - -export async function createCjsBundles( - entrypoints: { [name: string]: string }, - outFolder: string, - sentryUnpluginOptions: Options, - plugins: string[] = [] -): Promise { - if (plugins.length === 0 || plugins.includes("vite")) { - await vite.build({ - clearScreen: false, - build: { - sourcemap: true, - outDir: path.join(outFolder, "vite"), - rollupOptions: { - input: entrypoints, - output: { - format: "cjs", - entryFileNames: "[name].js", - }, - }, - }, - plugins: [sentryVitePlugin(sentryUnpluginOptions)], - }); - } - if (plugins.length === 0 || plugins.includes("rollup")) { - await rollup - .rollup({ - input: entrypoints, - plugins: [sentryRollupPlugin(sentryUnpluginOptions)], - }) - .then((bundle) => - bundle.write({ - sourcemap: true, - dir: path.join(outFolder, "rollup"), - format: "cjs", - exports: "named", - }) - ); - } - - if (plugins.length === 0 || plugins.includes("esbuild")) { - await esbuild.build({ - sourcemap: true, - entryPoints: entrypoints, - outdir: path.join(outFolder, "esbuild"), - plugins: [sentryEsbuildPlugin(sentryUnpluginOptions)], - minify: true, - bundle: true, - format: "cjs", - }); - } - - // Webpack 4 doesn't work on Node.js versions >= 18 - if (parseInt(nodejsMajorversion) < 18 && (plugins.length === 0 || plugins.includes("webpack4"))) { - webpack4( - { - devtool: "source-map", - mode: "production", - entry: entrypoints, - cache: false, - output: { - path: path.join(outFolder, "webpack4"), - libraryTarget: "commonjs", - }, - target: "node", // needed for webpack 4 so we can access node api - plugins: [sentryWebpackPlugin(sentryUnpluginOptions)], - }, - (err) => { - if (err) { - throw err; - } - } - ); - } - - if (plugins.length === 0 || plugins.includes("webpack5")) { - webpack5( - { - devtool: "source-map", - cache: false, - entry: entrypoints, - output: { - path: path.join(outFolder, "webpack5"), - library: { - type: "commonjs", - }, - }, - mode: "production", - plugins: [sentryWebpackPlugin(sentryUnpluginOptions)], - }, - (err) => { - if (err) { - throw err; - } - } - ); - } -} From 21713038bdae814e269877a4649f94cc72041b02 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 26 Feb 2025 17:12:46 +0100 Subject: [PATCH 4/7] run webpack 4 only on Node < 18 --- .../errorhandling/error-no-handler.test.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts b/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts index 3f6a0bba..a3afe1cf 100644 --- a/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts +++ b/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts @@ -2,6 +2,7 @@ /* eslint-disable jest/expect-expect */ import path from "path"; import { spawn } from "child_process"; +import { testIfNodeMajorVersionIsLessThan18 } from "../../utils/testIf"; jest.setTimeout(10_000); @@ -26,7 +27,7 @@ describe("Error throwing by default (no errorHandler)", () => { sentryServer.kill(); }); - test.each(["vite", "rollup", "webpack5", "webpack4", "esbuild"])( + test.each(["vite", "rollup", "webpack5", "esbuild"])( "doesn't throw when Sentry server responds with error code for %s", async (bundler) => { const buildProcess = spawn("yarn", ["ts-node", path.join(__dirname, `build-${bundler}.ts`)], { @@ -49,4 +50,28 @@ describe("Error throwing by default (no errorHandler)", () => { buildProcess.kill(); } ); + + testIfNodeMajorVersionIsLessThan18( + "doesn't throw when Sentry server responds with error code for webpack 4", + async () => { + const buildProcess = spawn("yarn", ["ts-node", path.join(__dirname, `build-webpack4.ts`)], { + env: { ...process.env, FAKE_SENTRY_PORT }, + stdio: "ignore", // <-- set to "inherit" to get build output. Deactivated to avoid spamming test logs. + }); + + const exitCode = await new Promise((resolve, reject) => { + buildProcess.on("exit", (code) => { + resolve(code ?? 99); + }); + + buildProcess.on("error", (err) => { + reject(err); + }); + }); + + expect(exitCode).toBe(0); + + buildProcess.kill(); + } + ); }); From c69e84e936636d88da09a686cf35d2f87fe2de32 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 3 Mar 2025 17:39:53 +0100 Subject: [PATCH 5/7] cleanup tests, maybe fix windows fails? --- .../fixtures/errorhandling/build-esbuild.ts | 2 +- .../fixtures/errorhandling/error-no-handler.test.ts | 2 +- .../fixtures/errorhandling/plugin-options-wrong.ts | 10 ---------- 3 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 packages/integration-tests/fixtures/errorhandling/plugin-options-wrong.ts diff --git a/packages/integration-tests/fixtures/errorhandling/build-esbuild.ts b/packages/integration-tests/fixtures/errorhandling/build-esbuild.ts index 7e2a90cc..080d9d12 100644 --- a/packages/integration-tests/fixtures/errorhandling/build-esbuild.ts +++ b/packages/integration-tests/fixtures/errorhandling/build-esbuild.ts @@ -5,7 +5,7 @@ import path from "path"; build({ entryPoints: [path.join(__dirname, "input", "bundle.js")], - outdir: "./out/esbuild", + outdir: path.join(__dirname, "out", "esbuild"), plugins: [sentryEsbuildPlugin(pluginOptions)], minify: true, bundle: true, diff --git a/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts b/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts index a3afe1cf..e1b965cc 100644 --- a/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts +++ b/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts @@ -9,7 +9,7 @@ jest.setTimeout(10_000); describe("Error throwing by default (no errorHandler)", () => { const FAKE_SENTRY_PORT = "9876"; - const sentryServer = spawn("node", ["fakeSentry.js"], { + const sentryServer = spawn("node", [path.join(__dirname, "fakeSentry.js")], { cwd: __dirname, stdio: "ignore", // <-- set to "inherit" to get server logs. Deactivated to avoid test logs. env: { ...process.env, FAKE_SENTRY_PORT }, diff --git a/packages/integration-tests/fixtures/errorhandling/plugin-options-wrong.ts b/packages/integration-tests/fixtures/errorhandling/plugin-options-wrong.ts deleted file mode 100644 index fb5960b1..00000000 --- a/packages/integration-tests/fixtures/errorhandling/plugin-options-wrong.ts +++ /dev/null @@ -1,10 +0,0 @@ -export default { - url: `http://localhost:${process.env["FAKE_SENTRY_PORT"] || 3000}`, - authToken: "fake-auth", - org: "fake-org", - project: "fake-project", - release: { - name: "1.0.0", - }, - debug: true, -}; From 5d8d658da7b52298277c05d196c54a606fa4cc4a Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 3 Mar 2025 17:54:29 +0100 Subject: [PATCH 6/7] maybe fix windows now? --- .../fixtures/errorhandling/error-no-handler.test.ts | 3 +++ .../integration-tests/fixtures/errorhandling/fakeSentry.js | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts b/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts index e1b965cc..461b35e8 100644 --- a/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts +++ b/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts @@ -13,6 +13,7 @@ describe("Error throwing by default (no errorHandler)", () => { cwd: __dirname, stdio: "ignore", // <-- set to "inherit" to get server logs. Deactivated to avoid test logs. env: { ...process.env, FAKE_SENTRY_PORT }, + shell: true, }); beforeAll(async () => { @@ -33,6 +34,7 @@ describe("Error throwing by default (no errorHandler)", () => { const buildProcess = spawn("yarn", ["ts-node", path.join(__dirname, `build-${bundler}.ts`)], { env: { ...process.env, FAKE_SENTRY_PORT }, stdio: "ignore", // <-- set to "inherit" to get build output. Deactivated to avoid spamming test logs. + shell: true, }); const exitCode = await new Promise((resolve, reject) => { @@ -57,6 +59,7 @@ describe("Error throwing by default (no errorHandler)", () => { const buildProcess = spawn("yarn", ["ts-node", path.join(__dirname, `build-webpack4.ts`)], { env: { ...process.env, FAKE_SENTRY_PORT }, stdio: "ignore", // <-- set to "inherit" to get build output. Deactivated to avoid spamming test logs. + shell: true, }); const exitCode = await new Promise((resolve, reject) => { diff --git a/packages/integration-tests/fixtures/errorhandling/fakeSentry.js b/packages/integration-tests/fixtures/errorhandling/fakeSentry.js index c4ca721f..bacc9540 100644 --- a/packages/integration-tests/fixtures/errorhandling/fakeSentry.js +++ b/packages/integration-tests/fixtures/errorhandling/fakeSentry.js @@ -4,6 +4,7 @@ const { createServer } = require("http"); const port = process.env["FAKE_SENTRY_PORT"] || 3000; const server = createServer((req, res) => { + // eslint-disable-next-line no-console console.log("[SANTRY] incoming request", req.url); res.statusCode = 503; res.end("Error: Santry unreachable"); From 600f2abe0f5c869494a701afedbd0fc1dac8351e Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 3 Mar 2025 18:05:28 +0100 Subject: [PATCH 7/7] run webpack 4 test --- .../errorhandling/error-no-handler.test.ts | 34 ++++--------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts b/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts index 461b35e8..fd93a8cb 100644 --- a/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts +++ b/packages/integration-tests/fixtures/errorhandling/error-no-handler.test.ts @@ -2,7 +2,6 @@ /* eslint-disable jest/expect-expect */ import path from "path"; import { spawn } from "child_process"; -import { testIfNodeMajorVersionIsLessThan18 } from "../../utils/testIf"; jest.setTimeout(10_000); @@ -28,7 +27,13 @@ describe("Error throwing by default (no errorHandler)", () => { sentryServer.kill(); }); - test.each(["vite", "rollup", "webpack5", "esbuild"])( + const bundlersToTest = ["vite", "rollup", "webpack5", "esbuild"]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + if (parseInt(process.version.split(".")[0]!.slice(1)) < 18) { + bundlersToTest.push("webpack4"); + } + + test.each(bundlersToTest)( "doesn't throw when Sentry server responds with error code for %s", async (bundler) => { const buildProcess = spawn("yarn", ["ts-node", path.join(__dirname, `build-${bundler}.ts`)], { @@ -52,29 +57,4 @@ describe("Error throwing by default (no errorHandler)", () => { buildProcess.kill(); } ); - - testIfNodeMajorVersionIsLessThan18( - "doesn't throw when Sentry server responds with error code for webpack 4", - async () => { - const buildProcess = spawn("yarn", ["ts-node", path.join(__dirname, `build-webpack4.ts`)], { - env: { ...process.env, FAKE_SENTRY_PORT }, - stdio: "ignore", // <-- set to "inherit" to get build output. Deactivated to avoid spamming test logs. - shell: true, - }); - - const exitCode = await new Promise((resolve, reject) => { - buildProcess.on("exit", (code) => { - resolve(code ?? 99); - }); - - buildProcess.on("error", (err) => { - reject(err); - }); - }); - - expect(exitCode).toBe(0); - - buildProcess.kill(); - } - ); });