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 @@ -3,7 +3,6 @@ import * as fs from 'fs';
import * as path from 'path';
import {
assertSourceMapUpload,
findDebugIdsInFilesWithGlobCharacters,
findDebugIdsWithConflictingSourcemaps,
findFilesWithForeignSourcemaps,
findFilesWithMultipleDebugIds,
Expand Down Expand Up @@ -73,9 +72,6 @@ const outputDirsWithSourceMaps = [
const debugIdsWithoutSourceMap = new Set(
outputDirsWithSourceMaps.flatMap(dir => [...debugIdsOfChunksWithoutSourceMap(dir)]),
);
const debugIdsSkippedByUploader = isStaticBuild
? []
: findDebugIdsInFilesWithGlobCharacters({ outputDir: SERVER_OUTPUT });

const { requests, bundles, sourcemaps, uploadedDebugIds } = assertSourceMapUpload({
outputDirs: outputDirsWithSourceMaps,
Expand All @@ -86,7 +82,7 @@ const { requests, bundles, sourcemaps, uploadedDebugIds } = assertSourceMapUploa
// Nitro 3 deletes `sourcesContent` from the server maps unless `experimental.sourcemapMinify` is `false`. The module
// sets it to `false`, which is the only reason the server marker survives into `sourcesContent`.
markers: { 'client source (Vite plugin)': CLIENT_MARKER, 'server source (Rollup plugin)': SERVER_MARKER },
debugIdsWithoutSourceMap: [...debugIdsWithoutSourceMap, ...debugIdsSkippedByUploader],
debugIdsWithoutSourceMap: [...debugIdsWithoutSourceMap],
});
console.log(`Found ${bundles.length} artifact bundle(s)\n`);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,6 @@ function sourceMapsIn(dir: string): string[] {
.map(entry => path.join(entry.parentPath, entry.name));
}

// TODO: The uploader passes the emitted file paths to `glob` as patterns, so it skips a file whose path has glob
// characters, like Nitro's `_libs/@tanstack/react-router+[...].mjs`. Remove this when the uploader matches paths literally.
function debugIdsOfFilesWithGlobCharacters(dir: string): Set<string> {
return new Set(
emittedJsFiles(dir)
.filter(file => /[[\]{}()*?]/.test(path.relative(dir, file)))
.flatMap(readDebugIds),
);
}

const requests = loadMockServerResults();

// --- The upload reached Sentry ---
Expand Down Expand Up @@ -125,10 +115,7 @@ for (const outputDir of [CLIENT_OUTPUT, SERVER_OUTPUT]) {
const injectedDebugIds = findInjectedDebugIds({ outputDir });
assert.ok(injectedDebugIds.length > 0, `Expected debug IDs to be injected into ${outputDir}`);

const exemptDebugIds = new Set([
...debugIdsOfChunksWithoutSourceMap(outputDir),
...debugIdsOfFilesWithGlobCharacters(outputDir),
]);
const exemptDebugIds = debugIdsOfChunksWithoutSourceMap(outputDir);
const unuploaded = injectedDebugIds.filter(debugId => !uploadedDebugIds.has(debugId) && !exemptDebugIds.has(debugId));
assert.deepEqual(unuploaded, [], `Expected every debug ID in ${outputDir} to have an uploaded source map`);

Expand Down
3 changes: 2 additions & 1 deletion packages/bundler-plugins/src/core/build-plugin-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { escape as escapeGlob } from 'glob';
import { SentryCliAdapter } from './cli';
import type { NormalizedOptions } from './options-mapping';
import { normalizeUserOptions, validateOptions } from './options-mapping';
Expand Down Expand Up @@ -708,7 +709,7 @@ export function createSentryBuildPluginManager(
logger.debug(
'No `sourcemaps.assets` option provided, falling back to uploading detected build artifacts.',
);
globAssets = buildArtifactPaths;
globAssets = buildArtifactPaths.map(asset => escapeGlob(asset.split(path.sep).join('/')));
}

const globResult = await startSpan({ name: 'glob', scope: sentryScope }, async () =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, sep } from 'node:path';
import { rollup } from 'rollup';
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
import { sentryRollupPlugin } from '../../src/rollup';

const upload = vi.hoisted(() => vi.fn());
const fixtures: string[] = [];
vi.mock('sentry', () => ({
createSentrySDK: () => ({
sourcemap: { upload },
release: {},
}),
}));

beforeEach(() => {
upload.mockReset();
});

afterEach(() => {
for (const fixture of fixtures.splice(0)) {
rmSync(fixture, { recursive: true, force: true });
}
});

test.each([
{ name: 'ordinary filenames', file: 'sdk.mjs', explicitAssets: false, nativePath: false },
{ name: 'glob characters in detected paths', file: 'sdk+[...].mjs', explicitAssets: false, nativePath: false },
{ name: 'explicit asset globs', file: 'sdk+[...].mjs', explicitAssets: true, nativePath: false },
{ name: 'native path separators', file: 'sdk+[...].mjs', explicitAssets: false, nativePath: true },
{ name: 'ignored assets', file: 'sdk+[...].mjs', explicitAssets: false, nativePath: false, ignored: true },
])('handles $name when uploading Rollup artifacts', async ({ file, explicitAssets, nativePath, ignored }) => {
const fixture = mkdtempSync(join(tmpdir(), 'sentry-rollup-literal-paths-'));
fixtures.push(fixture);
const input = join(fixture, 'input.js');
const output = join(fixture, 'dist', '_libs', file);
writeFileSync(input, 'export function example() { throw new Error("original fixture"); }\n');
const uploadedFiles: string[] = [];
upload.mockImplementation(async (options: { directory: string }) => {
uploadedFiles.push(...readdirSync(options.directory));
});

const bundle = await rollup({
input,
plugins: sentryRollupPlugin({
authToken: 'local-fixture-only',
org: 'local-fixture',
project: 'local-fixture',
telemetry: false,
silent: true,
release: { create: false, finalize: false },
sourcemaps: {
assets: explicitAssets ? join(fixture, 'dist', '**', '*.mjs').split(sep).join('/') : undefined,
ignore: ignored ? join(fixture, 'dist', '**', 'sdk+*.mjs').split(sep).join('/') : undefined,
},
}),
});
try {
await bundle.write({ file: nativePath ? output : output.replace(/\\/g, '/'), format: 'es', sourcemap: true });
} finally {
await bundle.close();
}

expect(existsSync(output)).toBe(true);
expect(existsSync(`${output}.map`)).toBe(true);
expect(readFileSync(output, 'utf8')).toContain('sentry-dbid-');
expect(uploadedFiles.filter(name => name.endsWith('.js'))).toHaveLength(ignored ? 0 : 1);
expect(uploadedFiles.filter(name => name.endsWith('.js.map'))).toHaveLength(ignored ? 0 : 1);
});
Loading