Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
"sentry-cli-mock.json": "["release","create","CURRENT_SHA","--project","fake-project"],
["release","set-commits","CURRENT_SHA","--auto"],
["release","finalize","CURRENT_SHA"],
["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"],
",
}
`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
"sentry-cli-mock.json": "["release","create","CURRENT_SHA","--project","fake-project"],
["release","set-commits","CURRENT_SHA","--auto"],
["release","finalize","CURRENT_SHA"],
["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"],
",
}
`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
",
"sentry-cli-mock.json": "["release","set-commits","CURRENT_SHA","--auto"],
["release","finalize","CURRENT_SHA"],
["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"],
",
}
`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as esbuild from "esbuild";
import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
import { sentryConfig } from "../configs/sourcemaps-assets-auto.config.js";

await esbuild.build({
entryPoints: ["./src/entry1.js", "./src/entry2.js"],
bundle: true,
outdir: "./out/sourcemaps-assets-auto",
minify: false,
format: "esm",
splitting: true,
chunkNames: "[name]",
plugins: [sentryEsbuildPlugin(sentryConfig)],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { expect } from "vitest";
import { test } from "./utils";

test(import.meta.url, ({ runBundler, readOutputFiles }) => {
runBundler({ SENTRY_TEST_RECORD_UPLOADED_SOURCES: "1" });
const files = readOutputFiles();

expect(Object.keys(files).sort()).toMatchInlineSnapshot(`
[
"chunk.js",
"entry1.js",
"entry2.js",
"sentry-cli-mock.json",
"sentry-uploaded-sources.json",
]
`);
expect(JSON.parse(files["sentry-uploaded-sources.json"]!)).toMatchInlineSnapshot(`
[
[
"sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000",
"../../src/entry1.js",
"../../src/entry1.js",
],
]
`);
expect(files["sentry-cli-mock.json"]).toMatchInlineSnapshot(`
"["release","create","CURRENT_SHA","--project","fake-project"],
["release","set-commits","CURRENT_SHA","--auto"],
["release","finalize","CURRENT_SHA"],
["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"],
"
`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as esbuild from "esbuild";
import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
import { sentryConfig } from "../configs/sourcemaps-ignore-auto.config.js";

await esbuild.build({
entryPoints: ["./src/entry1.js", "./src/entry2.js"],
bundle: true,
outdir: "./out/sourcemaps-ignore-auto",
minify: false,
format: "esm",
splitting: true,
chunkNames: "[name]",
plugins: [sentryEsbuildPlugin(sentryConfig)],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { expect } from "vitest";
import { test } from "./utils";

test(import.meta.url, ({ runBundler, readOutputFiles }) => {
runBundler({ SENTRY_TEST_RECORD_UPLOADED_SOURCES: "1" });
const files = readOutputFiles();

expect(Object.keys(files).sort()).toMatchInlineSnapshot(`
[
"chunk.js",
"entry1.js",
"entry2.js",
"sentry-cli-mock.json",
"sentry-uploaded-sources.json",
]
`);
expect(JSON.parse(files["sentry-uploaded-sources.json"]!)).toMatchInlineSnapshot(`
[
[
"sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000",
"../../src/entry1.js",
"../../src/entry1.js",
],
]
`);
expect(files["sentry-cli-mock.json"]).toMatchInlineSnapshot(`
"["release","create","CURRENT_SHA","--project","fake-project"],
["release","set-commits","CURRENT_SHA","--auto"],
["release","finalize","CURRENT_SHA"],
["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"],
"
`);
});
48 changes: 42 additions & 6 deletions packages/bundler-plugins/src/core/build-plugin-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export function _resetDeployedReleasesForTesting(): void {
* before the bundler writes them to disk. The upload reads from this folder, so the bundler can
* drop the source map assets from its own output.
*/
export type UploadResult = { uploaded: boolean; uploadedSourceMapPaths: string[] };

export type UploadStaging = {
readonly dir: string;
/** Number of artifacts written so far. */
Expand Down Expand Up @@ -127,11 +129,14 @@ export type SentryBuildPluginManager = {

/**
* Uploads sourcemaps using the "Debug ID" method. This function takes a list of build artifact paths that will be uploaded
*
* `uploadedSourceMapPaths` lists the source maps read from disk that were uploaded. It is empty
* when nothing was uploaded, in direct mode (`prepareArtifacts: false`) and for staged artifacts.
*/
uploadSourcemaps(
buildArtifactPaths: string[],
opts?: { prepareArtifacts?: boolean; staging?: UploadStaging },
): Promise<{ uploaded: boolean }>;
): Promise<UploadResult>;

/**
* Creates a staging folder that `uploadSourcemaps` uploads together with the build artifacts
Expand All @@ -157,6 +162,11 @@ export type SentryBuildPluginManager = {
* and none of `sourcemaps.ignore`.
*/
shouldUpload: UploadFilter;
/**
* Deletes source maps from the build output after their upload. A map that cannot be deleted
* goes to `errorHandler`, or fails the build when none is set.
*/
deleteFromOutput(sourceMapPaths: string[]): Promise<void>;
};

/**
Expand Down Expand Up @@ -232,7 +242,7 @@ export function createSentryBuildPluginManager(
createRelease: async () => {
/* noop */
},
uploadSourcemaps: async () => ({ uploaded: false }),
uploadSourcemaps: async () => ({ uploaded: false, uploadedSourceMapPaths: [] }),
createUploadStaging: async () => ({
dir: '',
count: 0,
Expand All @@ -250,6 +260,9 @@ export function createSentryBuildPluginManager(
decide: () => ({ enableHidden: false, deleteAfterUpload: false }),
logDecision: () => undefined,
shouldUpload: () => false,
deleteFromOutput: async () => {
/* noop */
},
},
deleteArtifacts: async () => {
/* noop */
Expand Down Expand Up @@ -655,16 +668,17 @@ export function createSentryBuildPluginManager(
async uploadSourcemaps(
buildArtifactPaths: string[],
opts?: { prepareArtifacts?: boolean; staging?: UploadStaging },
): Promise<{ uploaded: boolean }> {
): Promise<UploadResult> {
const staging = opts?.staging;

if (!canUploadSourceMaps(options, logger, isDevMode)) {
await staging?.discard();
return { uploaded: false };
return { uploaded: false, uploadedSourceMapPaths: [] };
}

const assets = options.sourcemaps?.assets;
let uploaded = false;
const preparedSourceMapPaths: string[] = [];

await startSpan(
// This is `forceTransaction`ed because this span is used in dashboards in the form of indexed transactions.
Expand Down Expand Up @@ -763,14 +777,17 @@ export function createSentryBuildPluginManager(
// Preparing the bundles can be a lot of work and doing it all at once has the potential of nuking the heap so
// instead we do it with a maximum of 16 concurrent workers
const preparationTasks = debugIdChunkFilePaths.map((chunkFilePath, chunkIndex) => async () => {
await prepareBundleForDebugIdUpload(
const prepared = await prepareBundleForDebugIdUpload(
chunkFilePath,
tmpUploadFolder,
stagedCount + chunkIndex,
logger,
options.sourcemaps?.rewriteSources ?? defaultRewriteSourcesHook,
options.sourcemaps?.resolveSourceMap,
);
if (prepared?.sourceMapPath !== undefined) {
preparedSourceMapPaths.push(prepared.sourceMapPath);
}
});
const workers: Promise<void>[] = [];
const worker = async (): Promise<void> => {
Expand Down Expand Up @@ -859,7 +876,7 @@ export function createSentryBuildPluginManager(
},
);

return { uploaded };
return { uploaded, uploadedSourceMapPaths: uploaded ? preparedSourceMapPaths.sort() : [] };
},

async createUploadStaging(): Promise<UploadStaging> {
Expand Down Expand Up @@ -959,6 +976,25 @@ export function createSentryBuildPluginManager(
},
logDecision,
shouldUpload,
async deleteFromOutput(sourceMapPaths) {
try {
await Promise.all(
sourceMapPaths.map(async sourceMapPath => {
try {
await fs.promises.rm(sourceMapPath, { force: true });
} catch (e) {
logger.error(`Could not delete source map ${sourceMapPath} from the build output.`);
throw e;
}
}),
);
} catch (e) {
sentryScope.captureException('Error deleting source maps after upload');
await safeFlushTelemetry(sentryClient);
// Throwing by default: a map left in the output could leak to production.
handleRecoverableError(e, true);
}
},
},

/**
Expand Down
7 changes: 6 additions & 1 deletion packages/bundler-plugins/src/core/debug-id-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ export function createDebugIdUploadFunction({ sentryBuildPluginManager }: DebugI
};
}

/** The source map of a prepared bundle. `sourceMapPath` is `undefined` for an inline map. */
export type PreparedBundle = { sourceMapPath: string | undefined };

/** Resolves to `undefined` when the bundle was skipped. */
export async function prepareBundleForDebugIdUpload(
bundleFilePath: string,
uploadFolder: string,
chunkIndex: number,
logger: Logger,
rewriteSourcesHook: RewriteSourcesHook,
resolveSourceMapHook: ResolveSourceMapHook | undefined,
): Promise<void> {
): Promise<PreparedBundle | undefined> {
let bundleContent: string;
try {
bundleContent = await fs.promises.readFile(bundleFilePath, 'utf8');
Expand Down Expand Up @@ -81,6 +85,7 @@ export async function prepareBundleForDebugIdUpload(
}

await writePreparedArtifact(uploadFolder, `${result.artifact.debugId}-${chunkIndex}`, result.artifact);
return { sourceMapPath };
}

export type PreparedUploadArtifact = {
Expand Down
2 changes: 1 addition & 1 deletion packages/bundler-plugins/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export {
generateModuleMetadataInjectorCode,
} from './utils';
export { createSentryBuildPluginManager } from './build-plugin-manager';
export type { SentryBuildPluginManager, UploadStaging } from './build-plugin-manager';
export type { SentryBuildPluginManager, UploadResult, UploadStaging } from './build-plugin-manager';
export { createDebugIdUploadFunction, addDebugIdToEmittedArtifacts, stampDebugId } from './debug-id-upload';
export { decideSourceMapHandling } from './sourcemap-settings';
export type { SourceMapDecision, SourceMapKind } from './sourcemap-settings';
Expand Down
Loading
Loading