wrangler 2.1.7 → 2.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrangler",
3
- "version": "2.1.7",
3
+ "version": "2.1.9",
4
4
  "description": "Command-line interface for all things Cloudflare Workers",
5
5
  "keywords": [
6
6
  "wrangler",
@@ -112,6 +112,7 @@
112
112
  "@databases/sql": "^3.2.0",
113
113
  "@iarna/toml": "^3.0.0",
114
114
  "@microsoft/api-extractor": "^7.28.3",
115
+ "@miniflare/tre": "3.0.0-next.2",
115
116
  "@types/better-sqlite3": "^7.6.0",
116
117
  "@types/busboy": "^1.5.0",
117
118
  "@types/command-exists": "^1.2.0",
@@ -138,7 +139,6 @@
138
139
  "dotenv": "^16.0.0",
139
140
  "execa": "^6.1.0",
140
141
  "express": "^4.18.1",
141
- "faye-websocket": "^0.11.4",
142
142
  "finalhandler": "^1.2.0",
143
143
  "find-up": "^6.3.0",
144
144
  "get-port": "^6.1.2",
@@ -157,7 +157,7 @@
157
157
  "jest-websocket-mock": "^2.3.0",
158
158
  "mime": "^3.0.0",
159
159
  "msw": "^0.47.1",
160
- "npx-import": "^1.0.2",
160
+ "npx-import": "^1.1.3",
161
161
  "open": "^8.4.0",
162
162
  "p-queue": "^7.2.0",
163
163
  "pretty-bytes": "^6.0.0",
@@ -1022,6 +1022,7 @@ describe("wrangler dev", () => {
1022
1022
  --jsx-fragment The function that is called for each JSX fragment [string]
1023
1023
  --tsconfig Path to a custom tsconfig.json file [string]
1024
1024
  -l, --local Run on my machine [boolean] [default: false]
1025
+ --experimental-local Run on my machine using the Cloudflare Workers runtime [boolean] [default: false]
1025
1026
  --minify Minify the script [boolean]
1026
1027
  --node-compat Enable node.js compatibility [boolean]
1027
1028
  --persist Enable persistence for local mode, using default path: .wrangler/state [boolean]
@@ -1325,18 +1326,18 @@ describe("wrangler dev", () => {
1325
1326
  fs.writeFileSync("index.js", `export default {};`);
1326
1327
  await runWrangler("dev index.js");
1327
1328
  expect(std).toMatchInlineSnapshot(`
1328
- Object {
1329
- "debug": "",
1330
- "err": "",
1331
- "out": "Using vars defined in .dev.vars
1332
- Your worker has access to the following bindings:
1333
- - Vars:
1334
- - variable: \\"123\\"
1335
- - overriden: \\"(hidden)\\"
1336
- - SECRET: \\"(hidden)\\"",
1337
- "warn": "",
1338
- }
1339
- `);
1329
+ Object {
1330
+ "debug": "",
1331
+ "err": "",
1332
+ "out": "Using vars defined in .dev.vars
1333
+ Your worker has access to the following bindings:
1334
+ - Vars:
1335
+ - variable: 123
1336
+ - overriden: \\"(hidden)\\"
1337
+ - SECRET: \\"(hidden)\\"",
1338
+ "warn": "",
1339
+ }
1340
+ `);
1340
1341
  });
1341
1342
  });
1342
1343
  });
@@ -2608,6 +2608,105 @@ describe("init", () => {
2608
2608
  ).rejects.toThrowError();
2609
2609
  });
2610
2610
 
2611
+ it("should not inlcude migrations in config file when none are necessary", async () => {
2612
+ const mockDate = "1988-08-07";
2613
+ jest
2614
+ .spyOn(Date.prototype, "toISOString")
2615
+ .mockImplementation(() => `${mockDate}T00:00:00.000Z`);
2616
+ const mockData = {
2617
+ id: "memory-crystal",
2618
+ default_environment: {
2619
+ environment: "test",
2620
+ created_on: "1988-08-07",
2621
+ modified_on: "1988-08-07",
2622
+ script: {
2623
+ id: "memory-crystal",
2624
+ tag: "test-tag",
2625
+ etag: "some-etag",
2626
+ handlers: [],
2627
+ modified_on: "1988-08-07",
2628
+ created_on: "1988-08-07",
2629
+ usage_model: "bundled",
2630
+ compatibility_date: "1988-08-07",
2631
+ },
2632
+ },
2633
+ environments: [],
2634
+ };
2635
+
2636
+ setMockResponse(
2637
+ `/accounts/:accountId/workers/services/:scriptName`,
2638
+ "GET",
2639
+ () => mockData
2640
+ );
2641
+ setMockResponse(
2642
+ `/accounts/:accountId/workers/services/:scriptName/environments/:environment/bindings`,
2643
+ "GET",
2644
+ () => []
2645
+ );
2646
+ setMockResponse(
2647
+ `/accounts/:accountId/workers/services/:scriptName/environments/:environment/routes`,
2648
+ "GET",
2649
+ () => []
2650
+ );
2651
+ setMockResponse(
2652
+ `/accounts/:accountId/workers/services/:scriptName/environments/:environment`,
2653
+ "GET",
2654
+ () => mockServiceMetadata.default_environment
2655
+ );
2656
+ setMockResponse(
2657
+ `/accounts/:accountId/workers/scripts/:scriptName/schedules`,
2658
+ "GET",
2659
+ () => {
2660
+ return {
2661
+ schedules: [],
2662
+ };
2663
+ }
2664
+ );
2665
+
2666
+ setMockFetchDashScript({
2667
+ accountId: "LCARS",
2668
+ fromDashScriptName: "isolinear-optical-chip",
2669
+ environment: mockServiceMetadata.default_environment.environment,
2670
+ mockResponse: mockDashboardScript,
2671
+ });
2672
+
2673
+ mockConfirm(
2674
+ {
2675
+ text: "Would you like to use git to manage this Worker?",
2676
+ result: false,
2677
+ },
2678
+ {
2679
+ text: "Would you like to use TypeScript?",
2680
+ result: true,
2681
+ },
2682
+ {
2683
+ text: "No package.json found. Would you like to create one?",
2684
+ result: true,
2685
+ },
2686
+ {
2687
+ text: "Would you like to install the type definitions for Workers into your package.json?",
2688
+ result: true,
2689
+ }
2690
+ );
2691
+
2692
+ await runWrangler("init --from-dash isolinear-optical-chip");
2693
+
2694
+ checkFiles({
2695
+ items: {
2696
+ "isolinear-optical-chip/wrangler.toml": wranglerToml({
2697
+ compatibility_date: "1988-08-07",
2698
+ env: {},
2699
+ main: "src/index.ts",
2700
+ triggers: {
2701
+ crons: [],
2702
+ },
2703
+ usage_model: "bundled",
2704
+ name: "isolinear-optical-chip",
2705
+ }),
2706
+ },
2707
+ });
2708
+ });
2709
+
2611
2710
  it("should not continue if no worker name is provided", async () => {
2612
2711
  await expect(
2613
2712
  runWrangler("init --from-dash")
@@ -512,7 +512,7 @@ describe("publish", () => {
512
512
  "Total Upload: xx KiB / gzip: xx KiB
513
513
  Your worker has access to the following bindings:
514
514
  - Vars:
515
- - xyz: \\"123\\"
515
+ - xyz: 123
516
516
  Uploaded test-name (TIMINGS)
517
517
  Published test-name (TIMINGS)
518
518
  https://test-name.test-sub-domain.workers.dev"
@@ -4527,7 +4527,7 @@ addEventListener('fetch', event => {});`
4527
4527
  - some unsafe thing: UNSAFE_BINDING_ONE
4528
4528
  - another unsafe thing: UNSAFE_BINDING_TWO
4529
4529
  - Vars:
4530
- - ENV_VAR_ONE: \\"123\\"
4530
+ - ENV_VAR_ONE: 123
4531
4531
  - ENV_VAR_TWO: \\"Hello, I'm an environment variable\\"
4532
4532
  - Wasm Modules:
4533
4533
  - WASM_MODULE_ONE: some_wasm.wasm
@@ -5275,8 +5275,11 @@ addEventListener('fetch', event => {});`
5275
5275
  Your worker has access to the following bindings:
5276
5276
  - Vars:
5277
5277
  - text: \\"plain ol' string\\"
5278
- - count: \\"1\\"
5279
- - complex: \\"[object Object]\\"
5278
+ - count: 1
5279
+ - complex: {
5280
+ \\"enabled\\": true,
5281
+ \\"id\\": 123
5282
+ }
5280
5283
  Uploaded test-name (TIMINGS)
5281
5284
  Published test-name (TIMINGS)
5282
5285
  https://test-name.test-sub-domain.workers.dev"
package/src/api/dev.ts CHANGED
@@ -54,6 +54,7 @@ interface DevOptions {
54
54
  _?: (string | number)[]; //yargs wants this
55
55
  $0?: string; //yargs wants this
56
56
  testScheduled?: boolean;
57
+ experimentalLocal?: boolean;
57
58
  }
58
59
 
59
60
  interface DevApiOptions {
package/src/bundle.ts CHANGED
@@ -79,6 +79,7 @@ export async function bundleWorker(
79
79
  targetConsumer: "dev" | "publish";
80
80
  local: boolean;
81
81
  testScheduled?: boolean | undefined;
82
+ experimentalLocalStubCache: boolean | undefined;
82
83
  }
83
84
  ): Promise<BundleResult> {
84
85
  const {
@@ -99,6 +100,7 @@ export async function bundleWorker(
99
100
  firstPartyWorkerDevFacade,
100
101
  targetConsumer,
101
102
  testScheduled,
103
+ experimentalLocalStubCache,
102
104
  } = options;
103
105
 
104
106
  // We create a temporary directory for any oneoff files we
@@ -233,12 +235,20 @@ export async function bundleWorker(
233
235
 
234
236
  // At this point, inputEntry points to the entry point we want to build.
235
237
 
238
+ const inject: string[] = [];
239
+ if (checkFetch) inject.push(checkedFetchFileToInject);
240
+ if (experimentalLocalStubCache) {
241
+ inject.push(
242
+ path.resolve(getBasePath(), "templates/experimental-local-cache-stubs.js")
243
+ );
244
+ }
245
+
236
246
  const result = await esbuild.build({
237
247
  entryPoints: [inputEntry.file],
238
248
  bundle: true,
239
249
  absWorkingDir: entry.directory,
240
250
  outdir: destination,
241
- inject: checkFetch ? [checkedFetchFileToInject] : [],
251
+ inject,
242
252
  external: ["__STATIC_CONTENT_MANIFEST"],
243
253
  format: entry.format === "modules" ? "esm" : "iife",
244
254
  target: "es2020",
@@ -219,10 +219,20 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
219
219
  if (vars !== undefined && Object.keys(vars).length > 0) {
220
220
  output.push({
221
221
  type: "Vars",
222
- entries: Object.entries(vars).map(([key, value]) => ({
223
- key,
224
- value: `"${truncate(`${value}`)}"`,
225
- })),
222
+ entries: Object.entries(vars).map(([key, value]) => {
223
+ let parsedValue;
224
+ if (typeof value === "string") {
225
+ parsedValue = `"${truncate(value)}"`;
226
+ } else if (typeof value === "object") {
227
+ parsedValue = JSON.stringify(value, null, 1);
228
+ } else {
229
+ parsedValue = `${truncate(`${value}`)}`;
230
+ }
231
+ return {
232
+ key,
233
+ value: parsedValue,
234
+ };
235
+ }),
226
236
  });
227
237
  }
228
238
 
package/src/dev/dev.tsx CHANGED
@@ -153,6 +153,7 @@ export type DevProps = {
153
153
  firstPartyWorker: boolean | undefined;
154
154
  sendMetrics: boolean | undefined;
155
155
  testScheduled: boolean | undefined;
156
+ experimentalLocal: boolean | undefined;
156
157
  };
157
158
 
158
159
  export function DevImplementation(props: DevProps): JSX.Element {
@@ -213,6 +214,7 @@ function InteractiveDevSession(props: DevProps) {
213
214
 
214
215
  type DevSessionProps = DevProps & {
215
216
  local: boolean;
217
+ experimentalLocal?: boolean;
216
218
  };
217
219
 
218
220
  function DevSession(props: DevSessionProps) {
@@ -274,6 +276,7 @@ function DevSession(props: DevSessionProps) {
274
276
  // Enable the bundling to know whether we are using dev or publish
275
277
  targetConsumer: "dev",
276
278
  testScheduled: props.testScheduled ?? false,
279
+ experimentalLocalStubCache: props.local && props.experimentalLocal,
277
280
  });
278
281
 
279
282
  return props.local ? (
@@ -300,6 +303,7 @@ function DevSession(props: DevSessionProps) {
300
303
  inspect={props.inspect}
301
304
  onReady={props.onReady}
302
305
  enablePagesAssetsServiceBinding={props.enablePagesAssetsServiceBinding}
306
+ experimentalLocal={props.experimentalLocal}
303
307
  />
304
308
  ) : (
305
309
  <Remote
package/src/dev/local.tsx CHANGED
@@ -1,13 +1,18 @@
1
+ import assert from "node:assert";
1
2
  import { fork } from "node:child_process";
2
3
  import { realpathSync } from "node:fs";
3
- import { writeFile } from "node:fs/promises";
4
+ import { readFile, writeFile } from "node:fs/promises";
4
5
  import path from "node:path";
6
+ import { npxImport } from "npx-import";
5
7
  import { useState, useEffect, useRef } from "react";
6
8
  import onExit from "signal-exit";
7
9
  import { registerWorker } from "../dev-registry";
8
10
  import useInspector from "../inspect";
9
11
  import { logger } from "../logger";
10
- import { DEFAULT_MODULE_RULES } from "../module-collection";
12
+ import {
13
+ DEFAULT_MODULE_RULES,
14
+ ModuleTypeToRuleType,
15
+ } from "../module-collection";
11
16
  import { getBasePath } from "../paths";
12
17
  import { waitForPortToBeAvailable } from "../proxy";
13
18
  import type { Config } from "../config";
@@ -27,8 +32,13 @@ import type {
27
32
  CfD1Database,
28
33
  } from "../worker";
29
34
  import type { EsbuildBundle } from "./use-esbuild";
35
+ import type {
36
+ Miniflare as Miniflare3Type,
37
+ MiniflareOptions as Miniflare3Options,
38
+ } from "@miniflare/tre";
30
39
  import type { MiniflareOptions } from "miniflare";
31
40
  import type { ChildProcess } from "node:child_process";
41
+
32
42
  export interface LocalProps {
33
43
  name: string | undefined;
34
44
  bundle: EsbuildBundle | undefined;
@@ -53,6 +63,7 @@ export interface LocalProps {
53
63
  logPrefix?: string;
54
64
  enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
55
65
  testScheduled?: boolean;
66
+ experimentalLocal?: boolean;
56
67
  }
57
68
 
58
69
  export function Local(props: LocalProps) {
@@ -88,9 +99,11 @@ function useLocalWorker({
88
99
  onReady,
89
100
  logPrefix,
90
101
  enablePagesAssetsServiceBinding,
102
+ experimentalLocal,
91
103
  }: LocalProps) {
92
104
  // TODO: pass vars via command line
93
105
  const local = useRef<ChildProcess>();
106
+ const experimentalLocalRef = useRef<Miniflare3Type>();
94
107
  const removeSignalExitListener = useRef<() => void>();
95
108
  const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
96
109
 
@@ -187,6 +200,20 @@ function useLocalWorker({
187
200
  enablePagesAssetsServiceBinding,
188
201
  });
189
202
 
203
+ if (experimentalLocal) {
204
+ // TODO: refactor setupMiniflareOptions so we don't need to parse here
205
+ const mf2Options: MiniflareOptions = JSON.parse(forkOptions[0]);
206
+ const mf = await setupExperimentalLocal(mf2Options, format, bundle);
207
+ await mf.ready;
208
+ experimentalLocalRef.current = mf;
209
+ removeSignalExitListener.current = onExit(() => {
210
+ logger.log("⎔ Shutting down experimental local server.");
211
+ mf.dispose();
212
+ experimentalLocalRef.current = undefined;
213
+ });
214
+ return;
215
+ }
216
+
190
217
  const nodeOptions = setupNodeOptions({ inspect, ip, inspectorPort });
191
218
  logger.log("⎔ Starting a local server...");
192
219
 
@@ -279,9 +306,16 @@ function useLocalWorker({
279
306
  logger.log("⎔ Shutting down local server.");
280
307
  local.current?.kill();
281
308
  local.current = undefined;
282
- removeSignalExitListener.current && removeSignalExitListener.current();
283
- removeSignalExitListener.current = undefined;
284
309
  }
310
+ if (experimentalLocalRef.current) {
311
+ logger.log("⎔ Shutting down experimental local server.");
312
+ // Initialisation errors are also thrown asynchronously by dispose().
313
+ // The catch() above should've caught them though.
314
+ experimentalLocalRef.current?.dispose().catch(() => {});
315
+ experimentalLocalRef.current = undefined;
316
+ }
317
+ removeSignalExitListener.current?.();
318
+ removeSignalExitListener.current = undefined;
285
319
  };
286
320
  }, [
287
321
  bundle,
@@ -314,6 +348,7 @@ function useLocalWorker({
314
348
  logPrefix,
315
349
  onReady,
316
350
  enablePagesAssetsServiceBinding,
351
+ experimentalLocal,
317
352
  ]);
318
353
  return { inspectorUrl };
319
354
  }
@@ -458,7 +493,10 @@ export function setupMiniflareOptions({
458
493
  logPrefix,
459
494
  workerDefinitions,
460
495
  enablePagesAssetsServiceBinding,
461
- }: SetupMiniflareOptionsProps): MiniflareOptions {
496
+ }: SetupMiniflareOptionsProps): {
497
+ miniflareCLIPath: string;
498
+ forkOptions: string[];
499
+ } {
462
500
  // It's now getting _really_ messy now with Pages ASSETS binding outside and the external Durable Objects inside.
463
501
  const options = {
464
502
  name: workerName,
@@ -586,3 +624,63 @@ export function setupNodeOptions({
586
624
  }
587
625
  return nodeOptions;
588
626
  }
627
+
628
+ // Caching of the `npx-import`ed `@miniflare/tre` package
629
+ // eslint-disable-next-line @typescript-eslint/consistent-type-imports
630
+ let Miniflare: typeof import("@miniflare/tre").Miniflare;
631
+
632
+ function arrayToObject(values: string[] = []): Record<string, string> {
633
+ return Object.fromEntries(values.map((value) => [value, value]));
634
+ }
635
+
636
+ export async function setupExperimentalLocal(
637
+ mf2Options: MiniflareOptions,
638
+ format: CfScriptFormat,
639
+ bundle: EsbuildBundle
640
+ ): Promise<Miniflare3Type> {
641
+ const options: Miniflare3Options = {
642
+ ...mf2Options,
643
+ // Miniflare 3 distinguishes between binding name and namespace/bucket IDs.
644
+ // For now, just use the same value as we did in Miniflare 2.
645
+ // TODO: use defined KV preview ID if any
646
+ kvNamespaces: arrayToObject(mf2Options.kvNamespaces),
647
+ r2Buckets: arrayToObject(mf2Options.r2Buckets),
648
+ };
649
+
650
+ if (format === "modules") {
651
+ // Manually specify all modules from the bundle. If we didn't do this,
652
+ // Miniflare 3 would try collect them automatically again itself.
653
+
654
+ // Resolve entrypoint relative to the temporary directory, ensuring
655
+ // path doesn't start with `..`, which causes issues in `workerd`.
656
+ // Also ensures other modules with relative names can be resolved.
657
+ const root = path.dirname(bundle.path);
658
+
659
+ assert.strictEqual(bundle.type, "esm");
660
+ options.modules = [
661
+ // Entrypoint
662
+ {
663
+ type: "ESModule",
664
+ path: path.relative(root, bundle.path),
665
+ contents: await readFile(bundle.path, "utf-8"),
666
+ },
667
+ // Misc (WebAssembly, etc, ...)
668
+ ...bundle.modules.map((module) => ({
669
+ type: ModuleTypeToRuleType[module.type ?? "esm"],
670
+ path: module.name,
671
+ contents: module.content,
672
+ })),
673
+ ];
674
+ }
675
+
676
+ logger.log("⎔ Starting an experimental local server...");
677
+
678
+ if (Miniflare === undefined) {
679
+ ({ Miniflare } = await npxImport<
680
+ // eslint-disable-next-line @typescript-eslint/consistent-type-imports
681
+ typeof import("@miniflare/tre")
682
+ >("@miniflare/tre@next"));
683
+ }
684
+
685
+ return new Miniflare(options);
686
+ }
@@ -17,6 +17,7 @@ import { logger } from "../logger";
17
17
  import { waitForPortToBeAvailable } from "../proxy";
18
18
  import {
19
19
  setupBindings,
20
+ setupExperimentalLocal,
20
21
  setupMiniflareOptions,
21
22
  setupNodeOptions,
22
23
  } from "./local";
@@ -28,6 +29,8 @@ import type { Entry } from "../entry";
28
29
  import type { DevProps, DirectorySyncResult } from "./dev";
29
30
  import type { LocalProps } from "./local";
30
31
  import type { EsbuildBundle } from "./use-esbuild";
32
+ import type { Miniflare as Miniflare3Type } from "@miniflare/tre";
33
+ import type { MiniflareOptions } from "miniflare";
31
34
 
32
35
  import type { ChildProcess } from "node:child_process";
33
36
 
@@ -91,6 +94,7 @@ export async function startDevServer(
91
94
  services: props.bindings.services,
92
95
  firstPartyWorkerDevFacade: props.firstPartyWorker,
93
96
  testScheduled: props.testScheduled,
97
+ experimentalLocalStubCache: props.experimentalLocal,
94
98
  });
95
99
 
96
100
  //run local now
@@ -117,6 +121,7 @@ export async function startDevServer(
117
121
  enablePagesAssetsServiceBinding: props.enablePagesAssetsServiceBinding,
118
122
  usageModel: props.usageModel,
119
123
  workerDefinitions,
124
+ experimentalLocal: props.experimentalLocal,
120
125
  });
121
126
 
122
127
  return {
@@ -159,6 +164,7 @@ async function runEsbuild({
159
164
  services,
160
165
  firstPartyWorkerDevFacade,
161
166
  testScheduled,
167
+ experimentalLocalStubCache,
162
168
  }: {
163
169
  entry: Entry;
164
170
  destination: string | undefined;
@@ -176,6 +182,7 @@ async function runEsbuild({
176
182
  workerDefinitions: WorkerRegistry;
177
183
  firstPartyWorkerDevFacade: boolean | undefined;
178
184
  testScheduled?: boolean;
185
+ experimentalLocalStubCache: boolean | undefined;
179
186
  }): Promise<EsbuildBundle | undefined> {
180
187
  if (!destination) return;
181
188
 
@@ -213,6 +220,7 @@ async function runEsbuild({
213
220
  targetConsumer: "dev", // We are starting a dev server
214
221
  local: false,
215
222
  testScheduled,
223
+ experimentalLocalStubCache,
216
224
  });
217
225
 
218
226
  return {
@@ -248,8 +256,10 @@ export async function startLocalServer({
248
256
  onReady,
249
257
  logPrefix,
250
258
  enablePagesAssetsServiceBinding,
259
+ experimentalLocal,
251
260
  }: LocalProps) {
252
261
  let local: ChildProcess | undefined;
262
+ let experimentalLocalRef: Miniflare3Type | undefined;
253
263
  let removeSignalExitListener: (() => void) | undefined;
254
264
  let inspectorUrl: string | undefined;
255
265
  const setInspectorUrl = (url: string) => {
@@ -337,6 +347,21 @@ export async function startLocalServer({
337
347
  enablePagesAssetsServiceBinding,
338
348
  });
339
349
 
350
+ if (experimentalLocal) {
351
+ // TODO: refactor setupMiniflareOptions so we don't need to parse here
352
+ const mf2Options: MiniflareOptions = JSON.parse(forkOptions[0]);
353
+ const mf = await setupExperimentalLocal(mf2Options, format, bundle);
354
+ const runtimeURL = await mf.ready;
355
+ experimentalLocalRef = mf;
356
+ removeSignalExitListener = onExit((_code, _signal) => {
357
+ logger.log("⎔ Shutting down experimental local server.");
358
+ mf.dispose();
359
+ experimentalLocalRef = undefined;
360
+ });
361
+ onReady?.(runtimeURL.hostname, parseInt(runtimeURL.port ?? 8787));
362
+ return;
363
+ }
364
+
340
365
  const nodeOptions = setupNodeOptions({ inspect, ip, inspectorPort });
341
366
  logger.log("⎔ Starting a local server...");
342
367
 
@@ -431,9 +456,16 @@ export async function startLocalServer({
431
456
  logger.log("⎔ Shutting down local server.");
432
457
  local.kill();
433
458
  local = undefined;
434
- removeSignalExitListener && removeSignalExitListener();
435
- removeSignalExitListener = undefined;
436
459
  }
460
+ if (experimentalLocalRef) {
461
+ logger.log("⎔ Shutting down experimental local server.");
462
+ // Initialisation errors are also thrown asynchronously by dispose().
463
+ // The catch() above should've caught them though.
464
+ experimentalLocalRef?.dispose().catch(() => {});
465
+ experimentalLocalRef = undefined;
466
+ }
467
+ removeSignalExitListener?.();
468
+ removeSignalExitListener = undefined;
437
469
  },
438
470
  };
439
471
  }
@@ -40,6 +40,7 @@ export function useEsbuild({
40
40
  local,
41
41
  targetConsumer,
42
42
  testScheduled,
43
+ experimentalLocalStubCache,
43
44
  }: {
44
45
  entry: Entry;
45
46
  destination: string | undefined;
@@ -61,6 +62,7 @@ export function useEsbuild({
61
62
  local: boolean;
62
63
  targetConsumer: "dev" | "publish";
63
64
  testScheduled: boolean;
65
+ experimentalLocalStubCache: boolean | undefined;
64
66
  }): EsbuildBundle | undefined {
65
67
  const [bundle, setBundle] = useState<EsbuildBundle>();
66
68
  const { exit } = useApp();
@@ -128,6 +130,7 @@ export function useEsbuild({
128
130
  local,
129
131
  targetConsumer,
130
132
  testScheduled,
133
+ experimentalLocalStubCache,
131
134
  });
132
135
 
133
136
  // Capture the `stop()` method to use as the `useEffect()` destructor.
@@ -188,6 +191,7 @@ export function useEsbuild({
188
191
  local,
189
192
  targetConsumer,
190
193
  testScheduled,
194
+ experimentalLocalStubCache,
191
195
  ]);
192
196
  return bundle;
193
197
  }