diff --git a/packages/bundler-plugin-core/src/debug-id-upload.ts b/packages/bundler-plugin-core/src/debug-id-upload.ts index d201b3a0..3c458388 100644 --- a/packages/bundler-plugin-core/src/debug-id-upload.ts +++ b/packages/bundler-plugin-core/src/debug-id-upload.ts @@ -213,7 +213,7 @@ export async function prepareBundleForDebugIdUpload( logger: Logger, rewriteSourcesHook: RewriteSourcesHook ) { - let bundleContent; + let bundleContent: string; try { bundleContent = await promisify(fs.readFile)(bundleFilePath, "utf8"); } catch (e) { @@ -232,14 +232,11 @@ export async function prepareBundleForDebugIdUpload( return; } - const uniqueUploadName = `${debugId}-${chunkIndex}`; - + const uniqueSourceFileUploadPath = getUniqueUploadPath(uploadFolder, chunkIndex, bundleFilePath); bundleContent += `\n//# debugId=${debugId}`; - const writeSourceFilePromise = fs.promises.writeFile( - path.join(uploadFolder, `${uniqueUploadName}.js`), - bundleContent, - "utf-8" - ); + const writeSourceFilePromise = fs.promises + .mkdir(path.dirname(uniqueSourceFileUploadPath), { recursive: true }) + .then(() => fs.promises.writeFile(uniqueSourceFileUploadPath, bundleContent, "utf-8")); const writeSourceMapFilePromise = determineSourceMapPathFromBundle( bundleFilePath, @@ -249,7 +246,7 @@ export async function prepareBundleForDebugIdUpload( if (sourceMapPath) { await prepareSourceMapForDebugIdUpload( sourceMapPath, - path.join(uploadFolder, `${uniqueUploadName}.js.map`), + getUniqueUploadPath(uploadFolder, chunkIndex, sourceMapPath), debugId, rewriteSourcesHook, logger @@ -261,6 +258,27 @@ export async function prepareBundleForDebugIdUpload( await writeSourceMapFilePromise; } +function getUniqueUploadPath(uploadFolder: string, chunkIndex: number, filePath: string) { + return path.join( + uploadFolder, + // We add a "chunk index" segment to the path that is a simple incrementing number to avoid name collisions. + // Name collisions can happen when files are located "outside" of the current working directory, at different levels but they share a subpath. + // Example: + // - CWD: /root/foo/cwd + // - File 1: /root/foo/index.js -> ../foo/index.js -> foo/index.js + // - File 2: /foo/index.js -> ../../foo/index.js -> foo/index.js + `${chunkIndex}`, + path.normalize( + path + .relative(process.cwd(), filePath) + .split(path.sep) + // We filter out these "navigation" segments because a) they look ugly b) they will cause us to break out of the upload folder. + .filter((segment) => segment !== ".." && segment !== ".") + .join(path.sep) + ) + ); +} + /** * Looks for a particular string pattern (`sdbid-[debug ID]`) in the bundle * source and extracts the bundle's debug ID from it. @@ -379,7 +397,8 @@ async function prepareSourceMapForDebugIdUpload( } try { - await util.promisify(fs.writeFile)(targetPath, JSON.stringify(map), { + await fs.promises.mkdir(path.dirname(targetPath), { recursive: true }); + await fs.promises.writeFile(targetPath, JSON.stringify(map), { encoding: "utf8", }); } catch (e) {