wrangler 2.3.1 → 2.4.0

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.3.1",
3
+ "version": "2.4.0",
4
4
  "description": "Command-line interface for all things Cloudflare Workers",
5
5
  "keywords": [
6
6
  "wrangler",
@@ -119,7 +119,7 @@
119
119
  "@databases/sql": "^3.2.0",
120
120
  "@iarna/toml": "^3.0.0",
121
121
  "@microsoft/api-extractor": "^7.28.3",
122
- "@miniflare/tre": "^3.0.0-next.5",
122
+ "@miniflare/tre": "^3.0.0-next.6",
123
123
  "@types/better-sqlite3": "^7.6.0",
124
124
  "@types/busboy": "^1.5.0",
125
125
  "@types/command-exists": "^1.2.0",
@@ -830,6 +830,31 @@ describe("wrangler dev", () => {
830
830
  });
831
831
 
832
832
  describe("inspector port", () => {
833
+ it("should connect WebSocket server with --experimental-local", async () => {
834
+ writeWranglerToml({
835
+ main: "./index.js",
836
+ });
837
+ fs.writeFileSync(
838
+ "index.js",
839
+ `export default {
840
+ async fetch(request, env, ctx ){
841
+ console.log('Hello World LOGGING');
842
+ },
843
+ };`
844
+ );
845
+ await runWrangler("dev --experimental-local");
846
+
847
+ expect((Dev as jest.Mock).mock.calls[0][0].inspectorPort).toEqual(9229);
848
+ expect(std).toMatchInlineSnapshot(`
849
+ Object {
850
+ "debug": "",
851
+ "err": "",
852
+ "out": "",
853
+ "warn": "",
854
+ }
855
+ `);
856
+ });
857
+
833
858
  it("should use 9229 as the default port", async () => {
834
859
  writeWranglerToml({
835
860
  main: "index.js",
@@ -1178,6 +1203,7 @@ describe("wrangler dev", () => {
1178
1203
  --tsconfig Path to a custom tsconfig.json file [string]
1179
1204
  -l, --local Run on my machine [boolean] [default: false]
1180
1205
  --experimental-local Run on my machine using the Cloudflare Workers runtime [boolean] [default: false]
1206
+ --experimental-local-remote-kv Read/write KV data from/to real namespaces on the Cloudflare network [boolean] [default: false]
1181
1207
  --minify Minify the script [boolean]
1182
1208
  --node-compat Enable node.js compatibility [boolean]
1183
1209
  --persist Enable persistence for local mode, using default path: .wrangler/state [boolean]
@@ -51,6 +51,7 @@ describe("wrangler", () => {
51
51
  wrangler logout 🚪 Logout from Cloudflare
52
52
  wrangler whoami 🕵️ Retrieve your user info and test your auth config
53
53
  wrangler types 📝 Generate types from bindings & module rules in config
54
+ wrangler deployments 🚢 Displays the 10 most recent deployments for a worker
54
55
 
55
56
  Flags:
56
57
  -c, --config Path to .toml configuration file [string]
@@ -96,6 +97,7 @@ describe("wrangler", () => {
96
97
  wrangler logout 🚪 Logout from Cloudflare
97
98
  wrangler whoami 🕵️ Retrieve your user info and test your auth config
98
99
  wrangler types 📝 Generate types from bindings & module rules in config
100
+ wrangler deployments 🚢 Displays the 10 most recent deployments for a worker
99
101
 
100
102
  Flags:
101
103
  -c, --config Path to .toml configuration file [string]
@@ -1165,16 +1165,21 @@ describe("pages", () => {
1165
1165
  const body = init.body as FormData;
1166
1166
  const manifest = JSON.parse(body.get("manifest") as string);
1167
1167
 
1168
- // for Functions projects, we auto-generate a `_worker.js` and `_routes.json`
1168
+ // for Functions projects, we auto-generate a `_worker.js`,
1169
+ // `functions-filepath-routing-config.json`, and `_routes.json`
1169
1170
  // file, based on the contents of `/functions`
1170
1171
  const generatedWorkerJS = body.get("_worker.js") as Blob;
1171
1172
  const generatedRoutesJSON = await (
1172
1173
  body.get("_routes.json") as Blob
1173
1174
  ).text();
1175
+ const generatedFilepathRoutingConfig = await (
1176
+ body.get("functions-filepath-routing-config.json") as Blob
1177
+ ).text();
1174
1178
 
1175
1179
  // make sure this is all we uploaded
1176
1180
  expect([...body.keys()]).toEqual([
1177
1181
  "manifest",
1182
+ "functions-filepath-routing-config.json",
1178
1183
  "_worker.js",
1179
1184
  "_routes.json",
1180
1185
  ]);
@@ -1202,6 +1207,25 @@ describe("pages", () => {
1202
1207
  include: ["/hello"],
1203
1208
  exclude: [],
1204
1209
  });
1210
+
1211
+ // Make sure the routing config is valid json
1212
+ const parsedFilepathRoutingConfig = JSON.parse(
1213
+ generatedFilepathRoutingConfig
1214
+ );
1215
+ // The actual shape doesn't matter that much since this
1216
+ // is only used for display in Dash, but it's still useful for
1217
+ // tracking unexpected changes to this config.
1218
+ expect(parsedFilepathRoutingConfig).toStrictEqual({
1219
+ routes: [
1220
+ {
1221
+ routePath: "/hello",
1222
+ mountPath: "/",
1223
+ method: "",
1224
+ module: ["hello.js:onRequest"],
1225
+ },
1226
+ ],
1227
+ baseURL: "/",
1228
+ });
1205
1229
  });
1206
1230
 
1207
1231
  return {
@@ -1456,10 +1480,14 @@ describe("pages", () => {
1456
1480
  const customRoutesJSON = await (
1457
1481
  body.get("_routes.json") as Blob
1458
1482
  ).text();
1483
+ const generatedFilepathRoutingConfig = await (
1484
+ body.get("functions-filepath-routing-config.json") as Blob
1485
+ ).text();
1459
1486
 
1460
1487
  // make sure this is all we uploaded
1461
1488
  expect([...body.keys()]).toEqual([
1462
1489
  "manifest",
1490
+ "functions-filepath-routing-config.json",
1463
1491
  "_worker.js",
1464
1492
  "_routes.json",
1465
1493
  ]);
@@ -1481,6 +1509,32 @@ describe("pages", () => {
1481
1509
  include: ["/hello"],
1482
1510
  exclude: [],
1483
1511
  });
1512
+
1513
+ // Make sure the routing config is valid json
1514
+ const parsedFilepathRoutingConfig = JSON.parse(
1515
+ generatedFilepathRoutingConfig
1516
+ );
1517
+ // The actual shape doesn't matter that much since this
1518
+ // is only used for display in Dash, but it's still useful for
1519
+ // tracking unexpected changes to this config.
1520
+ console.log(generatedFilepathRoutingConfig);
1521
+ expect(parsedFilepathRoutingConfig).toStrictEqual({
1522
+ routes: [
1523
+ {
1524
+ routePath: "/goodbye",
1525
+ mountPath: "/",
1526
+ method: "",
1527
+ module: ["goodbye.ts:onRequest"],
1528
+ },
1529
+ {
1530
+ routePath: "/hello",
1531
+ mountPath: "/",
1532
+ method: "",
1533
+ module: ["hello.js:onRequest"],
1534
+ },
1535
+ ],
1536
+ baseURL: "/",
1537
+ });
1484
1538
  });
1485
1539
 
1486
1540
  return {
package/src/api/dev.ts CHANGED
@@ -56,6 +56,8 @@ interface DevOptions {
56
56
  $0?: string; //yargs wants this
57
57
  testScheduled?: boolean;
58
58
  experimentalLocal?: boolean;
59
+ accountId?: string;
60
+ experimentalLocalRemoteKv?: boolean;
59
61
  }
60
62
 
61
63
  interface DevApiOptions {
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { mkdir } from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import chalk from "chalk";
4
5
  import { render, Static, Text } from "ink";
5
6
  import Table from "ink-table";
6
7
  import { npxImport } from "npx-import";
@@ -141,7 +142,7 @@ export const Handler = withConfig<ExecuteArgs>(
141
142
  }): Promise<void> => {
142
143
  logger.log(d1BetaWarning);
143
144
  if (file && command)
144
- return console.error(`Error: can't provide both --command and --file.`);
145
+ return logger.error(`Error: can't provide both --command and --file.`);
145
146
 
146
147
  const isInteractive = process.stdout.isTTY;
147
148
  const response: QueryResult[] | null = await executeSql(
@@ -180,7 +181,7 @@ export const Handler = withConfig<ExecuteArgs>(
180
181
  </Static>
181
182
  );
182
183
  } else {
183
- console.log(JSON.stringify(response, null, 2));
184
+ logger.log(JSON.stringify(response, null, 2));
184
185
  }
185
186
  }
186
187
  );
@@ -250,7 +251,7 @@ async function executeRemotely(
250
251
  if (!ok) return null;
251
252
  logger.log(`🌀 Let's go`);
252
253
  } else {
253
- console.error(warning);
254
+ logger.error(warning);
254
255
  }
255
256
  }
256
257
 
@@ -267,11 +268,16 @@ async function executeRemotely(
267
268
  // Don't output if shouldPrompt is undefined
268
269
  } else if (shouldPrompt !== undefined) {
269
270
  // Pipe to error so we don't break jq
270
- console.error(`Executing on ${name} (${db.uuid}):`);
271
+ logger.error(`Executing on ${name} (${db.uuid}):`);
271
272
  }
272
273
 
273
274
  const results: QueryResult[] = [];
274
275
  for (const sql of batches) {
276
+ if (multiple_batches)
277
+ logger.log(
278
+ chalk.gray(` ${sql.slice(0, 70)}${sql.length > 70 ? "..." : ""}`)
279
+ );
280
+
275
281
  const result = await fetchResult<QueryResult[]>(
276
282
  `/accounts/${accountId}/d1/database/${db.uuid}/query`,
277
283
  {
@@ -317,16 +323,18 @@ function splitSql(splitter: (query: SQLQuery) => SQLQuery[], sql: SQLQuery) {
317
323
 
318
324
  function batchSplit(queries: string[]) {
319
325
  logger.log(`🌀 Parsing ${queries.length} statements`);
320
- const batches: string[] = [];
321
326
  const num_batches = Math.ceil(queries.length / QUERY_LIMIT);
327
+ const batches: string[] = [];
322
328
  for (let i = 0; i < num_batches; i++) {
323
329
  batches.push(
324
330
  queries.slice(i * QUERY_LIMIT, (i + 1) * QUERY_LIMIT).join("; ")
325
331
  );
326
332
  }
327
- logger.log(
328
- `🌀 We are sending ${batches.length} batch(es) to D1 (limited to ${QUERY_LIMIT} statements per batch)`
329
- );
333
+ if (num_batches > 1) {
334
+ logger.log(
335
+ `🌀 We are sending ${num_batches} batch(es) to D1 (limited to ${QUERY_LIMIT} statements per batch)`
336
+ );
337
+ }
330
338
  return batches;
331
339
  }
332
340
 
package/src/dev/dev.tsx CHANGED
@@ -155,6 +155,7 @@ export type DevProps = {
155
155
  sendMetrics: boolean | undefined;
156
156
  testScheduled: boolean | undefined;
157
157
  experimentalLocal: boolean | undefined;
158
+ experimentalLocalRemoteKv: boolean | undefined;
158
159
  };
159
160
 
160
161
  export function DevImplementation(props: DevProps): JSX.Element {
@@ -326,6 +327,8 @@ function DevSession(props: DevSessionProps) {
326
327
  onReady={props.onReady}
327
328
  enablePagesAssetsServiceBinding={props.enablePagesAssetsServiceBinding}
328
329
  experimentalLocal={props.experimentalLocal}
330
+ accountId={props.accountId}
331
+ experimentalLocalRemoteKv={props.experimentalLocalRemoteKv}
329
332
  />
330
333
  ) : (
331
334
  <Remote
package/src/dev/local.tsx CHANGED
@@ -4,10 +4,10 @@ import { realpathSync } from "node:fs";
4
4
  import { readFile, writeFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  import chalk from "chalk";
7
- import getPort from "get-port";
8
7
  import { npxImport } from "npx-import";
9
8
  import { useState, useEffect, useRef } from "react";
10
9
  import onExit from "signal-exit";
10
+ import { performApiFetch } from "../cfetch/internal";
11
11
  import { registerWorker } from "../dev-registry";
12
12
  import useInspector from "../inspect";
13
13
  import { logger } from "../logger";
@@ -17,6 +17,7 @@ import {
17
17
  } from "../module-collection";
18
18
  import { getBasePath } from "../paths";
19
19
  import { waitForPortToBeAvailable } from "../proxy";
20
+ import { requireAuth } from "../user";
20
21
  import type { Config } from "../config";
21
22
  import type { WorkerRegistry } from "../dev-registry";
22
23
  import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli";
@@ -38,9 +39,11 @@ import type { EsbuildBundle } from "./use-esbuild";
38
39
  import type {
39
40
  Miniflare as Miniflare3Type,
40
41
  MiniflareOptions as Miniflare3Options,
42
+ CloudflareFetch,
41
43
  } from "@miniflare/tre";
42
44
  import type { MiniflareOptions } from "miniflare";
43
45
  import type { ChildProcess } from "node:child_process";
46
+ import type { RequestInit } from "undici";
44
47
 
45
48
  export interface LocalProps {
46
49
  name: string | undefined;
@@ -67,15 +70,29 @@ export interface LocalProps {
67
70
  logPrefix?: string;
68
71
  enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
69
72
  testScheduled?: boolean;
70
- experimentalLocal?: boolean;
73
+ experimentalLocal: boolean | undefined;
74
+ accountId: string | undefined; // Account ID? In local mode??? :exploding_head:
75
+ experimentalLocalRemoteKv: boolean | undefined;
71
76
  }
72
77
 
78
+ type InspectorJSON = {
79
+ id: string;
80
+ title: string;
81
+ type: "node";
82
+ description: string;
83
+ webSocketDebuggerUrl: string;
84
+ devtoolsFrontendUrl: string;
85
+ devtoolsFrontendUrlCompat: string;
86
+ faviconUrl: string;
87
+ url: string;
88
+ }[];
89
+
73
90
  export function Local(props: LocalProps) {
74
91
  const { inspectorUrl } = useLocalWorker(props);
75
92
  useInspector({
76
93
  inspectorUrl,
77
94
  port: props.inspectorPort,
78
- logToTerminal: false,
95
+ logToTerminal: props.experimentalLocal ?? false,
79
96
  });
80
97
  return null;
81
98
  }
@@ -105,6 +122,8 @@ function useLocalWorker({
105
122
  logPrefix,
106
123
  enablePagesAssetsServiceBinding,
107
124
  experimentalLocal,
125
+ accountId,
126
+ experimentalLocalRemoteKv,
108
127
  }: LocalProps) {
109
128
  // TODO: pass vars via command line
110
129
  const local = useRef<ChildProcess>();
@@ -209,8 +228,19 @@ function useLocalWorker({
209
228
  });
210
229
 
211
230
  if (experimentalLocal) {
212
- const mf3Options = await transformLocalOptions(options, format, bundle);
231
+ const mf3Options = await transformLocalOptions({
232
+ miniflare2Options: options,
233
+ format,
234
+ bundle,
235
+ kvNamespaces: bindings?.kv_namespaces,
236
+ r2Buckets: bindings?.r2_buckets,
237
+ authenticatedAccountId: accountId,
238
+ kvRemote: experimentalLocalRemoteKv,
239
+ inspectorPort,
240
+ });
241
+
213
242
  const current = experimentalLocalRef.current;
243
+
214
244
  if (current === undefined) {
215
245
  // If we don't have an active Miniflare instance, create a new one
216
246
  const Miniflare = await getMiniflare3Constructor();
@@ -230,6 +260,34 @@ function useLocalWorker({
230
260
  logger.log("⎔ Reloading experimental local server.");
231
261
  await current.setOptions(mf3Options);
232
262
  }
263
+
264
+ try {
265
+ // fetch the inspector JSON response from the DevTools Inspector protocol
266
+ const inspectorJSONArr = (await (
267
+ await fetch(`http://127.0.0.1:${inspectorPort}/json`)
268
+ ).json()) as InspectorJSON;
269
+
270
+ const foundInspectorURL = inspectorJSONArr?.find((inspectorJSON) =>
271
+ inspectorJSON.id.startsWith("core:user")
272
+ )?.webSocketDebuggerUrl;
273
+ if (foundInspectorURL === undefined) {
274
+ setInspectorUrl(undefined);
275
+ } else {
276
+ const url = new URL(foundInspectorURL);
277
+ // Force inspector URL to be different on each reload so `useEffect`
278
+ // in `useInspector` is re-run to connect to newly restarted
279
+ // `workerd` server when updating options. Can't use a query param
280
+ // here as that seems to cause an infinite connection loop, can't
281
+ // use a hash as those are forbidden by `ws`, so username it is.
282
+ url.username = `${Date.now()}-${Math.floor(
283
+ Math.random() * Number.MAX_SAFE_INTEGER
284
+ )}`;
285
+ setInspectorUrl(url.toString());
286
+ }
287
+ } catch (error: unknown) {
288
+ logger.error("Error attempting to retrieve Debugger URL:", error);
289
+ }
290
+
233
291
  return;
234
292
  }
235
293
 
@@ -367,6 +425,8 @@ function useLocalWorker({
367
425
  onReady,
368
426
  enablePagesAssetsServiceBinding,
369
427
  experimentalLocal,
428
+ accountId,
429
+ experimentalLocalRemoteKv,
370
430
  ]);
371
431
 
372
432
  // Rather than disposing the Miniflare instance on every reload, only dispose
@@ -683,24 +743,65 @@ export function setupNodeOptions({
683
743
  return nodeOptions;
684
744
  }
685
745
 
686
- function arrayToObject(values: string[] = []): Record<string, string> {
687
- return Object.fromEntries(values.map((value) => [value, value]));
746
+ export interface SetupMiniflare3Options {
747
+ // Regular Miniflare 2 options to transform
748
+ miniflare2Options: MiniflareOptions;
749
+ // Miniflare 3 requires all modules to be manually specified
750
+ format: CfScriptFormat;
751
+ bundle: EsbuildBundle;
752
+
753
+ // Miniflare 3 accepts namespace/bucket names in addition to binding names.
754
+ // This means multiple workers persisting to the same location can have
755
+ // different binding names for the same namespace/bucket. Therefore, we need
756
+ // the full KV/R2 arrays. This is also required for remote KV storage, as
757
+ // we need actual namespace IDs to connect to.
758
+ kvNamespaces: CfKvNamespace[] | undefined;
759
+ r2Buckets: CfR2Bucket[] | undefined;
760
+
761
+ // Account ID to use for authenticated Cloudflare fetch. If true, prompt
762
+ // user for ID if multiple available.
763
+ authenticatedAccountId: string | true | undefined;
764
+ // Whether to read/write from/to real KV namespaces
765
+ kvRemote: boolean | undefined;
766
+ inspectorPort: number;
688
767
  }
689
768
 
690
- export async function transformLocalOptions(
691
- mf2Options: MiniflareOptions,
692
- format: CfScriptFormat,
693
- bundle: EsbuildBundle
694
- ): Promise<Miniflare3Options> {
769
+ export async function transformLocalOptions({
770
+ miniflare2Options,
771
+ format,
772
+ bundle,
773
+ kvNamespaces,
774
+ r2Buckets,
775
+ authenticatedAccountId,
776
+ kvRemote,
777
+ inspectorPort,
778
+ }: SetupMiniflare3Options): Promise<Miniflare3Options> {
779
+ // Build authenticated Cloudflare API fetch function if required
780
+ let cloudflareFetch: CloudflareFetch | undefined;
781
+ if (kvRemote && authenticatedAccountId !== undefined) {
782
+ const preferredAccountId =
783
+ authenticatedAccountId === true ? undefined : authenticatedAccountId;
784
+ const accountId = await requireAuth({ account_id: preferredAccountId });
785
+ cloudflareFetch = (resource, searchParams, init) => {
786
+ resource = `/accounts/${accountId}/${resource}`;
787
+ // Miniflare and Wrangler's `undici` versions may be slightly different,
788
+ // but their `RequestInit` types *should* be compatible
789
+ return performApiFetch(resource, init as RequestInit, searchParams);
790
+ };
791
+ }
792
+
695
793
  const options: Miniflare3Options = {
696
- ...mf2Options,
794
+ ...miniflare2Options,
697
795
  // Miniflare 3 distinguishes between binding name and namespace/bucket IDs.
698
- // For now, just use the same value as we did in Miniflare 2.
699
- // TODO: use defined KV preview ID if any
700
- kvNamespaces: arrayToObject(mf2Options.kvNamespaces),
701
- r2Buckets: arrayToObject(mf2Options.r2Buckets),
702
- inspectorPort: await getPort({ port: 9229 }),
796
+ kvNamespaces: Object.fromEntries(
797
+ kvNamespaces?.map(({ binding, id }) => [binding, id]) ?? []
798
+ ),
799
+ r2Buckets: Object.fromEntries(
800
+ r2Buckets?.map(({ binding, bucket_name }) => [binding, bucket_name]) ?? []
801
+ ),
802
+ inspectorPort,
703
803
  verbose: true,
804
+ cloudflareFetch,
704
805
  };
705
806
 
706
807
  if (format === "modules") {
@@ -729,6 +830,21 @@ export async function transformLocalOptions(
729
830
  ];
730
831
  }
731
832
 
833
+ if (kvRemote) {
834
+ // `kvPersist` is always assigned a truthy value in `setupMiniflareOptions`
835
+ assert(options.kvPersist);
836
+ const kvRemoteCache =
837
+ options.kvPersist === true
838
+ ? // If storing in temporary directory, find this path from the bundle
839
+ // output path
840
+ path.join(path.dirname(bundle.path), ".mf", "kv-remote")
841
+ : // Otherwise, `kvPersist` looks like `.../kv`, so rewrite it to
842
+ // `kv-remote` since the expected metadata format for remote storage
843
+ // is different to local
844
+ path.join(path.dirname(options.kvPersist), "kv-remote");
845
+ options.kvPersist = `remote:?cache=${encodeURIComponent(kvRemoteCache)}`;
846
+ }
847
+
732
848
  return options;
733
849
  }
734
850
 
@@ -740,7 +856,7 @@ export async function getMiniflare3Constructor(): Promise<typeof Miniflare> {
740
856
  ({ Miniflare } = await npxImport<
741
857
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
742
858
  typeof import("@miniflare/tre")
743
- >("@miniflare/tre@3.0.0-next.5"));
859
+ >("@miniflare/tre@3.0.0-next.6"));
744
860
  }
745
861
  return Miniflare;
746
862
  }
@@ -127,6 +127,8 @@ export async function startDevServer(
127
127
  usageModel: props.usageModel,
128
128
  workerDefinitions,
129
129
  experimentalLocal: props.experimentalLocal,
130
+ accountId: props.accountId,
131
+ experimentalLocalRemoteKv: props.experimentalLocalRemoteKv,
130
132
  });
131
133
 
132
134
  return {
@@ -303,6 +305,8 @@ export async function startLocalServer({
303
305
  logPrefix,
304
306
  enablePagesAssetsServiceBinding,
305
307
  experimentalLocal,
308
+ accountId,
309
+ experimentalLocalRemoteKv,
306
310
  }: LocalProps) {
307
311
  let local: ChildProcess | undefined;
308
312
  let experimentalLocalRef: Miniflare3Type | undefined;
@@ -396,7 +400,16 @@ export async function startLocalServer({
396
400
  });
397
401
 
398
402
  if (experimentalLocal) {
399
- const mf3Options = await transformLocalOptions(options, format, bundle);
403
+ const mf3Options = await transformLocalOptions({
404
+ miniflare2Options: options,
405
+ format,
406
+ bundle,
407
+ kvNamespaces: bindings?.kv_namespaces,
408
+ r2Buckets: bindings?.r2_buckets,
409
+ authenticatedAccountId: accountId,
410
+ kvRemote: experimentalLocalRemoteKv,
411
+ inspectorPort,
412
+ });
400
413
  const Miniflare = await getMiniflare3Constructor();
401
414
  const mf = new Miniflare(mf3Options);
402
415
  const runtimeURL = await mf.ready;
package/src/dev.tsx CHANGED
@@ -65,6 +65,7 @@ interface DevArgs {
65
65
  tsconfig?: string;
66
66
  local?: boolean;
67
67
  "experimental-local"?: boolean;
68
+ "experimental-local-remote-kv"?: boolean;
68
69
  minify?: boolean;
69
70
  var?: string[];
70
71
  define?: string[];
@@ -237,11 +238,25 @@ export function devOptions(yargs: Argv<CommonYargsOptions>): Argv<DevArgs> {
237
238
  type: "boolean",
238
239
  default: false,
239
240
  })
241
+ .option("experimental-local-remote-kv", {
242
+ describe:
243
+ "Read/write KV data from/to real namespaces on the Cloudflare network",
244
+ type: "boolean",
245
+ default: false,
246
+ })
240
247
  .check((argv) => {
241
248
  if (argv.local && argv["experimental-local"]) {
242
249
  throw new Error(
243
250
  "--local and --experimental-local are mutually exclusive. " +
244
- "Please select one or the other."
251
+ "Please enable one or the other."
252
+ );
253
+ }
254
+ if (
255
+ argv["experimental-local-remote-kv"] &&
256
+ !argv["experimental-local"]
257
+ ) {
258
+ throw new Error(
259
+ "--experimental-local-remote-kv requires --experimental-local to be enabled."
245
260
  );
246
261
  }
247
262
  return true;
@@ -461,6 +476,7 @@ export async function startDev(args: StartDevOptions) {
461
476
  sendMetrics={configParam.send_metrics}
462
477
  testScheduled={args["test-scheduled"]}
463
478
  experimentalLocal={args.experimentalLocal}
479
+ experimentalLocalRemoteKv={args.experimentalLocalRemoteKv}
464
480
  />
465
481
  );
466
482
  }
@@ -582,6 +598,7 @@ export async function startApiDev(args: StartDevOptions) {
582
598
  sendMetrics: configParam.send_metrics,
583
599
  testScheduled: args.testScheduled,
584
600
  experimentalLocal: args.experimentalLocal,
601
+ experimentalLocalRemoteKv: args.experimentalLocalRemoteKv,
585
602
  });
586
603
  }
587
604
 
package/src/dialogs.tsx CHANGED
@@ -153,8 +153,7 @@ export async function fromDashMessagePrompt(
153
153
  ): Promise<boolean | void> {
154
154
  if (deploySource === "dash") {
155
155
  logger.warn(
156
- `You are about to publish a Workers Service that was last published via the Cloudflare Dashboard.
157
- Edits that have been made via the dashboard will be overridden by your local code and config.`
156
+ `You are about to publish a Workers Service that was last published via the Cloudflare Dashboard.\nEdits that have been made via the dashboard will be overridden by your local code and config.`
158
157
  );
159
158
 
160
159
  if (!isInteractive() || CI.isCI()) return true;
package/src/index.tsx CHANGED
@@ -554,8 +554,7 @@ export function createCLIParser(argv: string[]) {
554
554
  "🚧`wrangler deployments` is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose";
555
555
  wrangler.command(
556
556
  "deployments",
557
- false,
558
- // "🚢 Logs the 10 most recent deployments with 'Version ID', 'Version number','Author email', 'Created on' and 'Latest deploy'",
557
+ "🚢 Displays the 10 most recent deployments for a worker",
559
558
  (yargs) => {
560
559
  yargs
561
560
  .option("name", {
@@ -275,11 +275,20 @@ export const Handler = async ({
275
275
  ? join(tmpdir(), `_routes-${Math.random()}.json`)
276
276
  : undefined;
277
277
 
278
+ // Routing configuration displayed in the Functions tab of a deployment in Dash
279
+ let filepathRoutingConfig: string | undefined;
280
+
278
281
  if (!_workerJS && existsSync(functionsDirectory)) {
279
282
  const outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.js`);
283
+ const outputConfigPath = join(
284
+ tmpdir(),
285
+ `functions-filepath-routing-config-${Math.random()}.json`
286
+ );
287
+
280
288
  try {
281
289
  await buildFunctions({
282
290
  outfile,
291
+ outputConfigPath,
283
292
  functionsDirectory,
284
293
  onEnd: () => {},
285
294
  buildOutputDirectory: dirname(outfile),
@@ -292,6 +301,7 @@ export const Handler = async ({
292
301
  });
293
302
 
294
303
  builtFunctions = readFileSync(outfile, "utf-8");
304
+ filepathRoutingConfig = readFileSync(outputConfigPath, "utf-8");
295
305
  } catch (e) {
296
306
  if (e instanceof FunctionsNoRoutesError) {
297
307
  logger.warn(
@@ -335,6 +345,16 @@ export const Handler = async ({
335
345
  logger.log(`✨ Uploading _redirects`);
336
346
  }
337
347
 
348
+ if (filepathRoutingConfig) {
349
+ formData.append(
350
+ "functions-filepath-routing-config.json",
351
+ new File(
352
+ [filepathRoutingConfig],
353
+ "functions-filepath-routing-config.json"
354
+ )
355
+ );
356
+ }
357
+
338
358
  /**
339
359
  * Advanced Mode
340
360
  * https://developers.cloudflare.com/pages/platform/functions/#advanced-mode
@@ -129,6 +129,8 @@ declare interface DevOptions {
129
129
  $0?: string;
130
130
  testScheduled?: boolean;
131
131
  experimentalLocal?: boolean;
132
+ accountId?: string;
133
+ experimentalLocalRemoteKv?: boolean;
132
134
  }
133
135
 
134
136
  declare interface EnablePagesAssetsServiceBindingOptions {