Skip to content

Commit 3d55f96

Browse files
authored
fix: store temporary files in .wrangler (#4127)
Previously, Wrangler used the OS's default temporary directory. On Windows, this is usually on the `C:` drive. If your source code was on a different drive, `esbuild` would generate invalid source maps (source paths relative to the cwd were used verbatim in the output, rather than being relative to sources root and the source map location), breaking breakpoint debugging in VSCode. This change ensures intermediate files are always written to the same drive as sources. It also ensures unused bundle outputs are cleaned up between `wrangler pages dev` reloads, and the same file path is used for writing functions routes. Closes #4052
1 parent be0c628 commit 3d55f96

File tree

17 files changed

+222
-96
lines changed

17 files changed

+222
-96
lines changed

.changeset/honest-tips-tie.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
fix: store temporary files in `.wrangler`
6+
7+
As Wrangler builds your code, it writes intermediate files to a temporary
8+
directory that gets cleaned up on exit. Previously, Wrangler used the OS's
9+
default temporary directory. On Windows, this is usually on the `C:` drive.
10+
If your source code was on a different drive, our bundling tool would generate
11+
invalid source maps, breaking breakpoint debugging. This change ensures
12+
intermediate files are always written to the same drive as sources. It also
13+
ensures unused build outputs are cleaned up when running `wrangler pages dev`.
14+
15+
This change also means you no longer need to set `cwd` and
16+
`resolveSourceMapLocations` in `.vscode/launch.json` when creating an `attach`
17+
configuration for breakpoint debugging. Your `.vscode/launch.json` should now
18+
look something like...
19+
20+
```jsonc
21+
{
22+
"configurations": [
23+
{
24+
"name": "Wrangler",
25+
"type": "node",
26+
"request": "attach",
27+
"port": 9229,
28+
// These can be omitted, but doing so causes silent errors in the runtime
29+
"attachExistingChildren": false,
30+
"autoAttachChildProcesses": false
31+
}
32+
]
33+
}
34+
```

packages/wrangler/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"check:lint": "eslint .",
5757
"check:type": "tsc",
5858
"clean": "rimraf wrangler-dist miniflare-dist emitted-types",
59-
"dev": "pnpm run clean && concurrently -c black,blue --kill-others-on-fail false 'pnpm run bundle --watch' 'pnpm run check:type --watch --preserveWatchOutput'",
59+
"dev": "pnpm run clean && concurrently -c black,blue --kill-others-on-fail false \"pnpm run bundle --watch\" \"pnpm run check:type --watch --preserveWatchOutput\"",
6060
"emit-types": "tsc -p tsconfig.emit.json && node -r esbuild-register scripts/emit-types.ts",
6161
"prepublishOnly": "SOURCEMAPS=false npm run build",
6262
"start": "pnpm run bundle && cross-env NODE_OPTIONS=--enable-source-maps ./bin/wrangler.js",
@@ -194,7 +194,6 @@
194194
"strip-ansi": "^7.0.1",
195195
"supports-color": "^9.2.2",
196196
"timeago.js": "^4.0.2",
197-
"tmp-promise": "^3.0.3",
198197
"ts-dedent": "^2.2.0",
199198
"undici": "5.20.0",
200199
"update-check": "^1.5.4",

packages/wrangler/src/api/pages/deploy.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { existsSync, lstatSync, readFileSync } from "node:fs";
2-
import { tmpdir } from "node:os";
32
import { join, resolve as resolvePath } from "node:path";
43
import { cwd } from "node:process";
54
import { File, FormData } from "undici";
@@ -20,6 +19,7 @@ import {
2019
} from "../../pages/functions/buildWorker";
2120
import { validateRoutes } from "../../pages/functions/routes-validation";
2221
import { upload } from "../../pages/upload";
22+
import { getPagesTmpDir } from "../../pages/utils";
2323
import { validate } from "../../pages/validate";
2424
import { createUploadWorkerBundleContents } from "./create-worker-bundle-contents";
2525
import type { BundleResult } from "../../deployment-bundle/bundle";
@@ -154,15 +154,15 @@ export async function deploy({
154154
const functionsDirectory =
155155
customFunctionsDirectory || join(cwd(), "functions");
156156
const routesOutputPath = !existsSync(join(directory, "_routes.json"))
157-
? join(tmpdir(), `_routes-${Math.random()}.json`)
157+
? join(getPagesTmpDir(), `_routes-${Math.random()}.json`)
158158
: undefined;
159159

160160
// Routing configuration displayed in the Functions tab of a deployment in Dash
161161
let filepathRoutingConfig: string | undefined;
162162

163163
if (!_workerJS && existsSync(functionsDirectory)) {
164164
const outputConfigPath = join(
165-
tmpdir(),
165+
getPagesTmpDir(),
166166
`functions-filepath-routing-config-${Math.random()}.json`
167167
);
168168

@@ -257,7 +257,10 @@ export async function deploy({
257257
});
258258
} else if (_workerJS) {
259259
if (bundle) {
260-
const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`);
260+
const outfile = join(
261+
getPagesTmpDir(),
262+
`./bundledWorker-${Math.random()}.mjs`
263+
);
261264
workerBundle = await buildRawWorker({
262265
workerScriptPath: _workerPath,
263266
outfile,

packages/wrangler/src/deploy/deploy.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
33
import path from "node:path";
44
import { URLSearchParams } from "node:url";
55
import chalk from "chalk";
6-
import tmp from "tmp-promise";
76
import { fetchListResult, fetchResult } from "../cfetch";
87
import { printBindings } from "../config";
98
import { bundleWorker } from "../deployment-bundle/bundle";
@@ -27,6 +26,7 @@ import { getMigrationsToUpload } from "../durable";
2726
import { logger } from "../logger";
2827
import { getMetricsUsageHeaders } from "../metrics";
2928
import { ParseError } from "../parse";
29+
import { getWranglerTmpDir } from "../paths";
3030
import { getQueue, putConsumer } from "../queues/client";
3131
import { getWorkersDevSubdomain } from "../routes";
3232
import { syncAssets } from "../sites";
@@ -72,6 +72,7 @@ type Props = {
7272
keepVars: boolean | undefined;
7373
logpush: boolean | undefined;
7474
oldAssetTtl: number | undefined;
75+
projectRoot: string | undefined;
7576
};
7677

7778
type RouteObject = ZoneIdRoute | ZoneNameRoute | CustomDomainRoute;
@@ -407,7 +408,8 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
407408
);
408409
}
409410

410-
const destination = props.outDir ?? (await tmp.dir({ unsafeCleanup: true }));
411+
const destination =
412+
props.outDir ?? getWranglerTmpDir(props.projectRoot, "deploy");
411413
const envName = props.env ?? "production";
412414

413415
const start = Date.now();
@@ -507,6 +509,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
507509
// This could potentially cause issues as we no longer have identical behaviour between dev and deploy?
508510
targetConsumer: "deploy",
509511
local: false,
512+
projectRoot: props.projectRoot,
510513
}
511514
);
512515

@@ -693,7 +696,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
693696
if (typeof destination !== "string") {
694697
// this means we're using a temp dir,
695698
// so let's clean up before we proceed
696-
await destination.cleanup();
699+
destination.remove();
697700
}
698701
}
699702

packages/wrangler/src/deploy/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ export async function deployHandler(
239239

240240
const configPath =
241241
args.config || (args.script && findWranglerToml(path.dirname(args.script)));
242+
const projectRoot = configPath && path.dirname(configPath);
242243
const config = readConfig(configPath, args);
243244
const entry = await getEntry(args, config, "deploy");
244245
await metrics.sendMetricsEvent(
@@ -319,5 +320,6 @@ export async function deployHandler(
319320
keepVars: args.keepVars,
320321
logpush: args.logpush,
321322
oldAssetTtl: args.oldAssetTtl,
323+
projectRoot,
322324
});
323325
}

packages/wrangler/src/deployment-bundle/bundle.ts

+13-12
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import * as path from "node:path";
33
import NodeGlobalsPolyfills from "@esbuild-plugins/node-globals-polyfill";
44
import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";
55
import * as esbuild from "esbuild";
6-
import tmp from "tmp-promise";
7-
import { getBasePath } from "../paths";
6+
import { getBasePath, getWranglerTmpDir } from "../paths";
87
import { applyMiddlewareLoaderFacade } from "./apply-middleware";
98
import {
109
isBuildFailure,
@@ -85,6 +84,7 @@ export type BundleOptions = {
8584
isOutfile?: boolean;
8685
forPages?: boolean;
8786
local: boolean;
87+
projectRoot: string | undefined;
8888
};
8989

9090
/**
@@ -122,15 +122,13 @@ export async function bundleWorker(
122122
isOutfile,
123123
forPages,
124124
local,
125+
projectRoot,
125126
}: BundleOptions
126127
): Promise<BundleResult> {
127128
// We create a temporary directory for any one-off files we
128129
// need to create. This is separate from the main build
129130
// directory (`destination`).
130-
const unsafeTmpDir = await tmp.dir({ unsafeCleanup: true });
131-
// Make sure we resolve all files relative to the actual temporary directory,
132-
// without symlinks, otherwise `esbuild` will generate invalid source maps.
133-
const tmpDirPath = fs.realpathSync(unsafeTmpDir.path);
131+
const tmpDir = getWranglerTmpDir(projectRoot, "bundle");
134132

135133
const entryFile = entry.file;
136134

@@ -234,12 +232,9 @@ export async function bundleWorker(
234232
// we need to extract that file to an accessible place before injecting
235233
// it in, hence this code here.
236234

237-
const checkedFetchFileToInject = path.join(tmpDirPath, "checked-fetch.js");
235+
const checkedFetchFileToInject = path.join(tmpDir.path, "checked-fetch.js");
238236

239237
if (checkFetch && !fs.existsSync(checkedFetchFileToInject)) {
240-
fs.mkdirSync(tmpDirPath, {
241-
recursive: true,
242-
});
243238
fs.writeFileSync(
244239
checkedFetchFileToInject,
245240
fs.readFileSync(
@@ -264,7 +259,7 @@ export async function bundleWorker(
264259
) {
265260
const result = await applyMiddlewareLoaderFacade(
266261
entry,
267-
tmpDirPath,
262+
tmpDir.path,
268263
middlewareToLoad,
269264
doBindings
270265
);
@@ -365,10 +360,16 @@ export async function bundleWorker(
365360
}
366361

367362
stop = async function () {
363+
tmpDir.remove();
368364
await ctx.dispose();
369365
};
370366
} else {
371367
result = await esbuild.build(buildOptions);
368+
// Even when we're not watching, we still want some way of cleaning up the
369+
// temporary directory when we don't need it anymore
370+
stop = async function () {
371+
tmpDir.remove();
372+
};
372373
}
373374
} catch (e) {
374375
if (!legacyNodeCompat && isBuildFailure(e))
@@ -405,7 +406,7 @@ export async function bundleWorker(
405406
stop,
406407
sourceMapPath,
407408
sourceMapMetadata: {
408-
tmpDir: tmpDirPath,
409+
tmpDir: tmpDir.path,
409410
entryDirectory: entry.directory,
410411
},
411412
};

packages/wrangler/src/dev.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ export async function startDev(args: StartDevOptions) {
366366
const configPath =
367367
args.config ||
368368
(args.script && findWranglerToml(path.dirname(args.script)));
369+
const projectRoot = configPath && path.dirname(configPath);
369370
let config = readConfig(configPath, args);
370371

371372
if (config.configPath) {
@@ -476,6 +477,7 @@ export async function startDev(args: StartDevOptions) {
476477
firstPartyWorker={configParam.first_party_worker}
477478
sendMetrics={configParam.send_metrics}
478479
testScheduled={args.testScheduled}
480+
projectRoot={projectRoot}
479481
/>
480482
);
481483
}
@@ -520,6 +522,7 @@ export async function startApiDev(args: StartDevOptions) {
520522

521523
const configPath =
522524
args.config || (args.script && findWranglerToml(path.dirname(args.script)));
525+
const projectRoot = configPath && path.dirname(configPath);
523526
const config = readConfig(configPath, args);
524527

525528
const {
@@ -616,6 +619,7 @@ export async function startApiDev(args: StartDevOptions) {
616619
sendMetrics: configParam.send_metrics,
617620
testScheduled: args.testScheduled,
618621
disableDevRegistry: args.disableDevRegistry ?? false,
622+
projectRoot,
619623
});
620624
}
621625

packages/wrangler/src/dev/dev.tsx

+11-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { spawn } from "node:child_process";
2-
import fs from "node:fs";
32
import * as path from "node:path";
43
import * as util from "node:util";
54
import { watch } from "chokidar";
@@ -9,7 +8,6 @@ import { Box, Text, useApp, useInput, useStdin } from "ink";
98
import React, { useEffect, useRef, useState } from "react";
109
import { useErrorHandler, withErrorBoundary } from "react-error-boundary";
1110
import onExit from "signal-exit";
12-
import tmp from "tmp-promise";
1311
import { fetch } from "undici";
1412
import { runCustomBuild } from "../deployment-bundle/run-custom-build";
1513
import {
@@ -20,6 +18,7 @@ import {
2018
} from "../dev-registry";
2119
import { logger } from "../logger";
2220
import openInBrowser from "../open-in-browser";
21+
import { getWranglerTmpDir } from "../paths";
2322
import { openInspector } from "./inspect";
2423
import { Local } from "./local";
2524
import { Remote } from "./remote";
@@ -31,6 +30,7 @@ import type { Entry } from "../deployment-bundle/entry";
3130
import type { CfModule, CfWorkerInit } from "../deployment-bundle/worker";
3231
import type { WorkerRegistry } from "../dev-registry";
3332
import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli/types";
33+
import type { EphemeralDirectory } from "../paths";
3434
import type { AssetPaths } from "../sites";
3535

3636
/**
@@ -164,6 +164,7 @@ export type DevProps = {
164164
firstPartyWorker: boolean | undefined;
165165
sendMetrics: boolean | undefined;
166166
testScheduled: boolean | undefined;
167+
projectRoot: string | undefined;
167168
};
168169

169170
export function DevImplementation(props: DevProps): JSX.Element {
@@ -248,7 +249,7 @@ type DevSessionProps = DevProps & {
248249
function DevSession(props: DevSessionProps) {
249250
useCustomBuild(props.entry, props.build);
250251

251-
const directory = useTmpDir();
252+
const directory = useTmpDir(props.projectRoot);
252253

253254
const workerDefinitions = useDevRegistry(
254255
props.name,
@@ -284,6 +285,7 @@ function DevSession(props: DevSessionProps) {
284285
targetConsumer: "dev",
285286
testScheduled: props.testScheduled ?? false,
286287
experimentalLocal: props.experimentalLocal,
288+
projectRoot: props.projectRoot,
287289
});
288290

289291
// TODO(queues) support remote wrangler dev
@@ -376,37 +378,23 @@ function DevSession(props: DevSessionProps) {
376378
);
377379
}
378380

379-
export interface DirectorySyncResult {
380-
name: string;
381-
removeCallback: () => void;
382-
}
383-
384-
function useTmpDir(): string | undefined {
381+
function useTmpDir(projectRoot: string | undefined): string | undefined {
385382
const [directory, setDirectory] = useState<string>();
386383
const handleError = useErrorHandler();
387384
useEffect(() => {
388-
let dir: DirectorySyncResult | undefined;
385+
let dir: EphemeralDirectory | undefined;
389386
try {
390-
// const tmpdir = path.resolve(".wrangler", "tmp");
391-
// fs.mkdirSync(tmpdir, { recursive: true });
392-
// dir = tmp.dirSync({ unsafeCleanup: true, tmpdir });
393-
dir = tmp.dirSync({ unsafeCleanup: true }) as DirectorySyncResult;
394-
// Make sure we resolve all files relative to the actual temporary
395-
// directory, without symlinks, otherwise `esbuild` will generate invalid
396-
// source maps.
397-
const realpath = fs.realpathSync(dir.name);
398-
setDirectory(realpath);
387+
dir = getWranglerTmpDir(projectRoot, "dev");
388+
setDirectory(dir.path);
399389
return;
400390
} catch (err) {
401391
logger.error(
402392
"Failed to create temporary directory to store built files."
403393
);
404394
handleError(err);
405395
}
406-
return () => {
407-
dir?.removeCallback();
408-
};
409-
}, [handleError]);
396+
return () => dir?.remove();
397+
}, [projectRoot, handleError]);
410398
return directory;
411399
}
412400

0 commit comments

Comments
 (0)