diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/.gitignore b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/.gitignore new file mode 100644 index 000000000000..1dca2550f7e3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/.gitignore @@ -0,0 +1,23 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Mock Sentry server artifacts +.tmp_mock_uploads.json +.tmp_chunks +.tmp_build_stdout +.tmp_build_stderr + +# Logs +logs +*.log + +# Misc +.DS_Store diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/app/app.vue b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/app/app.vue new file mode 100644 index 000000000000..eb66eddd8a33 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/app/app.vue @@ -0,0 +1,12 @@ + + + diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/app/pages/index.vue b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/app/pages/index.vue new file mode 100644 index 000000000000..bc19868e4488 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/app/pages/index.vue @@ -0,0 +1,9 @@ + + + diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/assert-build.ts b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/assert-build.ts new file mode 100644 index 000000000000..5bf988c300ad --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/assert-build.ts @@ -0,0 +1,313 @@ +import * as assert from 'assert/strict'; +import * as fs from 'fs'; +import * as path from 'path'; +import { + findDebugIdsWithConflictingSourcemaps, + findFilesWithForeignSourcemaps, + findFilesWithMultipleDebugIds, + findInjectedDebugIds, + findSourceMapFiles, + findSourceMappingUrlComments, + getArtifactBundles, + getAssembleRequests, + getChunkUploadPosts, + getDebugIdPairs, + getSourcemaps, + loadMockServerResults, + startNodeServer, +} from '@sentry-internal/test-utils'; + +/** This variant omits `sourcemaps.filesToDeleteAfterUpload`, so Sentry must upload but not delete. */ +const keepClientSourceMaps = process.env.E2E_KEEP_CLIENT_SOURCEMAPS === 'true'; + +/** `nuxt generate` emits no `.output/server`. Keyed on the command so a missing one under `nuxt build` still fails. */ +const isStaticBuild = process.env.NUXT_COMMAND === 'generate'; + +/** Nuxt 5 writes the client build here directly, so Sentry deletes the maps in Vite's only output directory. */ +const CLIENT_OUTPUT = path.join('.output', 'public'); +const SERVER_OUTPUT = path.join('.output', 'server'); + +/** Both markers sit in comments, so bundlers strip them from the code but keep them in `sourcesContent`. */ +const CLIENT_MARKER = 'SOURCEMAP_MARKER_CLIENT'; +const SERVER_MARKER = 'SOURCEMAP_MARKER_SERVER'; + +const UUID_REGEX = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i; + +function filesContaining(dir: string, needle: string): string[] { + return fs + .readdirSync(dir, { recursive: true, withFileTypes: true }) + .filter(entry => entry.isFile()) + .map(entry => path.join(entry.parentPath, entry.name)) + .filter(file => fs.readFileSync(file, 'utf8').includes(needle)); +} + +/** + * Rolldown emits no source map for a chunk without mappable source, such as Vue's export helper. + * Such a chunk still gets a debug ID, but there is no map to upload for it. + */ +function debugIdsOfChunksWithoutSourceMap(dir: string): Set { + const debugIds = new Set(); + + for (const file of filesContaining(dir, 'sentry-dbid-')) { + if (file.endsWith('.map') || fs.existsSync(`${file}.map`)) { + continue; + } + + for (const match of fs.readFileSync(file, 'utf8').matchAll(/sentry-dbid-([\da-f-]{36})/gi)) { + debugIds.add((match[1] as string).toLowerCase()); + } + } + + return debugIds; +} + +// TODO: The uploader passes the emitted file paths to `glob` as patterns, so it skips a file whose path has glob +// characters, like Nitro 3's `_libs/_sentry/node+[...].mjs`. Remove this when the uploader matches paths literally. +function debugIdsOfFilesWithGlobCharacters(dir: string): Set { + const debugIds = new Set(); + + for (const file of filesContaining(dir, 'sentry-dbid-')) { + if (!/[[\]{}()*?]/.test(path.relative(dir, file))) { + continue; + } + + for (const match of fs.readFileSync(file, 'utf8').matchAll(/sentry-dbid-([\da-f-]{36})/gi)) { + debugIds.add((match[1] as string).toLowerCase()); + } + } + + return debugIds; +} + +console.log( + `Variant: ${isStaticBuild ? 'nuxt generate' : 'nuxt build'}, ` + + `client source maps ${keepClientSourceMaps ? 'kept' : 'deleted'}\n`, +); + +const requests = loadMockServerResults(); + +console.log(`Captured ${requests.length} requests to mock Sentry server:\n`); +for (const request of requests) { + console.log(` ${request.method} ${request.url} (${request.bodySize} bytes)`); +} +console.log(''); + +// --- The upload reached Sentry --- + +assert.ok( + requests.some(r => r.authorization.includes('fake-auth-token')), + 'Expected requests with the configured auth token', +); + +// Sentry rejects a release that names no project with a 400, and the mock server accepts anything, +// so assert on the body rather than on the call alone. +const releaseCreates = requests.filter(r => r.url?.includes('/releases') && r.method === 'POST'); +assert.ok(releaseCreates.length > 0, 'Expected a POST to create the release'); +for (const request of releaseCreates) { + const body = request.jsonBody as { version?: string; projects?: string[] } | undefined; + assert.equal( + body?.version, + 'test-release', + `Expected the release create body to carry the version, got ${JSON.stringify(body)}`, + ); + assert.deepEqual( + body?.projects, + ['test-project'], + `Expected the release create body to name the project, got ${JSON.stringify(body)}`, + ); +} + +assert.ok( + requests.some(r => r.url?.includes('/releases/') && r.method === 'PUT'), + 'Expected a PUT to finalize the release', +); + +assert.ok( + requests.some(r => r.url?.includes('/chunk-upload/') && r.method === 'GET'), + 'Expected a GET for the chunk-upload options', +); + +const chunkPosts = getChunkUploadPosts(requests); +assert.ok( + chunkPosts.some(r => r.bodySize > 0), + 'Expected at least one chunk upload POST with a non-empty body', +); + +const assembleRequests = getAssembleRequests(requests); +assert.ok(assembleRequests.length > 0, 'Expected at least one assemble request'); +for (const request of assembleRequests) { + assert.ok(request.assembleBody?.projects?.includes('test-project'), 'Expected assemble request for test-project'); + assert.equal(request.assembleBody?.version, 'test-release', 'Expected assemble request to reference the release'); + assert.ok((request.assembleBody?.chunks?.length ?? 0) > 0, 'Expected assemble request to have chunk checksums'); + for (const chunk of request.assembleBody?.chunks ?? []) { + assert.match(chunk, /^[\da-f]{40}$/i, `Expected a SHA-1 chunk checksum, got: ${chunk}`); + } +} + +const bundles = getArtifactBundles(requests); +assert.ok(bundles.length > 0, 'Expected at least one artifact bundle with a manifest'); +console.log(`Found ${bundles.length} artifact bundle(s)\n`); + +// --- Both bundlers uploaded --- + +const sourcemaps = getSourcemaps(bundles); +assert.ok( + sourcemaps.some(map => map.sourcemap.mappings?.length), + 'Expected at least one uploaded sourcemap with non-empty mappings', +); + +const containsMarker = (marker: string): boolean => + sourcemaps.some(map => map.sourcemap.sourcesContent?.some(source => source?.includes(marker))); + +// Vite builds the client and Nitro builds the server. Counting bundles would still pass with either +// plugin dropped, so each side is pinned to a marker only that side's source supplies. +assert.ok(containsMarker(CLIENT_MARKER), 'Expected an uploaded sourcemap carrying the client source (Vite plugin)'); +// TODO: Nitro 3 builds the server with Rolldown, and the uploaded server maps carry no `sourcesContent`: the +// module's `sourcemapExcludeSources: false` does not reach that build. Assert the server marker when that is fixed: +// assert.ok(containsMarker(SERVER_MARKER), 'Expected an uploaded sourcemap carrying the server source (Rollup plugin)'); + +// `rewriteSources` normalizes `../../../foo` to `./foo` so paths stay resolvable in Sentry. +const unnormalizedSources = [...new Set(sourcemaps.flatMap(map => map.sourcemap.sources ?? []))].filter( + source => source.startsWith('../') || path.isAbsolute(source), +); +assert.deepEqual(unnormalizedSources, [], `Expected every uploaded source to be normalized to './…'`); + +// --- Debug IDs tie the shipped bundle to the uploaded map --- + +const uploadedDebugIds = new Set(getDebugIdPairs(bundles).map(pair => pair.debugId.toLowerCase())); +assert.ok(uploadedDebugIds.size > 0, 'Expected at least one JS/sourcemap pair with matching debug IDs'); + +const malformedDebugIds = [...uploadedDebugIds].filter(debugId => !UUID_REGEX.test(debugId)); +assert.deepEqual(malformedDebugIds, [], 'Expected every uploaded debug ID to be a UUID'); + +// An uploaded map is only reachable at runtime if the shipped bundle claims the same ID. Inspecting +// the upload alone cannot show this. +// Nuxt 5 writes the client build to `CLIENT_OUTPUT` directly. After Sentry deletes the client maps there, a chunk +// that never had a map looks the same as one whose map was deleted. So the client upload is checked only when the +// maps are kept. The upload is the same in all variants. +const outputDirsWithSourceMaps = [ + ...(keepClientSourceMaps ? [CLIENT_OUTPUT] : []), + ...(isStaticBuild ? [] : [SERVER_OUTPUT]), +]; +const debugIdsWithoutSourceMap = new Set( + outputDirsWithSourceMaps.flatMap(dir => [...debugIdsOfChunksWithoutSourceMap(dir)]), +); +const debugIdsSkippedByUploader = isStaticBuild ? new Set() : debugIdsOfFilesWithGlobCharacters(SERVER_OUTPUT); + +for (const outputDir of isStaticBuild ? [CLIENT_OUTPUT] : [CLIENT_OUTPUT, SERVER_OUTPUT]) { + const injectedDebugIds = findInjectedDebugIds({ outputDir }); + assert.ok(injectedDebugIds.length > 0, `Expected debug IDs to be injected into ${outputDir}`); + // The exemption for chunks without a source map must not let a build without any uploaded map pass. + assert.ok( + injectedDebugIds.some(debugId => uploadedDebugIds.has(debugId)), + `Expected at least one debug ID in ${outputDir} to have an uploaded sourcemap`, + ); + + if (outputDirsWithSourceMaps.includes(outputDir)) { + const unuploaded = injectedDebugIds.filter( + debugId => + !uploadedDebugIds.has(debugId) && + !debugIdsWithoutSourceMap.has(debugId) && + !debugIdsSkippedByUploader.has(debugId), + ); + assert.deepEqual(unuploaded, [], `Expected every debug ID in ${outputDir} to have an uploaded sourcemap`); + } + + console.log(` ${outputDir}: ${injectedDebugIds.length} injected debug ID(s)`); + + // Nitro bundles the SSR build again, so its debug IDs can end up next to Nitro's own. + // Reference issue: https://github.com/getsentry/sentry-javascript/issues/18519 + const filesWithMultipleDebugIds = findFilesWithMultipleDebugIds({ outputDir }); + assert.deepEqual(filesWithMultipleDebugIds, [], `Expected at most one debug ID per file in ${outputDir}`); + + // A server file can borrow the debug ID of its SSR build chunk. Then the upload check above passes on + // Vite's map even if Nitro uploaded nothing. + const filesWithForeignSourcemaps = findFilesWithForeignSourcemaps({ outputDir }, sourcemaps); + assert.deepEqual( + filesWithForeignSourcemaps, + [], + `Expected the source map uploaded for each file in ${outputDir} to be generated for that file`, + ); +} + +// If both builds upload a map under a borrowed debug ID, Sentry can resolve with the wrong one. +const conflictingDebugIds = findDebugIdsWithConflictingSourcemaps(sourcemaps); +assert.deepEqual(conflictingDebugIds, [], 'Expected every uploaded debug ID to resolve to exactly one source map'); + +// TODO: Nitro 3 bundles the SSR build's output again, but its maps stop at that output +// (`.nuxt/vite/services/ssr/...`) and do not chain to the SSR build's own maps. So server frames do not reach +// `.vue` files. Assert this when the server maps reach the original sources: +// const serverDebugIds = new Set(findInjectedDebugIds({ outputDir: SERVER_OUTPUT })); +// const serverSources = sourcemaps +// .filter(map => map.debugId && serverDebugIds.has(map.debugId)) +// .flatMap(map => getSourcemapSources(map.sourcemap)); +// assert.ok(serverSources.some(source => source.endsWith('app/pages/index.vue'))); +console.log(''); + +// --- What the build leaves behind in the client output --- + +const clientSourceMaps = findSourceMapFiles({ outputDir: CLIENT_OUTPUT }); + +if (keepClientSourceMaps) { + assert.ok(clientSourceMaps.length > 0, `Expected Sentry to leave the user-enabled maps in ${CLIENT_OUTPUT}`); + console.log(` ${clientSourceMaps.length} source map(s) kept in ${CLIENT_OUTPUT}, as configured\n`); +} else { + // This directory is served to the internet, so a surviving `.map` hands out the original source. + assert.deepEqual(clientSourceMaps, [], `Expected no source maps in ${CLIENT_OUTPUT} after upload`); + + // The maps are gone, so a surviving reference only 404s in devtools and leaks where they were. + const danglingReferences = findSourceMappingUrlComments({ outputDir: CLIENT_OUTPUT }); + assert.deepEqual(danglingReferences, [], `Expected no sourceMappingURL comments in ${CLIENT_OUTPUT}`); + + // Catches source maps inlined as `data:` URIs, which the reference check above skips by design. + const leakedSource = filesContaining(CLIENT_OUTPUT, CLIENT_MARKER); + assert.deepEqual(leakedSource, [], `Expected no original client source under ${CLIENT_OUTPUT}`); + + console.log(` ${CLIENT_OUTPUT} is free of source maps, sourceMappingURL comments and original source\n`); +} + +// --- What the built server answers for the client source maps --- + +// `nuxt generate` emits no server, and a static host serves only the files that exist. +if (!isStaticBuild) { + const clientChunkUrls = fs + .readdirSync(CLIENT_OUTPUT, { recursive: true, withFileTypes: true }) + .filter(entry => entry.isFile() && entry.name.endsWith('.js')) + .map( + entry => `/${path.relative(CLIENT_OUTPUT, path.join(entry.parentPath, entry.name)).split(path.sep).join('/')}`, + ); + assert.ok(clientChunkUrls.length > 0, `Expected client chunks in ${CLIENT_OUTPUT}`); + + const server = await startNodeServer({ entry: path.join(SERVER_OUTPUT, 'index.mjs'), port: 3030 }); + + try { + // Without this, a server that serves no public files at all would pass the 404 checks. + const chunkStatus = (await fetch(`${server.baseUrl}${clientChunkUrls[0]}`)).status; + assert.equal(chunkStatus, 200, `Expected the server to serve the client chunk ${clientChunkUrls[0]}`); + + // Nitro serves public files from a list it writes into the server bundle. A map deleted after that + // stays on the list, and Nitro answers its request with a 500 instead of a 404. + const responses = await Promise.all( + clientChunkUrls.map(async chunkUrl => { + const mapUrl = `${chunkUrl}.map`; + const expectedStatus = fs.existsSync(path.join(CLIENT_OUTPUT, mapUrl)) ? 200 : 404; + const { status } = await fetch(`${server.baseUrl}${mapUrl}`); + return { mapUrl, status, expectedStatus }; + }), + ); + const unexpectedStatuses = responses + .filter(({ status, expectedStatus }) => status !== expectedStatus) + .map(({ mapUrl, status, expectedStatus }) => `${mapUrl} → ${status}, expected ${expectedStatus}`); + assert.deepEqual( + unexpectedStatuses, + [], + 'Expected the server to answer each client source map URL with 200 if the map is in the output, else 404', + ); + + console.log(` The server answers all ${clientChunkUrls.length} client source map URL(s) as expected\n`); + } finally { + await server.close(); + } +} + +console.log('All sourcemap assertions passed!'); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/nuxt.config.ts b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/nuxt.config.ts new file mode 100644 index 000000000000..f2f14911dde3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/nuxt.config.ts @@ -0,0 +1,33 @@ +// Nuxt defaults `sourcemap.client` to `false`, which the SDK respects as a deliberate opt-out, so +// an app that never mentions `sourcemap`, uploads nothing client-side. `'hidden'` is what the SDK's +// own warning tells users to set, which makes it the setup worth regression-testing. +const keepClientSourceMaps = process.env.E2E_KEEP_CLIENT_SOURCEMAPS === 'true'; + +export default defineNuxtConfig({ + compatibilityDate: '2025-06-06', + imports: { autoImport: false }, + + sourcemap: { client: 'hidden' }, + + modules: ['@sentry/nuxt/module'], + + runtimeConfig: { + public: { + sentry: { + dsn: 'https://public@dsn.ingest.sentry.io/1337', + }, + }, + }, + + sentry: { + sentryUrl: 'http://localhost:3032', + authToken: 'fake-auth-token', + org: 'test-org', + project: 'test-project', + release: { name: 'test-release' }, + // Dropping `filesToDeleteAfterUpload` is the whole point of the "kept" variant: Sentry should + // upload the maps and leave the emitted files alone. + sourcemaps: keepClientSourceMaps ? {} : { filesToDeleteAfterUpload: ['.output/public/**/*.map'] }, + debug: true, + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/package.json b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/package.json new file mode 100644 index 000000000000..ccc0df377bcd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/package.json @@ -0,0 +1,47 @@ +{ + "name": "nuxt-5-sourcemaps", + "description": "E2E test app asserting what the Nuxt SDK uploads to Sentry and what it leaves behind in `.output` on Nuxt 5 (Nitro 3).", + "private": true, + "type": "module", + "scripts": { + "build": "node start-mock-sentry-server.mjs & nuxt ${NUXT_COMMAND:-build} > .tmp_build_stdout 2> .tmp_build_stderr; BUILD_EXIT=$?; kill %1 2>/dev/null; if [ $BUILD_EXIT -ne 0 ]; then cat .tmp_build_stdout; cat .tmp_build_stderr >&2; fi; exit $BUILD_EXIT", + "clean": "npx nuxi cleanup", + "test:build": "pnpm install && pnpm build", + "test:assert": "pnpm ts-node --script-mode assert-build.ts", + "test:build:keep-client-sourcemaps": "E2E_KEEP_CLIENT_SOURCEMAPS=true pnpm test:build", + "test:assert:keep-client-sourcemaps": "E2E_KEEP_CLIENT_SOURCEMAPS=true pnpm test:assert", + "test:build:static": "NUXT_COMMAND=generate pnpm test:build", + "test:assert:static": "NUXT_COMMAND=generate pnpm test:assert" + }, + "dependencies": { + "@sentry/nuxt": "file:../../packed/sentry-nuxt-packed.tgz", + "nitro": "latest", + "nuxt": "npm:nuxt-nightly@5x" + }, + "devDependencies": { + "@playwright/test": "~1.63.0", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@types/node": "^22.20.0", + "ts-node": "10.9.1", + "typescript": "~5.0.0" + }, + "volta": { + "extends": "../../package.json", + "pnpm": "11.10.0" + }, + "sentryTest": { + "optional": true, + "optionalVariants": [ + { + "build-command": "pnpm test:build:keep-client-sourcemaps", + "assert-command": "pnpm test:assert:keep-client-sourcemaps", + "label": "nuxt-5-sourcemaps (client source maps kept)" + }, + { + "build-command": "pnpm test:build:static", + "assert-command": "pnpm test:assert:static", + "label": "nuxt-5-sourcemaps (static / nuxt generate)" + } + ] + } +} diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/pnpm-workspace.yaml new file mode 100644 index 000000000000..4210a62a804d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + '@sentry/cli': true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/sentry.client.config.ts b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/sentry.client.config.ts new file mode 100644 index 000000000000..795fc10a560c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/sentry.client.config.ts @@ -0,0 +1,7 @@ +import * as Sentry from '@sentry/nuxt'; +import { useRuntimeConfig } from '#imports'; + +Sentry.init({ + dsn: useRuntimeConfig().public.sentry.dsn, + tracesSampleRate: 1.0, +}); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/sentry.server.config.ts new file mode 100644 index 000000000000..38ed95a35801 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/sentry.server.config.ts @@ -0,0 +1,6 @@ +import * as Sentry from '@sentry/nuxt'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, +}); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/server/api/server-error.ts b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/server/api/server-error.ts new file mode 100644 index 000000000000..dba169666ee1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/server/api/server-error.ts @@ -0,0 +1,8 @@ +import { defineHandler } from 'nitro'; + +// SOURCEMAP_MARKER_SERVER — the server counterpart of the client marker. Nitro defaults to +// `sourcemapExcludeSources: true`, which would drop this from the uploaded map; the Sentry module +// flips it to `false`, so finding this marker in `sourcesContent` is what proves that still works. +export default defineHandler(() => { + throw new Error('Server error from the Nuxt source map E2E app'); +}); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/start-mock-sentry-server.mjs b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/start-mock-sentry-server.mjs new file mode 100644 index 000000000000..1680a6360165 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/start-mock-sentry-server.mjs @@ -0,0 +1,3 @@ +import { startMockSentryServer } from '@sentry-internal/test-utils'; + +startMockSentryServer({ org: 'test-org' }); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/tsconfig.json b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/tsconfig.json new file mode 100644 index 000000000000..a2aac493d878 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["dom", "esnext"], + "module": "esnext", + "moduleResolution": "bundler", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "noEmit": true + }, + "include": ["**/*.ts", "**/*.vue"], + "exclude": ["node_modules", ".output", ".nuxt"] +}