Skip to content

Commit bc5b5b1

Browse files
committed
add prepareArtifacts option
1 parent cc4d21d commit bc5b5b1

File tree

1 file changed

+111
-82
lines changed

1 file changed

+111
-82
lines changed

packages/bundler-plugin-core/src/build-plugin-manager.ts

Lines changed: 111 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ export type SentryBuildPluginManager = {
7777
/**
7878
* Uploads sourcemaps using the "Debug ID" method. This function takes a list of build artifact paths that will be uploaded
7979
*/
80-
uploadSourcemaps(buildArtifactPaths: string[]): Promise<void>;
80+
uploadSourcemaps(
81+
buildArtifactPaths: string[],
82+
opts?: { prepareArtifacts?: boolean }
83+
): Promise<void>;
8184

8285
/**
8386
* Will delete artifacts based on the passed `sourcemaps.filesToDeleteAfterUpload` option.
@@ -546,9 +549,16 @@ export function createSentryBuildPluginManager(
546549
},
547550

548551
/**
549-
* Uploads sourcemaps using the "Debug ID" method. This function takes a list of build artifact paths that will be uploaded
552+
* Uploads sourcemaps using the "Debug ID" method.
553+
*
554+
* By default, this prepares bundles in a temporary folder before uploading. You can opt into an
555+
* in-place, direct upload path by setting `prepareArtifacts` to `false`. If `prepareArtifacts` is set to
556+
* `false`, no preparation (e.g. adding `//# debugId=...` and writing adjusted source maps) is performed and no temp folder is used.
557+
*
558+
* @param buildArtifactPaths - The paths of the build artifacts to upload
559+
* @param opts - Optional flags to control temp folder usage and preparation
550560
*/
551-
async uploadSourcemaps(buildArtifactPaths: string[]) {
561+
async uploadSourcemaps(buildArtifactPaths: string[], opts?: { prepareArtifacts?: boolean }) {
552562
if (!canUploadSourceMaps(options, logger, isDevMode)) {
553563
return;
554564
}
@@ -557,26 +567,16 @@ export function createSentryBuildPluginManager(
557567
// This is `forceTransaction`ed because this span is used in dashboards in the form of indexed transactions.
558568
{ name: "debug-id-sourcemap-upload", scope: sentryScope, forceTransaction: true },
559569
async () => {
570+
// If we're not using a temp folder, we must not prepare artifacts in-place (to avoid mutating user files)
571+
const shouldPrepare = opts?.prepareArtifacts ?? true;
572+
560573
let folderToCleanUp: string | undefined;
561574

562575
// It is possible that this writeBundle hook (which calls this function) is called multiple times in one build (for example when reusing the plugin, or when using build tooling like `@vitejs/plugin-legacy`)
563576
// Therefore we need to actually register the execution of this hook as dependency on the sourcemap files.
564577
const freeUploadDependencyOnBuildArtifacts = createDependencyOnBuildArtifacts();
565578

566579
try {
567-
const tmpUploadFolder = await startSpan(
568-
{ name: "mkdtemp", scope: sentryScope },
569-
async () => {
570-
return (
571-
process.env?.["SENTRY_TEST_OVERRIDE_TEMP_DIR"] ||
572-
(await fs.promises.mkdtemp(
573-
path.join(os.tmpdir(), "sentry-bundler-plugin-upload-")
574-
))
575-
);
576-
}
577-
);
578-
579-
folderToCleanUp = tmpUploadFolder;
580580
const assets = options.sourcemaps?.assets;
581581

582582
let globAssets: string | string[];
@@ -594,14 +594,17 @@ export function createSentryBuildPluginManager(
594594
async () =>
595595
await glob(globAssets, {
596596
absolute: true,
597-
nodir: true,
597+
// If we do not use a temp folder, we allow directories and files; CLI will traverse as needed when given paths.
598+
nodir: shouldPrepare,
598599
ignore: options.sourcemaps?.ignore,
599600
})
600601
);
601602

602-
const debugIdChunkFilePaths = globResult.filter((debugIdChunkFilePath) => {
603-
return !!stripQueryAndHashFromPath(debugIdChunkFilePath).match(/\.(js|mjs|cjs)$/);
604-
});
603+
const debugIdChunkFilePaths = shouldPrepare
604+
? globResult.filter((debugIdChunkFilePath) => {
605+
return !!stripQueryAndHashFromPath(debugIdChunkFilePath).match(/\.(js|mjs|cjs)$/);
606+
})
607+
: globResult;
605608

606609
// The order of the files output by glob() is not deterministic
607610
// Ensure order within the files so that {debug-id}-{chunkIndex} coupling is consistent
@@ -616,75 +619,101 @@ export function createSentryBuildPluginManager(
616619
"Didn't find any matching sources for debug ID upload. Please check the `sourcemaps.assets` option."
617620
);
618621
} else {
619-
const numUploadedFiles = await startSpan(
620-
{ name: "prepare-bundles", scope: sentryScope },
621-
async (prepBundlesSpan) => {
622-
// Preparing the bundles can be a lot of work and doing it all at once has the potential of nuking the heap so
623-
// instead we do it with a maximum of 16 concurrent workers
624-
const preparationTasks = debugIdChunkFilePaths.map(
625-
(chunkFilePath, chunkIndex) => async () => {
626-
await prepareBundleForDebugIdUpload(
627-
chunkFilePath,
628-
tmpUploadFolder,
629-
chunkIndex,
630-
logger,
631-
options.sourcemaps?.rewriteSources ?? defaultRewriteSourcesHook,
632-
options.sourcemaps?.resolveSourceMap
633-
);
634-
}
635-
);
636-
const workers: Promise<void>[] = [];
637-
const worker = async (): Promise<void> => {
638-
while (preparationTasks.length > 0) {
639-
const task = preparationTasks.shift();
640-
if (task) {
641-
await task();
622+
if (!shouldPrepare) {
623+
// Direct CLI upload from existing artifact paths (no preparation or temp copies)
624+
await startSpan({ name: "upload", scope: sentryScope }, async () => {
625+
const cliInstance = createCliInstance(options);
626+
await cliInstance.releases.uploadSourceMaps(options.release.name ?? "undefined", {
627+
include: [
628+
{
629+
paths: debugIdChunkFilePaths,
630+
rewrite: false,
631+
dist: options.release.dist,
632+
},
633+
],
634+
live: "rejectOnError",
635+
});
636+
});
637+
638+
logger.info("Successfully uploaded source maps to Sentry");
639+
} else {
640+
const tmpUploadFolder = await startSpan(
641+
{ name: "mkdtemp", scope: sentryScope },
642+
async () => {
643+
return (
644+
process.env?.["SENTRY_TEST_OVERRIDE_TEMP_DIR"] ||
645+
(await fs.promises.mkdtemp(
646+
path.join(os.tmpdir(), "sentry-bundler-plugin-upload-")
647+
))
648+
);
649+
}
650+
);
651+
folderToCleanUp = tmpUploadFolder;
652+
653+
// Prepare into temp folder, then upload
654+
await startSpan(
655+
{ name: "prepare-bundles", scope: sentryScope },
656+
async (prepBundlesSpan) => {
657+
// Preparing the bundles can be a lot of work and doing it all at once has the potential of nuking the heap so
658+
// instead we do it with a maximum of 16 concurrent workers
659+
const preparationTasks = debugIdChunkFilePaths.map(
660+
(chunkFilePath, chunkIndex) => async () => {
661+
await prepareBundleForDebugIdUpload(
662+
chunkFilePath,
663+
tmpUploadFolder,
664+
chunkIndex,
665+
logger,
666+
options.sourcemaps?.rewriteSources ?? defaultRewriteSourcesHook,
667+
options.sourcemaps?.resolveSourceMap
668+
);
669+
}
670+
);
671+
const workers: Promise<void>[] = [];
672+
const worker = async (): Promise<void> => {
673+
while (preparationTasks.length > 0) {
674+
const task = preparationTasks.shift();
675+
if (task) {
676+
await task();
677+
}
642678
}
679+
};
680+
for (let workerIndex = 0; workerIndex < 16; workerIndex++) {
681+
workers.push(worker());
643682
}
644-
};
645-
for (let workerIndex = 0; workerIndex < 16; workerIndex++) {
646-
workers.push(worker());
647-
}
648683

649-
await Promise.all(workers);
684+
await Promise.all(workers);
650685

651-
const files = await fs.promises.readdir(tmpUploadFolder);
652-
const stats = files.map((file) =>
653-
fs.promises.stat(path.join(tmpUploadFolder, file))
654-
);
655-
const uploadSize = (await Promise.all(stats)).reduce(
656-
(accumulator, { size }) => accumulator + size,
657-
0
658-
);
659-
660-
setMeasurement("files", files.length, "none", prepBundlesSpan);
661-
setMeasurement("upload_size", uploadSize, "byte", prepBundlesSpan);
662-
663-
await startSpan({ name: "upload", scope: sentryScope }, async () => {
664-
const cliInstance = createCliInstance(options);
665-
666-
await cliInstance.releases.uploadSourceMaps(
667-
options.release.name ?? "undefined", // unfortunately this needs a value for now but it will not matter since debug IDs overpower releases anyhow
668-
{
669-
include: [
670-
{
671-
paths: [tmpUploadFolder],
672-
rewrite: false,
673-
dist: options.release.dist,
674-
},
675-
],
676-
// We want this promise to throw if the sourcemaps fail to upload so that we know about it.
677-
// see: https://github.com/getsentry/sentry-cli/pull/2605
678-
live: "rejectOnError",
679-
}
686+
const files = await fs.promises.readdir(tmpUploadFolder);
687+
const stats = files.map((file) =>
688+
fs.promises.stat(path.join(tmpUploadFolder, file))
689+
);
690+
const uploadSize = (await Promise.all(stats)).reduce(
691+
(accumulator, { size }) => accumulator + size,
692+
0
680693
);
681-
});
682694

683-
return files.length;
684-
}
685-
);
695+
setMeasurement("files", files.length, "none", prepBundlesSpan);
696+
setMeasurement("upload_size", uploadSize, "byte", prepBundlesSpan);
697+
698+
await startSpan({ name: "upload", scope: sentryScope }, async () => {
699+
const cliInstance = createCliInstance(options);
700+
await cliInstance.releases.uploadSourceMaps(
701+
options.release.name ?? "undefined",
702+
{
703+
include: [
704+
{
705+
paths: [tmpUploadFolder],
706+
rewrite: false,
707+
dist: options.release.dist,
708+
},
709+
],
710+
live: "rejectOnError",
711+
}
712+
);
713+
});
714+
}
715+
);
686716

687-
if (numUploadedFiles > 0) {
688717
logger.info("Successfully uploaded source maps to Sentry");
689718
}
690719
}

0 commit comments

Comments
 (0)