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
9 changes: 9 additions & 0 deletions .changeset/default-upload-source-maps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"wrangler": minor
---

Upload source maps by default for `wrangler deploy`, `wrangler versions upload` and `wrangler preview`

When `upload_source_maps` isn't set in config or via `--upload-source-maps`, Wrangler now generates and uploads source maps so stack traces in logs and `wrangler tail` point at your original source without extra setup. In this default mode, source maps that can't be resolved (for example inline `data:` maps or missing `.map` files from a custom build) are skipped rather than failing the upload. Setting `upload_source_maps = true` keeps the existing strict behaviour, and `upload_source_maps = false` (or `--no-upload-source-maps` for deploy and versions upload) opts out.

`wrangler build --experimental-cf-build-output` is unchanged and still only includes source maps when `upload_source_maps = true`.
5 changes: 5 additions & 0 deletions packages/workers-utils/src/config/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,11 @@ interface EnvironmentInheritable {
/**
* Include source maps when uploading this worker.
*
* Defaults to `true` for `wrangler deploy`, `wrangler versions upload` and
* `wrangler preview`, where source maps that can't be resolved are skipped
* rather than failing the upload. Defaults to `false` for
* `wrangler build --experimental-cf-build-output`.
*
* For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#source-maps
*
* @inheritable
Expand Down
56 changes: 56 additions & 0 deletions packages/wrangler/src/__tests__/deploy/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,62 @@ export default { fetch() { return new Response(mod); } };`;
await runWrangler("deploy");
});

it("should include source map with bundle by default", async ({
expect,
}) => {
writeWranglerConfig({ main: "index.ts" });
writeWorkerSource({ format: "ts" });
mockSubDomainRequest();
mockUploadWorkerRequest({
expectedMainModule: "index.js",
expectedModules: {
"index.js.map": expect.stringMatching(
/"sources":\["another.ts","index.ts"\],"sourceRoot":"".*"file":"index.js"/
),
},
});

await runWrangler("deploy");
});

it("should skip unresolvable source maps by default rather than failing", async () => {
writeWranglerConfig({ no_bundle: true, main: "index.js" });
fs.writeFileSync(
"index.js",
`export default { fetch() { return new Response("Hello World"); } }\n` +
"//# sourceMappingURL=missing.js.map"
);

mockSubDomainRequest();
mockUploadWorkerRequest({
expectedMainModule: "index.js",
expectedModules: {
"index.js.map": null,
},
});

await runWrangler("deploy");
});

it("should fail on unresolvable source maps when upload_source_maps = true", async ({
expect,
}) => {
writeWranglerConfig({
no_bundle: true,
main: "index.js",
upload_source_maps: true,
});
fs.writeFileSync(
"index.js",
`export default { fetch() { return new Response("Hello World"); } }\n` +
"//# sourceMappingURL=missing.js.map"
);

await expect(runWrangler("deploy")).rejects.toThrow(
/Invalid source map path in .*index\.js: .*missing\.js\.map does not exist\./
);
});

it("should not include source map with bundle when upload_source_maps = false", async () => {
writeWranglerConfig({
main: "index.ts",
Expand Down
10 changes: 10 additions & 0 deletions packages/wrangler/src/__tests__/deploy/formats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,11 @@ export default{
Content-Type: application/wasm

Hello wasm World!
------formdata-undici-0.test
Content-Disposition: form-data; name="index.js.map"; filename="index.js.map"
Content-Type: application/source-map

{"version":3,"sources":["index.js"],"sourceRoot":"","sourcesContent":["\\nimport txt from './textfile.txt';\\nimport hello from './hello.wasm';\\nexport default{\\n async fetch(){\\n\\t\\tconst module = await WebAssembly.instantiate(hello);\\n return new Response(txt + module.exports.hello);\\n }\\n}\\n"],"mappings":";AACA,OAAO,SAAS;AAChB,OAAO,WAAW;AAClB,IAAO,gBAAO;AAAA,EACZ,MAAM,QAAO;AACb,UAAM,SAAS,MAAM,YAAY,YAAY,KAAK;AAChD,WAAO,IAAI,SAAS,MAAM,OAAO,QAAQ,KAAK;AAAA,EAChD;AACF;","names":[],"file":"index.js"}
------formdata-undici-0.test--
"
`);
Expand Down Expand Up @@ -1311,6 +1316,11 @@ export default{
Content-Type: application/wasm

Hello wasm World!
------formdata-undici-0.test
Content-Disposition: form-data; name="index.js.map"; filename="index.js.map"
Content-Type: application/source-map

{"version":3,"sources":["index.js"],"sourceRoot":"","sourcesContent":["\\nimport txt from './textfile.txt';\\nimport hello from './hello.wasm';\\nexport default{\\n async fetch(){\\n\\t\\tconst module = await WebAssembly.instantiate(hello);\\n return new Response(txt + module.exports.hello);\\n }\\n}\\n"],"mappings":";AACA,OAAO,SAAS;AAChB,OAAO,WAAW;AAClB,IAAO,gBAAO;AAAA,EACZ,MAAM,QAAO;AACb,UAAM,SAAS,MAAM,YAAY,YAAY,KAAK;AAChD,WAAO,IAAI,SAAS,MAAM,OAAO,QAAQ,KAAK;AAAA,EAChD;AACF;","names":[],"file":"index.js"}
------formdata-undici-0.test--
"
`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,29 @@ describe("loadSourceMaps", () => {
expect(result[0].name).toBe("src/index.ts.map");
});

it("loads bundled source maps when the output path is absolute", ({
expect,
}) => {
const entryDir = createTempDir();
const outDir = createTempDir();
writeFileSync(path.join(outDir, "index.js.map"), validSourceMap);

const main = makeModule(
"index.js",
path.join(entryDir, "index.js"),
"const x = 1;\n"
);
const bundle: SourceMapBundle = {
sourceMapPath: path.join(outDir, "index.js.map"),
sourceMapMetadata: { tmpDir: outDir, entryDirectory: entryDir },
};

const result = loadSourceMaps(main, [], bundle);

expect(result).toHaveLength(1);
expect(result[0].name).toBe("index.js.map");
});

it("throws when bundled source map file is missing", ({ expect }) => {
const dir = createTempDir();
const main = makeModule(
Expand Down Expand Up @@ -85,6 +108,30 @@ describe("loadSourceMaps", () => {
expect(result[0].name).toBe("index.js.map");
});

it("skips unresolvable source maps in scan mode when not strict", ({
expect,
}) => {
const dir = createTempDir();
writeFileSync(path.join(dir, "other.js.map"), validSourceMap);

const main = makeModule(
"index.js",
path.join(dir, "index.js"),
`console.log("hello");\n//# sourceMappingURL=missing.js.map`
);
const other = makeModule(
"other.js",
path.join(dir, "other.js"),
`console.log("other");\n//# sourceMappingURL=other.js.map`
);

expect(() => loadSourceMaps(main, [other], {})).toThrow();

const result = loadSourceMaps(main, [other], {}, { strict: false });

expect(result.map((sourceMap) => sourceMap.name)).toEqual(["other.js.map"]);
});

it("handles multiple modules with source maps in scan mode", ({ expect }) => {
const dir = createTempDir();
writeFileSync(path.join(dir, "a.js.map"), validSourceMap);
Expand Down
164 changes: 84 additions & 80 deletions packages/wrangler/src/__tests__/preview.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6140,93 +6140,97 @@ describe("wrangler preview", () => {
);
});

test("should include source maps in deployment modules when upload_source_maps is enabled", async ({
expect,
}) => {
writeFileSync(
"wrangler.json",
JSON.stringify({
name: "test-worker",
main: "src/index.ts",
compatibility_date: "2025-01-01",
upload_source_maps: true,
previews: {},
})
);
test.for([
{ label: "enabled", uploadSourceMaps: true },
{ label: "unset", uploadSourceMaps: undefined },
])(
"should include source maps in deployment modules when upload_source_maps is $label",
async ({ uploadSourceMaps }, { expect }) => {
writeFileSync(
"wrangler.json",
JSON.stringify({
name: "test-worker",
main: "src/index.ts",
compatibility_date: "2025-01-01",
upload_source_maps: uploadSourceMaps,
previews: {},
})
);

let deploymentRequestBody:
| (Record<string, unknown> & {
modules?: PreviewDeploymentModulePart[];
})
| undefined;
let deploymentRequestBody:
| (Record<string, unknown> & {
modules?: PreviewDeploymentModulePart[];
})
| undefined;

msw.use(
http.get(
`*/accounts/:accountId/workers/workers/:workerId/previews/:previewId`,
() =>
HttpResponse.json(
{
success: false,
result: null,
errors: [{ code: 10025, message: "Preview not found" }],
},
{ status: 404 }
)
),
http.post(
`*/accounts/:accountId/workers/workers/:workerId/previews`,
() =>
HttpResponse.json(
{
success: true,
result: {
id: "preview-id-sourcemaps",
name: "test-preview",
slug: "test-preview",
urls: ["https://test-preview.test-worker.cloudflare.app"],
worker_name: "test-worker",
created_on: new Date().toISOString(),
msw.use(
http.get(
`*/accounts/:accountId/workers/workers/:workerId/previews/:previewId`,
() =>
HttpResponse.json(
{
success: false,
result: null,
errors: [{ code: 10025, message: "Preview not found" }],
},
},
{ status: 201 }
)
),
http.post(
`*/accounts/:accountId/workers/workers/:workerId/previews/:previewId/deployments`,
async ({ request }) => {
deploymentRequestBody = (await readPreviewDeploymentRequest(
request
)) as typeof deploymentRequestBody;
return HttpResponse.json(
{
success: true,
result: {
id: "deployment-id-sourcemaps",
preview_id: "preview-id-sourcemaps",
preview_name: "test-preview",
urls: ["https://sourcemaps123.test-worker.cloudflare.app"],
compatibility_date: "2025-01-01",
env: {},
created_on: new Date().toISOString(),
{ status: 404 }
)
),
http.post(
`*/accounts/:accountId/workers/workers/:workerId/previews`,
() =>
HttpResponse.json(
{
success: true,
result: {
id: "preview-id-sourcemaps",
name: "test-preview",
slug: "test-preview",
urls: ["https://test-preview.test-worker.cloudflare.app"],
worker_name: "test-worker",
created_on: new Date().toISOString(),
},
},
},
{ status: 201 }
);
}
)
);
{ status: 201 }
)
),
http.post(
`*/accounts/:accountId/workers/workers/:workerId/previews/:previewId/deployments`,
async ({ request }) => {
deploymentRequestBody = (await readPreviewDeploymentRequest(
request
)) as typeof deploymentRequestBody;
return HttpResponse.json(
{
success: true,
result: {
id: "deployment-id-sourcemaps",
preview_id: "preview-id-sourcemaps",
preview_name: "test-preview",
urls: ["https://sourcemaps123.test-worker.cloudflare.app"],
compatibility_date: "2025-01-01",
env: {},
created_on: new Date().toISOString(),
},
},
{ status: 201 }
);
}
)
);

await runWrangler("preview --name test-preview");
await runWrangler("preview --name test-preview");

const sourceMap = deploymentRequestBody?.modules?.find((module) =>
module.name.endsWith(".map")
);
const sourceMap = deploymentRequestBody?.modules?.find((module) =>
module.name.endsWith(".map")
);

expect(sourceMap?.content_type).toBe("application/source-map");
expect(JSON.parse(decodeModuleContent(sourceMap))).toEqual(
expect.objectContaining({ version: 3 })
);
});
expect(sourceMap?.content_type).toBe("application/source-map");
expect(JSON.parse(decodeModuleContent(sourceMap))).toEqual(
expect.objectContaining({ version: 3 })
);
}
);

test("should use previews.define for worker bundling", async ({
expect,
Expand Down
2 changes: 1 addition & 1 deletion packages/wrangler/src/deployment-bundle/build-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ async function writeBundle({
}

// Source maps. `buildResult.sourceMaps` is already gated by
// `uploadSourceMaps` inside `buildWorker` (undefined when off) and
// `uploadSourceMaps` inside `buildWorker` (undefined when explicitly off) and
// contains the main module's map plus any module maps, each named
// `<module>.map` (e.g. `index.js.map`).
for (const sourceMap of buildResult.sourceMaps ?? []) {
Expand Down
3 changes: 2 additions & 1 deletion packages/wrangler/src/deployment-bundle/deploy-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ export const sharedDeployVersionsArgs = {
type: "boolean",
},
"upload-source-maps": {
describe: "Include source maps when uploading this Worker",
describe:
"Include source maps when uploading this Worker (defaults to true)",
type: "boolean",
},
"node-compat": {
Expand Down
Loading
Loading