wrangler 0.0.0-e2a26a0 → 0.0.0-e3cab74

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.
Files changed (68) hide show
  1. package/package.json +16 -7
  2. package/pages/functions/filepath-routing.test.ts +112 -36
  3. package/pages/functions/filepath-routing.ts +66 -119
  4. package/pages/functions/routes.ts +8 -17
  5. package/pages/functions/template-worker.ts +3 -9
  6. package/src/__tests__/configuration.test.ts +2185 -0
  7. package/src/__tests__/dev.test.tsx +48 -6
  8. package/src/__tests__/guess-worker-format.test.ts +66 -0
  9. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  10. package/src/__tests__/helpers/mock-account-id.ts +6 -6
  11. package/src/__tests__/helpers/mock-bin.ts +4 -1
  12. package/src/__tests__/helpers/mock-cfetch.ts +2 -2
  13. package/src/__tests__/helpers/mock-console.ts +11 -5
  14. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  15. package/src/__tests__/helpers/run-wrangler.ts +5 -3
  16. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  17. package/src/__tests__/index.test.ts +343 -20
  18. package/src/__tests__/jest.setup.ts +21 -1
  19. package/src/__tests__/kv.test.ts +82 -80
  20. package/src/__tests__/package-manager.test.ts +2 -2
  21. package/src/__tests__/parse.test.ts +322 -0
  22. package/src/__tests__/publish.test.ts +1428 -244
  23. package/src/__tests__/r2.test.ts +198 -0
  24. package/src/__tests__/secret.test.ts +176 -60
  25. package/src/__tests__/sentry.test.ts +146 -0
  26. package/src/__tests__/tail.test.ts +564 -0
  27. package/src/api/form_data.ts +106 -23
  28. package/src/api/preview.ts +32 -13
  29. package/src/api/worker.ts +49 -18
  30. package/src/bundle.ts +133 -0
  31. package/src/cfetch/index.ts +8 -6
  32. package/src/cfetch/internal.ts +49 -17
  33. package/src/cli.ts +10 -0
  34. package/src/config/README.md +107 -0
  35. package/src/config/config.ts +265 -0
  36. package/src/config/diagnostics.ts +80 -0
  37. package/src/config/environment.ts +305 -0
  38. package/src/config/index.ts +52 -0
  39. package/src/config/validation-helpers.ts +370 -0
  40. package/src/config/validation.ts +1017 -0
  41. package/src/dev.tsx +338 -211
  42. package/src/entry.ts +57 -0
  43. package/src/environment-variables.ts +33 -0
  44. package/src/guess-worker-format.ts +68 -0
  45. package/src/index.tsx +860 -634
  46. package/src/inspect.ts +16 -3
  47. package/src/kv.tsx +20 -29
  48. package/src/module-collection.ts +141 -34
  49. package/src/open-in-browser.ts +13 -0
  50. package/src/package-manager.ts +21 -15
  51. package/src/pages.tsx +9 -10
  52. package/src/parse.ts +183 -0
  53. package/src/proxy.ts +11 -5
  54. package/src/publish.ts +272 -314
  55. package/src/r2.ts +50 -0
  56. package/src/reporting.ts +115 -0
  57. package/src/sites.tsx +16 -15
  58. package/src/tail/filters.ts +279 -0
  59. package/src/tail/index.ts +299 -0
  60. package/src/tail/printing.ts +62 -0
  61. package/src/user.tsx +74 -44
  62. package/templates/new-worker.js +1 -1
  63. package/templates/new-worker.ts +1 -1
  64. package/templates/tsconfig.json +105 -0
  65. package/wrangler-dist/cli.js +51308 -36440
  66. package/wrangler-dist/cli.js.map +3 -3
  67. package/src/config.ts +0 -641
  68. package/src/tail.tsx +0 -73
package/src/index.tsx CHANGED
@@ -1,9 +1,10 @@
1
1
  import * as fs from "node:fs";
2
- import { readFile, writeFile, mkdir } from "node:fs/promises";
2
+ import { writeFile, mkdir } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { setTimeout } from "node:timers/promises";
5
5
  import TOML from "@iarna/toml";
6
6
  import { findUp } from "find-up";
7
+ import getPort from "get-port";
7
8
  import { render } from "ink";
8
9
  import React from "react";
9
10
  import onExit from "signal-exit";
@@ -11,9 +12,10 @@ import makeCLI from "yargs";
11
12
  import { version as wranglerVersion } from "../package.json";
12
13
  import { toFormData } from "./api/form_data";
13
14
  import { fetchResult } from "./cfetch";
14
- import { normaliseAndValidateEnvironmentsConfig } from "./config";
15
+ import { findWranglerToml, readConfig } from "./config";
15
16
  import Dev from "./dev";
16
17
  import { confirm, prompt } from "./dialogs";
18
+ import { getEntry } from "./entry";
17
19
  import {
18
20
  getNamespaceId,
19
21
  listNamespaces,
@@ -27,9 +29,16 @@ import {
27
29
  } from "./kv";
28
30
  import { getPackageManager } from "./package-manager";
29
31
  import { pages } from "./pages";
32
+ import { formatMessage, ParseError, parseJSON, readFileSync } from "./parse";
30
33
  import publish from "./publish";
34
+ import { createR2Bucket, deleteR2Bucket, listR2Buckets } from "./r2";
31
35
  import { getAssetPaths } from "./sites";
32
- import { createTail } from "./tail";
36
+ import {
37
+ createTail,
38
+ jsonPrintLogs,
39
+ prettyPrintLogs,
40
+ translateCLICommandToFilterMessage,
41
+ } from "./tail";
33
42
  import {
34
43
  login,
35
44
  logout,
@@ -42,39 +51,53 @@ import {
42
51
  import { whoami } from "./whoami";
43
52
 
44
53
  import type { Config } from "./config";
54
+ import type { TailCLIFilters } from "./tail";
55
+ import type { RawData } from "ws";
45
56
  import type Yargs from "yargs";
46
57
 
58
+ type ConfigPath = string | undefined;
59
+
47
60
  const resetColor = "\x1b[0m";
48
61
  const fgGreenColor = "\x1b[32m";
49
62
 
50
- async function readConfig(configPath?: string): Promise<Config> {
51
- const config: Config = {};
52
- if (!configPath) {
53
- configPath = await findUp("wrangler.toml");
54
- // TODO - terminate this early instead of going all the way to the root
55
- }
63
+ function getRules(config: Config): Config["rules"] {
64
+ const rules = config.rules ?? config.build?.upload?.rules ?? [];
56
65
 
57
- if (configPath) {
58
- const tml: string = await readFile(configPath, "utf-8");
59
- const parsed = TOML.parse(tml) as Config;
60
- Object.assign(config, parsed);
66
+ if (config.rules && config.build?.upload?.rules) {
67
+ throw new Error(
68
+ `You cannot configure both [rules] and [build.upload.rules] in your wrangler.toml. Delete the \`build.upload\` section.`
69
+ );
61
70
  }
62
71
 
63
- normaliseAndValidateEnvironmentsConfig(config);
64
-
65
- if ("experimental_services" in config) {
72
+ if (config.build?.upload?.rules) {
66
73
  console.warn(
67
- "The experimental_services field is only for cloudflare internal usage right now, and is subject to change. Please do not use this on production projects"
74
+ `Deprecation notice: The \`build.upload.rules\` config field is no longer used, the rules should be specified via the \`rules\` config field. Delete the \`build.upload\` field from the configuration file, and add this:
75
+
76
+ ${TOML.stringify({ rules: config.build.upload.rules })}`
68
77
  );
69
78
  }
79
+ return rules;
80
+ }
70
81
 
71
- // todo: validate, add defaults
72
- // let's just do some basics for now
82
+ function isLegacyEnv(args: unknown, config: Config): boolean {
83
+ return (
84
+ (args as { "legacy-env": boolean | undefined })["legacy-env"] ??
85
+ config.legacy_env
86
+ );
87
+ }
73
88
 
74
- // @ts-expect-error we're being sneaky here for now
75
- config.__path__ = configPath;
89
+ function getScriptName(
90
+ args: { name: string | undefined; env: string | undefined },
91
+ config: Config
92
+ ): string | undefined {
93
+ const shortScriptName = args.name ?? config.name;
94
+ if (!shortScriptName) {
95
+ return;
96
+ }
76
97
 
77
- return config;
98
+ return isLegacyEnv(args, config)
99
+ ? `${shortScriptName}${args.env ? `-${args.env}` : ""}`
100
+ : shortScriptName;
78
101
  }
79
102
 
80
103
  // a helper to demand one of a set of options
@@ -103,8 +126,11 @@ function demandOneOfOption(...options: string[]) {
103
126
  }
104
127
 
105
128
  class CommandLineArgsError extends Error {}
106
- class DeprecationError extends Error {}
107
- class NotImplementedError extends Error {}
129
+ class DeprecationError extends Error {
130
+ constructor(message: string) {
131
+ super(`DEPRECATION WARNING:\n${message}`);
132
+ }
133
+ }
108
134
 
109
135
  export async function main(argv: string[]): Promise<void> {
110
136
  const wrangler = makeCLI(argv)
@@ -173,10 +199,16 @@ export async function main(argv: string[]): Promise<void> {
173
199
  "init [name]",
174
200
  "📥 Create a wrangler.toml configuration file",
175
201
  (yargs) => {
176
- return yargs.positional("name", {
177
- describe: "The name of your worker.",
178
- type: "string",
179
- });
202
+ return yargs
203
+ .positional("name", {
204
+ describe: "The name of your worker",
205
+ type: "string",
206
+ })
207
+ .option("yes", {
208
+ describe: 'Answer "yes" to any prompts for new projects',
209
+ type: "boolean",
210
+ alias: "y",
211
+ });
180
212
  },
181
213
  async (args) => {
182
214
  if ("type" in args) {
@@ -189,11 +221,24 @@ export async function main(argv: string[]): Promise<void> {
189
221
  throw new CommandLineArgsError(message);
190
222
  }
191
223
 
192
- const packageManager = await getPackageManager(process.cwd());
224
+ // TODO: make sure args.name is a valid identifier for a worker name
225
+
226
+ const creationDirectory = path.join(process.cwd(), args.name ?? "");
193
227
 
194
- const destination = "./wrangler.toml";
195
- if (fs.existsSync(destination)) {
196
- console.warn(`${destination} file already exists!`);
228
+ const packageManager = await getPackageManager(creationDirectory);
229
+
230
+ // TODO: ask which directory to make the worker in (defaults to args.name)
231
+ // TODO: if args.name isn't provided, ask what to name the worker
232
+
233
+ const wranglerTomlDestination = path.join(
234
+ creationDirectory,
235
+ "./wrangler.toml"
236
+ );
237
+ let justCreatedWranglerToml = false;
238
+ const workerName =
239
+ args.name || path.basename(path.resolve(process.cwd()));
240
+ if (fs.existsSync(wranglerTomlDestination)) {
241
+ console.warn(`${wranglerTomlDestination} file already exists!`);
197
242
  const shouldContinue = await confirm(
198
243
  "Do you want to continue initializing this project?"
199
244
  );
@@ -201,33 +246,42 @@ export async function main(argv: string[]): Promise<void> {
201
246
  return;
202
247
  }
203
248
  } else {
249
+ await mkdir(creationDirectory, { recursive: true });
204
250
  const compatibilityDate = new Date().toISOString().substring(0, 10);
205
251
  try {
206
252
  await writeFile(
207
- destination,
208
- `compatibility_date = "${compatibilityDate}"` + "\n"
253
+ wranglerTomlDestination,
254
+ TOML.stringify({
255
+ name: workerName,
256
+ compatibility_date: compatibilityDate,
257
+ }) + "\n"
209
258
  );
210
259
  console.log(`✨ Successfully created wrangler.toml`);
211
- // TODO: suggest next steps?
260
+ justCreatedWranglerToml = true;
212
261
  } catch (err) {
213
262
  throw new Error(
214
- `Failed to create wrangler.toml.\n${err.message ?? err}`
263
+ `Failed to create wrangler.toml.\n${(err as Error).message ?? err}`
215
264
  );
216
265
  }
217
266
  }
218
267
 
219
268
  let pathToPackageJson = await findUp("package.json");
269
+ let shouldCreatePackageJson = false;
270
+ const yesFlag = args.yes ?? false;
220
271
  if (!pathToPackageJson) {
221
272
  // If no package.json exists, ask to create one
222
- const shouldCreatePackageJson = await confirm(
223
- "No package.json found. Would you like to create one?"
224
- );
273
+ shouldCreatePackageJson =
274
+ yesFlag ||
275
+ (await confirm(
276
+ "No package.json found. Would you like to create one?"
277
+ ));
278
+
225
279
  if (shouldCreatePackageJson) {
226
280
  await writeFile(
227
- "./package.json",
281
+ path.join(creationDirectory, "./package.json"),
228
282
  JSON.stringify(
229
283
  {
230
- name: "worker",
284
+ name: workerName,
231
285
  version: "0.0.0",
232
286
  devDependencies: {
233
287
  wrangler: wranglerVersion,
@@ -240,15 +294,16 @@ export async function main(argv: string[]): Promise<void> {
240
294
  );
241
295
  await packageManager.install();
242
296
  console.log(`✨ Created package.json`);
243
- pathToPackageJson = path.join(process.cwd(), "package.json");
297
+ pathToPackageJson = path.join(creationDirectory, "package.json");
244
298
  } else {
245
299
  return;
246
300
  }
247
301
  } else {
248
302
  // If package.json exists and wrangler isn't installed,
249
303
  // then ask to add wrangler to devDependencies
250
- const packageJson = JSON.parse(
251
- await readFile(pathToPackageJson, "utf-8")
304
+ const packageJson = parseJSON(
305
+ readFileSync(pathToPackageJson),
306
+ pathToPackageJson
252
307
  );
253
308
  if (
254
309
  !(
@@ -256,9 +311,11 @@ export async function main(argv: string[]): Promise<void> {
256
311
  packageJson.dependencies?.wrangler
257
312
  )
258
313
  ) {
259
- const shouldInstall = await confirm(
260
- "Would you like to install wrangler into your package.json?"
261
- );
314
+ const shouldInstall =
315
+ yesFlag ||
316
+ (await confirm(
317
+ "Would you like to install wrangler into your package.json?"
318
+ ));
262
319
  if (shouldInstall) {
263
320
  await packageManager.addDevDeps(`wrangler@${wranglerVersion}`);
264
321
  console.log(`✨ Installed wrangler`);
@@ -271,28 +328,11 @@ export async function main(argv: string[]): Promise<void> {
271
328
  if (!pathToTSConfig) {
272
329
  // If there's no tsconfig, offer to create one
273
330
  // and install @cloudflare/workers-types
274
- if (await confirm("Would you like to use TypeScript?")) {
331
+ if (yesFlag || (await confirm("Would you like to use TypeScript?"))) {
275
332
  isTypescriptProject = true;
276
333
  await writeFile(
277
- "./tsconfig.json",
278
- JSON.stringify(
279
- {
280
- compilerOptions: {
281
- target: "es2021",
282
- module: "es2022",
283
- moduleResolution: "node",
284
- allowJs: true,
285
- allowSyntheticDefaultImports: true,
286
- isolatedModules: true,
287
- noEmit: true,
288
- lib: ["es2021"],
289
- resolveJsonModule: true,
290
- types: ["@cloudflare/workers-types"],
291
- },
292
- },
293
- null,
294
- " "
295
- ) + "\n"
334
+ path.join(creationDirectory, "./tsconfig.json"),
335
+ readFileSync(path.join(__dirname, "../templates/tsconfig.json"))
296
336
  );
297
337
  await packageManager.addDevDeps(
298
338
  "@cloudflare/workers-types",
@@ -302,14 +342,15 @@ export async function main(argv: string[]): Promise<void> {
302
342
  console.log(
303
343
  `✨ Created tsconfig.json, installed @cloudflare/workers-types into devDependencies`
304
344
  );
305
- pathToTSConfig = path.join(process.cwd(), "tsconfig.json");
345
+ pathToTSConfig = path.join(creationDirectory, "tsconfig.json");
306
346
  }
307
347
  } else {
308
348
  isTypescriptProject = true;
309
349
  // If there's a tsconfig, check if @cloudflare/workers-types
310
350
  // is already installed, and offer to install it if not
311
- const packageJson = JSON.parse(
312
- await readFile(pathToPackageJson, "utf-8")
351
+ const packageJson = parseJSON(
352
+ readFileSync(pathToPackageJson),
353
+ pathToPackageJson
313
354
  );
314
355
  if (
315
356
  !(
@@ -333,21 +374,107 @@ export async function main(argv: string[]): Promise<void> {
333
374
  }
334
375
  }
335
376
 
336
- if (isTypescriptProject) {
337
- if (!fs.existsSync("./src/index.ts")) {
338
- const shouldCreateSource = await confirm(
339
- `Would you like to create a Worker at src/index.ts?`
377
+ const packageJsonContent = parseJSON(
378
+ readFileSync(pathToPackageJson),
379
+ pathToPackageJson
380
+ );
381
+ const shouldWritePackageJsonScripts =
382
+ !packageJsonContent.scripts?.start &&
383
+ !packageJsonContent.scripts?.publish &&
384
+ shouldCreatePackageJson;
385
+
386
+ async function writePackageJsonScriptsAndUpdateWranglerToml(
387
+ isWritingScripts: boolean,
388
+ isCreatingWranglerToml: boolean,
389
+ packagePath: string,
390
+ scriptPath: string
391
+ ) {
392
+ if (isCreatingWranglerToml) {
393
+ // rewrite wrangler.toml with main = "path/to/script"
394
+ const parsedWranglerToml = TOML.parse(
395
+ readFileSync(wranglerTomlDestination)
396
+ );
397
+ fs.writeFileSync(
398
+ wranglerTomlDestination,
399
+ TOML.stringify({
400
+ name: parsedWranglerToml.name,
401
+ main: scriptPath,
402
+ compatibility_date: parsedWranglerToml.compatibility_date,
403
+ })
404
+ );
405
+ }
406
+ const isNamedWorker =
407
+ isCreatingWranglerToml && path.dirname(packagePath) !== process.cwd();
408
+
409
+ if (isWritingScripts) {
410
+ await writeFile(
411
+ packagePath,
412
+ JSON.stringify(
413
+ {
414
+ ...packageJsonContent,
415
+ scripts: {
416
+ ...packageJsonContent.scripts,
417
+ start: isCreatingWranglerToml
418
+ ? `wrangler dev`
419
+ : `wrangler dev ${scriptPath}`,
420
+ publish: isCreatingWranglerToml
421
+ ? `wrangler publish`
422
+ : `wrangler publish ${scriptPath}`,
423
+ },
424
+ },
425
+ null,
426
+ 2
427
+ ) + "\n"
428
+ );
429
+ console.log(
430
+ `\nTo start developing your Worker, run \`${
431
+ isNamedWorker ? `cd ${args.name} && ` : ""
432
+ }npm start\``
433
+ );
434
+ console.log(
435
+ `To publish your Worker to the Internet, run \`npm run publish\``
340
436
  );
437
+ } else {
438
+ console.log(
439
+ `\nTo start developing your Worker, run \`npx wrangler dev\`${
440
+ isCreatingWranglerToml ? "" : ` ${scriptPath}`
441
+ }`
442
+ );
443
+ console.log(
444
+ `To publish your Worker to the Internet, run \`npx wrangler publish\`${
445
+ isCreatingWranglerToml ? "" : ` ${scriptPath}`
446
+ }`
447
+ );
448
+ }
449
+ }
450
+
451
+ if (isTypescriptProject) {
452
+ if (!fs.existsSync(path.join(creationDirectory, "./src/index.ts"))) {
453
+ let shouldCreateSource = false;
454
+
455
+ shouldCreateSource =
456
+ yesFlag ||
457
+ (await confirm(
458
+ `Would you like to create a Worker at src/index.ts?`
459
+ ));
460
+
341
461
  if (shouldCreateSource) {
342
- await mkdir("./src", { recursive: true });
462
+ await mkdir(path.join(creationDirectory, "./src"), {
463
+ recursive: true,
464
+ });
343
465
  await writeFile(
344
- "./src/index.ts",
345
- await readFile(
346
- path.join(__dirname, "../templates/new-worker.ts"),
347
- "utf-8"
348
- )
466
+ path.join(creationDirectory, "./src/index.ts"),
467
+ readFileSync(path.join(__dirname, "../templates/new-worker.ts"))
349
468
  );
469
+
350
470
  console.log(`✨ Created src/index.ts`);
471
+
472
+ await writePackageJsonScriptsAndUpdateWranglerToml(
473
+ shouldWritePackageJsonScripts,
474
+ justCreatedWranglerToml,
475
+ pathToPackageJson,
476
+ "src/index.ts"
477
+ );
351
478
  }
352
479
  }
353
480
  } else {
@@ -356,15 +483,22 @@ export async function main(argv: string[]): Promise<void> {
356
483
  `Would you like to create a Worker at src/index.js?`
357
484
  );
358
485
  if (shouldCreateSource) {
359
- await mkdir("./src", { recursive: true });
486
+ await mkdir(path.join(creationDirectory, "./src"), {
487
+ recursive: true,
488
+ });
360
489
  await writeFile(
361
- path.join("./src/index.js"),
362
- await readFile(
363
- path.join(__dirname, "../templates/new-worker.js"),
364
- "utf-8"
365
- )
490
+ path.join(path.join(creationDirectory, "./src/index.js")),
491
+ readFileSync(path.join(__dirname, "../templates/new-worker.js"))
366
492
  );
493
+
367
494
  console.log(`✨ Created src/index.js`);
495
+
496
+ await writePackageJsonScriptsAndUpdateWranglerToml(
497
+ shouldWritePackageJsonScripts,
498
+ justCreatedWranglerToml,
499
+ pathToPackageJson,
500
+ "src/index.js"
501
+ );
368
502
  }
369
503
  }
370
504
  }
@@ -399,10 +533,10 @@ export async function main(argv: string[]): Promise<void> {
399
533
  // I mean, this entire app does, but this too.
400
534
  return yargs
401
535
  .option("scopes-list", {
402
- describe: "list all the available OAuth scopes with descriptions.",
536
+ describe: "List all the available OAuth scopes with descriptions",
403
537
  })
404
538
  .option("scopes", {
405
- describe: "allows to choose your set of OAuth scopes.",
539
+ describe: "Pick the set of applicable OAuth scopes when logging in",
406
540
  array: true,
407
541
  type: "string",
408
542
  });
@@ -473,17 +607,16 @@ export async function main(argv: string[]): Promise<void> {
473
607
 
474
608
  // dev
475
609
  wrangler.command(
476
- "dev <filename>",
610
+ "dev [script]",
477
611
  "👂 Start a local server for developing your worker",
478
612
  (yargs) => {
479
613
  return yargs
480
- .positional("filename", {
481
- describe: "entry point",
614
+ .positional("script", {
615
+ describe: "The path to an entry point for your worker",
482
616
  type: "string",
483
- demandOption: true,
484
617
  })
485
618
  .option("name", {
486
- describe: "name of the script",
619
+ describe: "Name of the worker",
487
620
  type: "string",
488
621
  })
489
622
  .option("format", {
@@ -493,7 +626,6 @@ export async function main(argv: string[]): Promise<void> {
493
626
  .option("env", {
494
627
  describe: "Perform on a specific environment",
495
628
  type: "string",
496
- // TODO: get choices for the toml file?
497
629
  })
498
630
  .option("compatibility-date", {
499
631
  describe: "Date to use for compatibility checks",
@@ -515,9 +647,12 @@ export async function main(argv: string[]): Promise<void> {
515
647
  default: "127.0.0.1",
516
648
  })
517
649
  .option("port", {
518
- describe: "Port to listen on, defaults to 8787",
650
+ describe: "Port to listen on",
651
+ type: "number",
652
+ })
653
+ .option("inspector-port", {
654
+ describe: "Port for devtools to connect to",
519
655
  type: "number",
520
- default: 8787,
521
656
  })
522
657
  .option("host", {
523
658
  type: "string",
@@ -563,14 +698,23 @@ export async function main(argv: string[]): Promise<void> {
563
698
  describe: "The function that is called for each JSX fragment",
564
699
  type: "string",
565
700
  })
701
+ .option("local", {
702
+ alias: "l",
703
+ describe: "Run on my machine",
704
+ type: "boolean",
705
+ default: false, // I bet this will a point of contention. We'll revisit it.
706
+ })
566
707
  .option("experimental-enable-local-persistence", {
567
708
  describe: "Enable persistence for this session (only for local mode)",
568
709
  type: "boolean",
569
710
  });
570
711
  },
571
712
  async (args) => {
572
- const { filename, format } = args;
573
- const config = args.config as Config;
713
+ const config = readConfig(
714
+ (args.config as ConfigPath) ||
715
+ (args.script && findWranglerToml(path.dirname(args.script)))
716
+ );
717
+ const entry = getEntry(config, "dev", args.script);
574
718
 
575
719
  if (args["experimental-public"]) {
576
720
  console.warn(
@@ -613,24 +757,16 @@ export async function main(argv: string[]): Promise<void> {
613
757
  const environments = config.env ?? {};
614
758
  const envRootObj = args.env ? environments[args.env] || {} : config;
615
759
 
616
- // TODO: this error shouldn't actually happen,
617
- // but we haven't fixed it internally yet
618
- if ("durable_objects" in envRootObj) {
619
- if (!(args.name || config.name)) {
620
- console.warn(
621
- 'A worker with durable objects needs to be named, or it may not work as expected. Add a "name" into wrangler.toml, or pass it in the command line with --name.'
622
- );
623
- }
624
- // TODO: if not already published, publish a draft worker
625
- }
626
-
627
760
  const { waitUntilExit } = render(
628
761
  <Dev
629
- name={args.name || config.name}
630
- entry={path.relative(process.cwd(), filename)}
762
+ name={getScriptName(args, config)}
763
+ entry={entry}
631
764
  env={args.env}
765
+ zone={undefined}
766
+ rules={getRules(config)}
767
+ legacyEnv={isLegacyEnv(args, config)}
632
768
  buildCommand={config.build || {}}
633
- format={format}
769
+ format={args.format || config.build?.upload?.format}
634
770
  initialMode={args.local ? "local" : "remote"}
635
771
  jsxFactory={args["jsx-factory"] || envRootObj?.jsx_factory}
636
772
  jsxFragment={args["jsx-fragment"] || envRootObj?.jsx_fragment}
@@ -644,7 +780,12 @@ export async function main(argv: string[]): Promise<void> {
644
780
  args.siteInclude,
645
781
  args.siteExclude
646
782
  )}
647
- port={args.port || config.dev?.port}
783
+ port={
784
+ args.port || config.dev?.port || (await getPort({ port: 8787 }))
785
+ }
786
+ inspectorPort={
787
+ args["inspector-port"] ?? (await getPort({ port: 9229 }))
788
+ }
648
789
  public={args["experimental-public"]}
649
790
  compatibilityDate={
650
791
  args["compatibility-date"] ||
@@ -655,7 +796,7 @@ export async function main(argv: string[]): Promise<void> {
655
796
  (args["compatibility-flags"] as string[]) ||
656
797
  config.compatibility_flags
657
798
  }
658
- usageModel={config.usage_model}
799
+ usageModel={envRootObj.usage_model}
659
800
  bindings={{
660
801
  kv_namespaces: envRootObj.kv_namespaces?.map(
661
802
  ({ binding, preview_id, id: _id }) => {
@@ -679,8 +820,11 @@ export async function main(argv: string[]): Promise<void> {
679
820
  }
680
821
  ),
681
822
  vars: envRootObj.vars,
823
+ wasm_modules: config.wasm_modules,
824
+ text_blobs: config.text_blobs,
682
825
  durable_objects: envRootObj.durable_objects,
683
- services: envRootObj.experimental_services,
826
+ r2_buckets: envRootObj.r2_buckets,
827
+ unsafe: envRootObj.unsafe?.bindings,
684
828
  }}
685
829
  />
686
830
  );
@@ -699,13 +843,17 @@ export async function main(argv: string[]): Promise<void> {
699
843
  describe: "Perform on a specific environment",
700
844
  })
701
845
  .positional("script", {
702
- describe: "script to upload",
846
+ describe: "The path to an entry point for your worker",
703
847
  type: "string",
704
848
  })
705
849
  .option("name", {
706
- describe: "name to use when uploading",
850
+ describe: "Name of the worker",
707
851
  type: "string",
708
852
  })
853
+ .option("format", {
854
+ choices: ["modules", "service-worker"] as const,
855
+ describe: "Choose an entry type",
856
+ })
709
857
  .option("compatibility-date", {
710
858
  describe: "Date to use for compatibility checks",
711
859
  type: "string",
@@ -746,16 +894,10 @@ export async function main(argv: string[]): Promise<void> {
746
894
  type: "array",
747
895
  })
748
896
  .option("routes", {
749
- describe: "routes to upload",
897
+ describe: "Routes to upload",
750
898
  alias: "route",
751
899
  type: "array",
752
900
  })
753
- .option("services", {
754
- describe: "experimental support for services",
755
- type: "boolean",
756
- default: "false",
757
- hidden: true,
758
- })
759
901
  .option("jsx-factory", {
760
902
  describe: "The function that is called for each JSX element",
761
903
  type: "string",
@@ -766,12 +908,6 @@ export async function main(argv: string[]): Promise<void> {
766
908
  });
767
909
  },
768
910
  async (args) => {
769
- if (args.local) {
770
- throw new NotImplementedError(
771
- "🚫 Local publishing is not yet supported"
772
- );
773
- }
774
-
775
911
  if (args["experimental-public"]) {
776
912
  console.warn(
777
913
  "🚨 The --experimental-public field is experimental and will change in the future."
@@ -783,7 +919,11 @@ export async function main(argv: string[]): Promise<void> {
783
919
  );
784
920
  }
785
921
 
786
- const config = args.config as Config;
922
+ const config = readConfig(
923
+ (args.config as ConfigPath) ||
924
+ (args.script && findWranglerToml(path.dirname(args.script)))
925
+ );
926
+ const entry = getEntry(config, "publish", args.script);
787
927
 
788
928
  if (args.latest) {
789
929
  console.warn(
@@ -791,23 +931,21 @@ export async function main(argv: string[]): Promise<void> {
791
931
  );
792
932
  }
793
933
 
794
- if (!args.local) {
795
- // -- snip, extract --
796
- const loggedIn = await loginOrRefreshIfRequired();
797
- if (!loggedIn) {
798
- // didn't login, let's just quit
799
- console.log("Did not login, quitting...");
800
- return;
801
- }
934
+ // -- snip, extract --
935
+ const loggedIn = await loginOrRefreshIfRequired();
936
+ if (!loggedIn) {
937
+ // didn't login, let's just quit
938
+ console.log("Did not login, quitting...");
939
+ return;
940
+ }
802
941
 
942
+ if (!config.account_id) {
943
+ config.account_id = await getAccountId();
803
944
  if (!config.account_id) {
804
- config.account_id = await getAccountId();
805
- if (!config.account_id) {
806
- throw new Error("No account id found, quitting...");
807
- }
945
+ throw new Error("No account id found, quitting...");
808
946
  }
809
- // -- snip, end --
810
947
  }
948
+ // -- snip, end --
811
949
 
812
950
  const assetPaths = getAssetPaths(
813
951
  config,
@@ -816,9 +954,11 @@ export async function main(argv: string[]): Promise<void> {
816
954
  args.siteExclude
817
955
  );
818
956
  await publish({
819
- config: args.config as Config,
820
- name: args.name,
821
- script: args.script,
957
+ config,
958
+ name: getScriptName(args, config),
959
+ rules: getRules(config),
960
+ format: args.format || config.build?.upload?.format,
961
+ entry,
822
962
  env: args.env,
823
963
  compatibilityDate: args.latest
824
964
  ? new Date().toISOString().substring(0, 10)
@@ -829,8 +969,7 @@ export async function main(argv: string[]): Promise<void> {
829
969
  jsxFragment: args["jsx-fragment"],
830
970
  routes: args.routes,
831
971
  assetPaths,
832
- format: undefined, // TODO: add args for this
833
- legacyEnv: undefined, // TODO: get this from somewhere... config?
972
+ legacyEnv: isLegacyEnv(args, config),
834
973
  experimentalPublic: args["experimental-public"] !== undefined,
835
974
  });
836
975
  }
@@ -839,12 +978,12 @@ export async function main(argv: string[]): Promise<void> {
839
978
  // tail
840
979
  wrangler.command(
841
980
  "tail [name]",
842
- "🦚 Starts a log tailing session for a deployed Worker.",
981
+ "🦚 Starts a log tailing session for a published Worker.",
843
982
  (yargs) => {
844
983
  return (
845
984
  yargs
846
985
  .positional("name", {
847
- describe: "name of the worker",
986
+ describe: "Name of the worker",
848
987
  type: "string",
849
988
  })
850
989
  // TODO: auto-detect if this should be json or pretty based on atty
@@ -856,6 +995,7 @@ export async function main(argv: string[]): Promise<void> {
856
995
  .option("status", {
857
996
  choices: ["ok", "error", "canceled"],
858
997
  describe: "Filter by invocation status",
998
+ array: true,
859
999
  })
860
1000
  .option("header", {
861
1001
  type: "string",
@@ -864,6 +1004,7 @@ export async function main(argv: string[]): Promise<void> {
864
1004
  .option("method", {
865
1005
  type: "string",
866
1006
  describe: "Filter by HTTP method",
1007
+ array: true,
867
1008
  })
868
1009
  .option("sampling-rate", {
869
1010
  type: "number",
@@ -873,28 +1014,33 @@ export async function main(argv: string[]): Promise<void> {
873
1014
  type: "string",
874
1015
  describe: "Filter by a text match in console.log messages",
875
1016
  })
1017
+ .option("ip", {
1018
+ type: "string",
1019
+ describe:
1020
+ 'Filter by the IP address the request originates from. Use "self" to filter for your own IP',
1021
+ array: true,
1022
+ })
876
1023
  .option("env", {
877
1024
  type: "string",
878
1025
  describe: "Perform on a specific environment",
879
1026
  })
1027
+ .option("debug", {
1028
+ type: "boolean",
1029
+ hidden: true,
1030
+ default: false,
1031
+ describe:
1032
+ "If a log would have been filtered out, send it through anyway alongside the filter which would have blocked it.",
1033
+ })
880
1034
  );
881
- // TODO: filter by client ip, which can be 'self' or an ip address
882
1035
  },
883
1036
  async (args) => {
884
- if (args.local) {
885
- throw new NotImplementedError(
886
- `local mode is not yet supported for this command`
887
- );
888
- }
1037
+ const config = readConfig(args.config as ConfigPath);
889
1038
 
890
- const config = args.config as Config;
1039
+ const scriptName = getScriptName(args, config);
891
1040
 
892
- if (!(args.name || config.name)) {
1041
+ if (!scriptName) {
893
1042
  throw new Error("Missing script name");
894
1043
  }
895
- const scriptName = `${args.name || config.name}${
896
- args.env ? `-${args.env}` : ""
897
- }`;
898
1044
 
899
1045
  // -- snip, extract --
900
1046
  const loggedIn = await loginOrRefreshIfRequired();
@@ -914,16 +1060,30 @@ export async function main(argv: string[]): Promise<void> {
914
1060
 
915
1061
  const accountId = config.account_id;
916
1062
 
917
- const filters = {
918
- status: args.status as "ok" | "error" | "canceled",
1063
+ const cliFilters: TailCLIFilters = {
1064
+ status: args.status as ("ok" | "error" | "canceled")[],
919
1065
  header: args.header,
920
1066
  method: args.method,
921
- "sampling-rate": args["sampling-rate"],
1067
+ samplingRate: args["sampling-rate"],
922
1068
  search: args.search,
1069
+ clientIp: args.ip,
923
1070
  };
924
1071
 
925
- const { tail, expiration, /* sendHeartbeat, */ deleteTail } =
926
- await createTail(accountId, scriptName, filters);
1072
+ const filters = translateCLICommandToFilterMessage(
1073
+ cliFilters,
1074
+ args.debug
1075
+ );
1076
+
1077
+ const { tail, expiration, deleteTail } = await createTail(
1078
+ accountId,
1079
+ scriptName,
1080
+ filters,
1081
+ !isLegacyEnv(args, config) ? args.env : undefined
1082
+ );
1083
+
1084
+ const scriptDisplayName = `${scriptName}${
1085
+ args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1086
+ }`;
927
1087
 
928
1088
  console.log(
929
1089
  `successfully created tail, expires at ${expiration.toLocaleString()}`
@@ -934,24 +1094,32 @@ export async function main(argv: string[]): Promise<void> {
934
1094
  await deleteTail();
935
1095
  });
936
1096
 
937
- tail.on("message", (data) => {
938
- console.log(JSON.stringify(JSON.parse(data.toString()), null, " "));
939
- });
1097
+ const printLog: (data: RawData) => void =
1098
+ args.format === "pretty" ? prettyPrintLogs : jsonPrintLogs;
1099
+
1100
+ tail.on("message", printLog);
940
1101
 
941
1102
  while (tail.readyState !== tail.OPEN) {
942
1103
  switch (tail.readyState) {
943
1104
  case tail.CONNECTING:
944
- await setTimeout(1000);
1105
+ await setTimeout(100);
945
1106
  break;
946
1107
  case tail.CLOSING:
947
- await setTimeout(1000);
1108
+ await setTimeout(100);
948
1109
  break;
949
1110
  case tail.CLOSED:
950
- process.exit(1);
1111
+ throw new Error(
1112
+ `Connection to ${scriptDisplayName} closed unexpectedly.`
1113
+ );
951
1114
  }
952
1115
  }
953
1116
 
954
- console.log(`Connected to ${scriptName}, waiting for logs...`);
1117
+ console.log(`Connected to ${scriptDisplayName}, waiting for logs...`);
1118
+
1119
+ tail.on("close", async () => {
1120
+ tail.terminate();
1121
+ await deleteTail();
1122
+ });
955
1123
  }
956
1124
  );
957
1125
 
@@ -962,14 +1130,12 @@ export async function main(argv: string[]): Promise<void> {
962
1130
  (yargs) => {
963
1131
  return yargs
964
1132
  .positional("method", {
1133
+ type: "string",
965
1134
  describe: "Type of request to preview your worker",
966
- choices: ["GET", "POST"],
967
- default: ["GET"],
968
1135
  })
969
1136
  .positional("body", {
970
1137
  type: "string",
971
1138
  describe: "Body string to post to your preview worker request.",
972
- default: "Null",
973
1139
  })
974
1140
  .option("env", {
975
1141
  type: "string",
@@ -981,11 +1147,104 @@ export async function main(argv: string[]): Promise<void> {
981
1147
  type: "boolean",
982
1148
  });
983
1149
  },
984
- () => {
985
- // "🔬 [DEPRECATED] Preview your code temporarily on https://cloudflareworkers.com"
986
- throw new DeprecationError(
987
- "`wrangler preview` has been deprecated, please refer to TODO://some/path for alternatives"
1150
+ async (args) => {
1151
+ if (args.method || args.body) {
1152
+ throw new DeprecationError(
1153
+ "The `wrangler preview` command has been deprecated.\n" +
1154
+ "Try using `wrangler dev` to to try out a worker during development.\n"
1155
+ );
1156
+ }
1157
+
1158
+ // Delegate to `wrangler dev`
1159
+ console.warn(
1160
+ "***************************************************\n" +
1161
+ "The `wrangler preview` command has been deprecated.\n" +
1162
+ "Attempting to run `wrangler dev` instead.\n" +
1163
+ "***************************************************\n"
1164
+ );
1165
+
1166
+ const config = readConfig(args.config as ConfigPath);
1167
+ const entry = getEntry(config, "dev");
1168
+
1169
+ // -- snip, extract --
1170
+ const loggedIn = await loginOrRefreshIfRequired();
1171
+ if (!loggedIn) {
1172
+ // didn't login, let's just quit
1173
+ console.log("Did not login, quitting...");
1174
+ return;
1175
+ }
1176
+
1177
+ if (!config.account_id) {
1178
+ config.account_id = await getAccountId();
1179
+ if (!config.account_id) {
1180
+ throw new Error("No account id found, quitting...");
1181
+ }
1182
+ }
1183
+ // -- snip, end --
1184
+
1185
+ const environments = config.env ?? {};
1186
+ const envRootObj = args.env ? environments[args.env] || {} : config;
1187
+
1188
+ const { waitUntilExit } = render(
1189
+ <Dev
1190
+ name={config.name}
1191
+ entry={entry}
1192
+ rules={getRules(config)}
1193
+ env={args.env}
1194
+ zone={undefined}
1195
+ legacyEnv={isLegacyEnv(args, config)}
1196
+ buildCommand={config.build || {}}
1197
+ format={config.build?.upload?.format}
1198
+ initialMode={args.local ? "local" : "remote"}
1199
+ jsxFactory={envRootObj?.jsx_factory}
1200
+ jsxFragment={envRootObj?.jsx_fragment}
1201
+ enableLocalPersistence={false}
1202
+ accountId={config.account_id}
1203
+ assetPaths={undefined}
1204
+ port={config.dev?.port}
1205
+ public={undefined}
1206
+ compatibilityDate={
1207
+ config.compatibility_date ||
1208
+ new Date().toISOString().substring(0, 10)
1209
+ }
1210
+ compatibilityFlags={
1211
+ (args["compatibility-flags"] as string[]) ||
1212
+ config.compatibility_flags
1213
+ }
1214
+ usageModel={config.usage_model}
1215
+ bindings={{
1216
+ kv_namespaces: envRootObj.kv_namespaces?.map(
1217
+ ({ binding, preview_id, id: _id }) => {
1218
+ // In `dev`, we make folks use a separate kv namespace called
1219
+ // `preview_id` instead of `id` so that they don't
1220
+ // break production data. So here we check that a `preview_id`
1221
+ // has actually been configured.
1222
+ // This whole block of code will be obsoleted in the future
1223
+ // when we have copy-on-write for previews on edge workers.
1224
+ if (!preview_id) {
1225
+ // TODO: This error has to be a _lot_ better, ideally just asking
1226
+ // to create a preview namespace for the user automatically
1227
+ throw new Error(
1228
+ `In development, you should use a separate kv namespace than the one you'd use in production. Please create a new kv namespace with "wrangler kv:namespace create <name> --preview" and add its id as preview_id to the kv_namespace "${binding}" in your wrangler.toml`
1229
+ ); // Ugh, I really don't like this message very much
1230
+ }
1231
+ return {
1232
+ binding,
1233
+ id: preview_id,
1234
+ };
1235
+ }
1236
+ ),
1237
+ vars: envRootObj.vars,
1238
+ wasm_modules: config.wasm_modules,
1239
+ text_blobs: config.text_blobs,
1240
+ durable_objects: envRootObj.durable_objects,
1241
+ r2_buckets: envRootObj.r2_buckets,
1242
+ unsafe: envRootObj.unsafe?.bindings,
1243
+ }}
1244
+ inspectorPort={await getPort({ port: 9229 })}
1245
+ />
988
1246
  );
1247
+ await waitUntilExit();
989
1248
  }
990
1249
  );
991
1250
 
@@ -998,7 +1257,7 @@ export async function main(argv: string[]): Promise<void> {
998
1257
  return routeYargs
999
1258
  .command(
1000
1259
  "list",
1001
- "List a route associated with a zone",
1260
+ "List the routes associated with a zone",
1002
1261
  (yargs) => {
1003
1262
  return yargs
1004
1263
  .option("env", {
@@ -1007,17 +1266,19 @@ export async function main(argv: string[]): Promise<void> {
1007
1266
  })
1008
1267
  .option("zone", {
1009
1268
  type: "string",
1010
- describe: "zone id",
1269
+ describe: "Zone id",
1011
1270
  })
1012
1271
  .positional("zone", {
1013
- describe: "zone id",
1272
+ describe: "Zone id",
1014
1273
  type: "string",
1015
1274
  });
1016
1275
  },
1017
1276
  async (args) => {
1018
1277
  console.log(":route list", args);
1019
1278
  // TODO: use environment (current wrangler doesn't do so?)
1020
- const zone = args.zone || (args.config as Config).zone_id;
1279
+ // TODO: don't get `zone_id` from config at all and require the command line arg `--zone`.
1280
+ const config = readConfig(args.config as ConfigPath);
1281
+ const zone = args.zone || config.zone_id;
1021
1282
  if (!zone) {
1022
1283
  throw new Error("missing zone id");
1023
1284
  }
@@ -1046,7 +1307,8 @@ export async function main(argv: string[]): Promise<void> {
1046
1307
  async (args) => {
1047
1308
  console.log(":route delete", args);
1048
1309
  // TODO: use environment (current wrangler doesn't do so?)
1049
- const zone = args.zone || (args.config as Config).zone_id;
1310
+ const config = readConfig(args.config as ConfigPath);
1311
+ const zone = args.zone || config.zone_id;
1050
1312
  if (!zone) {
1051
1313
  throw new Error("missing zone id");
1052
1314
  }
@@ -1088,83 +1350,82 @@ export async function main(argv: string[]): Promise<void> {
1088
1350
  (yargs) => {
1089
1351
  return yargs
1090
1352
  .positional("key", {
1091
- describe: "The variable name to be accessible in the script.",
1353
+ describe: "The variable name to be accessible in the script",
1092
1354
  type: "string",
1093
1355
  })
1094
1356
  .option("name", {
1095
- describe: "name of the script",
1357
+ describe: "Name of the worker",
1096
1358
  type: "string",
1097
1359
  })
1098
1360
  .option("env", {
1099
1361
  type: "string",
1100
1362
  describe:
1101
- "Binds the secret to the script of the specific environment.",
1363
+ "Binds the secret to the Worker of the specific environment",
1102
1364
  });
1103
1365
  },
1104
1366
  async (args) => {
1105
- const config = args.config as Config;
1367
+ const config = readConfig(args.config as ConfigPath);
1106
1368
 
1107
- // TODO: use environment (how does current wrangler do it?)
1108
- const scriptName = args.name || config.name;
1369
+ const scriptName = getScriptName(args, config);
1109
1370
  if (!scriptName) {
1110
1371
  throw new Error("Missing script name");
1111
1372
  }
1112
1373
 
1113
- if (args.local) {
1114
- console.warn("`wrangler secret put` is a no-op in --local mode");
1374
+ // -- snip, extract --
1375
+ const loggedIn = await loginOrRefreshIfRequired();
1376
+ if (!loggedIn) {
1377
+ // didn't login, let's just quit
1378
+ console.log("Did not login, quitting...");
1379
+ return;
1115
1380
  }
1116
1381
 
1117
- if (!args.local) {
1118
- // -- snip, extract --
1119
- const loggedIn = await loginOrRefreshIfRequired();
1120
- if (!loggedIn) {
1121
- // didn't login, let's just quit
1122
- console.log("Did not login, quitting...");
1123
- return;
1124
- }
1125
-
1382
+ if (!config.account_id) {
1383
+ config.account_id = await getAccountId();
1126
1384
  if (!config.account_id) {
1127
- config.account_id = await getAccountId();
1128
- if (!config.account_id) {
1129
- throw new Error("No account id found, quitting...");
1130
- }
1385
+ throw new Error("No account id found, quitting...");
1131
1386
  }
1132
- // -- snip, end --
1133
1387
  }
1388
+ // -- snip, end --
1134
1389
 
1135
1390
  const secretValue = await prompt(
1136
1391
  "Enter a secret value:",
1137
1392
  "password"
1138
1393
  );
1139
1394
 
1140
- if (args.local) {
1141
- return;
1142
- }
1143
-
1144
- console.log(`🌀 Creating the secret for script ${scriptName}`);
1395
+ console.log(
1396
+ `🌀 Creating the secret for script ${scriptName} ${
1397
+ args.env && !isLegacyEnv(args, config) ? `(${args.env})` : ""
1398
+ }`
1399
+ );
1145
1400
 
1146
1401
  async function submitSecret() {
1147
- return await fetchResult(
1148
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/`,
1149
- {
1150
- method: "PUT",
1151
- headers: { "Content-Type": "application/json" },
1152
- body: JSON.stringify({
1153
- name: args.key,
1154
- text: secretValue,
1155
- type: "secret_text",
1156
- }),
1157
- }
1158
- );
1402
+ const url =
1403
+ !args.env || isLegacyEnv(args, config)
1404
+ ? `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets`
1405
+ : `/accounts/${config.account_id}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1406
+
1407
+ return await fetchResult(url, {
1408
+ method: "PUT",
1409
+ headers: { "Content-Type": "application/json" },
1410
+ body: JSON.stringify({
1411
+ name: args.key,
1412
+ text: secretValue,
1413
+ type: "secret_text",
1414
+ }),
1415
+ });
1159
1416
  }
1160
1417
 
1161
1418
  try {
1162
1419
  await submitSecret();
1163
1420
  } catch (e) {
1421
+ // @ts-expect-error non-standard property on Error
1164
1422
  if (e.code === 10007) {
1165
1423
  // upload a draft worker
1424
+ // TODO: log a warning
1166
1425
  await fetchResult(
1167
- `/accounts/${config.account_id}/workers/scripts/${scriptName}`,
1426
+ !args["legacy-env"] && args.env
1427
+ ? `/accounts/${config.account_id}/workers/services/${scriptName}/environments/${args.env}`
1428
+ : `/accounts/${config.account_id}/workers/scripts/${scriptName}`,
1168
1429
  {
1169
1430
  method: "PUT",
1170
1431
  body: toFormData({
@@ -1178,6 +1439,10 @@ export async function main(argv: string[]): Promise<void> {
1178
1439
  kv_namespaces: [],
1179
1440
  vars: {},
1180
1441
  durable_objects: { bindings: [] },
1442
+ r2_buckets: [],
1443
+ wasm_modules: {},
1444
+ text_blobs: {},
1445
+ unsafe: [],
1181
1446
  },
1182
1447
  modules: [],
1183
1448
  migrations: undefined,
@@ -1202,69 +1467,64 @@ export async function main(argv: string[]): Promise<void> {
1202
1467
  (yargs) => {
1203
1468
  return yargs
1204
1469
  .positional("key", {
1205
- describe: "The variable name to be accessible in the script.",
1470
+ describe: "The variable name to be accessible in the script",
1206
1471
  type: "string",
1207
1472
  })
1208
1473
  .option("name", {
1209
- describe: "name of the script",
1474
+ describe: "Name of the worker",
1210
1475
  type: "string",
1211
1476
  })
1212
1477
  .option("env", {
1213
1478
  type: "string",
1214
1479
  describe:
1215
- "Binds the secret to the script of the specific environment.",
1480
+ "Binds the secret to the Worker of the specific environment",
1216
1481
  });
1217
1482
  },
1218
1483
  async (args) => {
1219
- const config = args.config as Config;
1484
+ const config = readConfig(args.config as ConfigPath);
1220
1485
 
1221
- // TODO: use environment (how does current wrangler do it?)
1222
- const scriptName = args.name || config.name;
1486
+ const scriptName = getScriptName(args, config);
1223
1487
  if (!scriptName) {
1224
1488
  throw new Error("Missing script name");
1225
1489
  }
1226
1490
 
1227
- if (args.local) {
1228
- console.warn(
1229
- "`wrangler secret delete` is a no-op in --local mode"
1230
- );
1491
+ // -- snip, extract --
1492
+ const loggedIn = await loginOrRefreshIfRequired();
1493
+ if (!loggedIn) {
1494
+ // didn't login, let's just quit
1495
+ console.log("Did not login, quitting...");
1496
+ return;
1231
1497
  }
1232
1498
 
1233
- if (!args.local) {
1234
- // -- snip, extract --
1235
- const loggedIn = await loginOrRefreshIfRequired();
1236
- if (!loggedIn) {
1237
- // didn't login, let's just quit
1238
- console.log("Did not login, quitting...");
1239
- return;
1240
- }
1241
-
1499
+ if (!config.account_id) {
1500
+ config.account_id = await getAccountId();
1242
1501
  if (!config.account_id) {
1243
- config.account_id = await getAccountId();
1244
- if (!config.account_id) {
1245
- throw new Error("No account id found, quitting...");
1246
- }
1502
+ throw new Error("No account id found, quitting...");
1247
1503
  }
1248
- // -- snip, end --
1249
1504
  }
1505
+ // -- snip, end --
1250
1506
 
1251
1507
  if (
1252
1508
  await confirm(
1253
- `Are you sure you want to permanently delete the variable ${args.key} on the script ${scriptName}?`
1509
+ `Are you sure you want to permanently delete the variable ${
1510
+ args.key
1511
+ } on the script ${scriptName}${
1512
+ args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1513
+ }?`
1254
1514
  )
1255
1515
  ) {
1256
1516
  console.log(
1257
- `🌀 Deleting the secret ${args.key} on script ${scriptName}.`
1517
+ `🌀 Deleting the secret ${args.key} on script ${scriptName}${
1518
+ args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1519
+ }`
1258
1520
  );
1259
1521
 
1260
- if (args.local) {
1261
- return;
1262
- }
1522
+ const url =
1523
+ !args.env || isLegacyEnv(args, config)
1524
+ ? `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets`
1525
+ : `/accounts/${config.account_id}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1263
1526
 
1264
- await fetchResult(
1265
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/${args.key}`,
1266
- { method: "DELETE" }
1267
- );
1527
+ await fetchResult(`${url}/${args.key}`, { method: "DELETE" });
1268
1528
  console.log(`✨ Success! Deleted secret ${args.key}`);
1269
1529
  }
1270
1530
  }
@@ -1275,59 +1535,45 @@ export async function main(argv: string[]): Promise<void> {
1275
1535
  (yargs) => {
1276
1536
  return yargs
1277
1537
  .option("name", {
1278
- describe: "name of the script",
1538
+ describe: "Name of the worker",
1279
1539
  type: "string",
1280
1540
  })
1281
1541
  .option("env", {
1282
1542
  type: "string",
1283
1543
  describe:
1284
- "Binds the secret to the script of the specific environment.",
1544
+ "Binds the secret to the Worker of the specific environment.",
1285
1545
  });
1286
1546
  },
1287
1547
  async (args) => {
1288
- const config = args.config as Config;
1548
+ const config = readConfig(args.config as ConfigPath);
1289
1549
 
1290
- // TODO: use environment (how does current wrangler do it?)
1291
- const scriptName = args.name || config.name;
1550
+ const scriptName = getScriptName(args, config);
1292
1551
  if (!scriptName) {
1293
1552
  throw new Error("Missing script name");
1294
1553
  }
1295
1554
 
1296
- if (args.local) {
1297
- console.warn("`wrangler secret list` is a no-op in --local mode");
1555
+ // -- snip, extract --
1556
+ const loggedIn = await loginOrRefreshIfRequired();
1557
+ if (!loggedIn) {
1558
+ // didn't login, let's just quit
1559
+ console.log("Did not login, quitting...");
1560
+ return;
1298
1561
  }
1299
1562
 
1300
- if (!args.local) {
1301
- // -- snip, extract --
1302
- const loggedIn = await loginOrRefreshIfRequired();
1303
- if (!loggedIn) {
1304
- // didn't login, let's just quit
1305
- console.log("Did not login, quitting...");
1306
- return;
1307
- }
1308
-
1563
+ if (!config.account_id) {
1564
+ config.account_id = await getAccountId();
1309
1565
  if (!config.account_id) {
1310
- config.account_id = await getAccountId();
1311
- if (!config.account_id) {
1312
- throw new Error("No account id found, quitting...");
1313
- }
1566
+ throw new Error("No account id found, quitting...");
1314
1567
  }
1315
- // -- snip, end --
1316
1568
  }
1569
+ // -- snip, end --
1317
1570
 
1318
- if (args.local) {
1319
- return;
1320
- }
1571
+ const url =
1572
+ !args.env || isLegacyEnv(args, config)
1573
+ ? `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets`
1574
+ : `/accounts/${config.account_id}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1321
1575
 
1322
- console.log(
1323
- JSON.stringify(
1324
- await fetchResult(
1325
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets`
1326
- ),
1327
- null,
1328
- " "
1329
- )
1330
- );
1576
+ console.log(JSON.stringify(await fetchResult(url), null, " "));
1331
1577
  }
1332
1578
  );
1333
1579
  }
@@ -1373,7 +1619,7 @@ export async function main(argv: string[]): Promise<void> {
1373
1619
  );
1374
1620
  }
1375
1621
 
1376
- const config = args.config as Config;
1622
+ const config = readConfig(args.config as ConfigPath);
1377
1623
  if (!config.name) {
1378
1624
  console.warn(
1379
1625
  "No configured name present, using `worker` as a prefix for the title"
@@ -1385,52 +1631,38 @@ export async function main(argv: string[]): Promise<void> {
1385
1631
  const preview = args.preview ? "_preview" : "";
1386
1632
  const title = `${name}${environment}-${args.namespace}${preview}`;
1387
1633
 
1388
- if (args.local) {
1389
- const { Miniflare } = await import("miniflare");
1390
- const mf = new Miniflare({
1391
- kvPersist: (args.kvPersist as string) || true,
1392
- // TODO: these options shouldn't be required
1393
- script: ` `, // has to be a string with at least one char
1394
- });
1395
- await mf.getKVNamespace(title); // this should "create" the namespace
1396
- console.log(`✨ Success! Created KV namespace ${title}`);
1397
- } else {
1398
- // -- snip, extract --
1399
- const loggedIn = await loginOrRefreshIfRequired();
1400
- if (!loggedIn) {
1401
- // didn't login, let's just quit
1402
- console.log("Did not login, quitting...");
1403
- return;
1404
- }
1634
+ // -- snip, extract --
1635
+ const loggedIn = await loginOrRefreshIfRequired();
1636
+ if (!loggedIn) {
1637
+ // didn't login, let's just quit
1638
+ console.log("Did not login, quitting...");
1639
+ return;
1640
+ }
1405
1641
 
1642
+ if (!config.account_id) {
1643
+ config.account_id = await getAccountId();
1406
1644
  if (!config.account_id) {
1407
- config.account_id = await getAccountId();
1408
- if (!config.account_id) {
1409
- throw new Error("No account id found, quitting...");
1410
- }
1645
+ throw new Error("No account id found, quitting...");
1411
1646
  }
1412
- // -- snip, end --
1647
+ }
1648
+ // -- snip, end --
1413
1649
 
1414
- // TODO: generate a binding name stripping non alphanumeric chars
1650
+ // TODO: generate a binding name stripping non alphanumeric chars
1415
1651
 
1416
- console.log(`🌀 Creating namespace with title "${title}"`);
1417
- const namespaceId = await createNamespace(
1418
- config.account_id,
1419
- title
1420
- );
1652
+ console.log(`🌀 Creating namespace with title "${title}"`);
1653
+ const namespaceId = await createNamespace(config.account_id, title);
1421
1654
 
1422
- console.log("✨ Success!");
1423
- const envString = args.env ? ` under [env.${args.env}]` : "";
1424
- const previewString = args.preview ? "preview_" : "";
1425
- console.log(
1426
- `Add the following to your configuration file in your kv_namespaces array${envString}:`
1427
- );
1428
- console.log(
1429
- `{ binding = "${args.namespace}", ${previewString}id = "${namespaceId}" }`
1430
- );
1655
+ console.log("✨ Success!");
1656
+ const envString = args.env ? ` under [env.${args.env}]` : "";
1657
+ const previewString = args.preview ? "preview_" : "";
1658
+ console.log(
1659
+ `Add the following to your configuration file in your kv_namespaces array${envString}:`
1660
+ );
1661
+ console.log(
1662
+ `{ binding = "${args.namespace}", ${previewString}id = "${namespaceId}" }`
1663
+ );
1431
1664
 
1432
- // TODO: automatically write this block to the wrangler.toml config file??
1433
- }
1665
+ // TODO: automatically write this block to the wrangler.toml config file??
1434
1666
  }
1435
1667
  )
1436
1668
  .command(
@@ -1438,39 +1670,33 @@ export async function main(argv: string[]): Promise<void> {
1438
1670
  "Outputs a list of all KV namespaces associated with your account id.",
1439
1671
  {},
1440
1672
  async (args) => {
1441
- const config = args.config as Config;
1673
+ const config = readConfig(args.config as ConfigPath);
1442
1674
 
1443
- if (args.local) {
1444
- throw new NotImplementedError(
1445
- `local mode is not yet supported for this command`
1446
- );
1447
- } else {
1448
- // -- snip, extract --
1449
- const loggedIn = await loginOrRefreshIfRequired();
1450
- if (!loggedIn) {
1451
- // didn't login, let's just quit
1452
- console.log("Did not login, quitting...");
1453
- return;
1454
- }
1675
+ // -- snip, extract --
1676
+ const loggedIn = await loginOrRefreshIfRequired();
1677
+ if (!loggedIn) {
1678
+ // didn't login, let's just quit
1679
+ console.log("Did not login, quitting...");
1680
+ return;
1681
+ }
1455
1682
 
1683
+ if (!config.account_id) {
1684
+ config.account_id = await getAccountId();
1456
1685
  if (!config.account_id) {
1457
- config.account_id = await getAccountId();
1458
- if (!config.account_id) {
1459
- throw new Error("No account id found, quitting...");
1460
- }
1686
+ throw new Error("No account id found, quitting...");
1461
1687
  }
1462
- // -- snip, end --
1688
+ }
1689
+ // -- snip, end --
1463
1690
 
1464
- // TODO: we should show bindings if they exist for given ids
1691
+ // TODO: we should show bindings if they exist for given ids
1465
1692
 
1466
- console.log(
1467
- JSON.stringify(
1468
- await listNamespaces(config.account_id),
1469
- null,
1470
- " "
1471
- )
1472
- );
1473
- }
1693
+ console.log(
1694
+ JSON.stringify(
1695
+ await listNamespaces(config.account_id),
1696
+ null,
1697
+ " "
1698
+ )
1699
+ );
1474
1700
  }
1475
1701
  )
1476
1702
  .command(
@@ -1497,61 +1723,55 @@ export async function main(argv: string[]): Promise<void> {
1497
1723
  });
1498
1724
  },
1499
1725
  async (args) => {
1500
- const config = args.config as Config;
1726
+ const config = readConfig(args.config as ConfigPath);
1501
1727
 
1502
- if (args.local) {
1503
- throw new NotImplementedError(
1504
- `local mode is not yet supported for this command`
1728
+ let id;
1729
+ try {
1730
+ id = getNamespaceId(args, config);
1731
+ } catch (e) {
1732
+ throw new CommandLineArgsError(
1733
+ "Not able to delete namespace.\n" + ((e as Error).message ?? e)
1505
1734
  );
1506
- } else {
1507
- let id;
1508
- try {
1509
- id = getNamespaceId(args);
1510
- } catch (e) {
1511
- throw new CommandLineArgsError(
1512
- "Not able to delete namespace.\n" + e.message
1513
- );
1514
- }
1735
+ }
1515
1736
 
1516
- // -- snip, extract --
1517
- const loggedIn = await loginOrRefreshIfRequired();
1518
- if (!loggedIn) {
1519
- // didn't login, let's just quit
1520
- console.log("Did not login, quitting...");
1521
- return;
1522
- }
1737
+ // -- snip, extract --
1738
+ const loggedIn = await loginOrRefreshIfRequired();
1739
+ if (!loggedIn) {
1740
+ // didn't login, let's just quit
1741
+ console.log("Did not login, quitting...");
1742
+ return;
1743
+ }
1523
1744
 
1745
+ if (!config.account_id) {
1746
+ config.account_id = await getAccountId();
1524
1747
  if (!config.account_id) {
1525
- config.account_id = await getAccountId();
1526
- if (!config.account_id) {
1527
- throw new Error("No account id found, quitting...");
1528
- }
1748
+ throw new Error("No account id found, quitting...");
1529
1749
  }
1530
- // -- snip, end --
1750
+ }
1751
+ // -- snip, end --
1531
1752
 
1532
- await fetchResult<{ id: string }>(
1533
- `/accounts/${config.account_id}/storage/kv/namespaces/${id}`,
1534
- { method: "DELETE" }
1535
- );
1753
+ await fetchResult<{ id: string }>(
1754
+ `/accounts/${config.account_id}/storage/kv/namespaces/${id}`,
1755
+ { method: "DELETE" }
1756
+ );
1536
1757
 
1537
- // TODO: recommend they remove it from wrangler.toml
1758
+ // TODO: recommend they remove it from wrangler.toml
1538
1759
 
1539
- // test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1540
- // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1541
- // n
1542
- // 💁 Not deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1543
- // ➜ test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1544
- // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1545
- // y
1546
- // 🌀 Deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1547
- // ✨ Success
1548
- // ⚠️ Make sure to remove this "kv-namespace" entry from your configuration file!
1549
- // ➜ test-mf
1760
+ // test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1761
+ // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1762
+ // n
1763
+ // 💁 Not deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1764
+ // ➜ test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1765
+ // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1766
+ // y
1767
+ // 🌀 Deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1768
+ // ✨ Success
1769
+ // ⚠️ Make sure to remove this "kv-namespace" entry from your configuration file!
1770
+ // ➜ test-mf
1550
1771
 
1551
- // TODO: do it automatically
1772
+ // TODO: do it automatically
1552
1773
 
1553
- // TODO: delete the preview namespace as well?
1554
- }
1774
+ // TODO: delete the preview namespace as well?
1555
1775
  }
1556
1776
  );
1557
1777
  }
@@ -1570,20 +1790,20 @@ export async function main(argv: string[]): Promise<void> {
1570
1790
  return yargs
1571
1791
  .positional("key", {
1572
1792
  type: "string",
1573
- describe: "The key to write to.",
1793
+ describe: "The key to write to",
1574
1794
  demandOption: true,
1575
1795
  })
1576
1796
  .positional("value", {
1577
1797
  type: "string",
1578
- describe: "The value to write.",
1798
+ describe: "The value to write",
1579
1799
  })
1580
1800
  .option("binding", {
1581
1801
  type: "string",
1582
- describe: "The binding of the namespace to write to.",
1802
+ describe: "The binding of the namespace to write to",
1583
1803
  })
1584
1804
  .option("namespace-id", {
1585
1805
  type: "string",
1586
- describe: "The id of the namespace to write to.",
1806
+ describe: "The id of the namespace to write to",
1587
1807
  })
1588
1808
  .check(demandOneOfOption("binding", "namespace-id"))
1589
1809
  .option("env", {
@@ -1596,7 +1816,7 @@ export async function main(argv: string[]): Promise<void> {
1596
1816
  })
1597
1817
  .option("ttl", {
1598
1818
  type: "number",
1599
- describe: "Time for which the entries should be visible.",
1819
+ describe: "Time for which the entries should be visible",
1600
1820
  })
1601
1821
  .option("expiration", {
1602
1822
  type: "number",
@@ -1605,18 +1825,18 @@ export async function main(argv: string[]): Promise<void> {
1605
1825
  })
1606
1826
  .option("path", {
1607
1827
  type: "string",
1608
- describe: "Read value from the file at a given path.",
1828
+ describe: "Read value from the file at a given path",
1609
1829
  })
1610
1830
  .check(demandOneOfOption("value", "path"));
1611
1831
  },
1612
1832
  async ({ key, ttl, expiration, ...args }) => {
1613
- const namespaceId = getNamespaceId(args);
1833
+ const config = readConfig(args.config as ConfigPath);
1834
+ const namespaceId = getNamespaceId(args, config);
1614
1835
  // One of `args.path` and `args.value` must be defined
1615
1836
  const value = args.path
1616
- ? await readFile(args.path, "utf-8")
1837
+ ? readFileSync(args.path)
1617
1838
  : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1618
1839
  args.value!;
1619
- const config = args.config as Config;
1620
1840
 
1621
1841
  if (args.path) {
1622
1842
  console.log(
@@ -1628,37 +1848,26 @@ export async function main(argv: string[]): Promise<void> {
1628
1848
  );
1629
1849
  }
1630
1850
 
1631
- if (args.local) {
1632
- const { Miniflare } = await import("miniflare");
1633
- const mf = new Miniflare({
1634
- kvPersist: (args.kvPersist as string) || true,
1635
- // TODO: these options shouldn't be required
1636
- script: ` `, // has to be a string with at least one char
1637
- });
1638
- const ns = await mf.getKVNamespace(namespaceId);
1639
- await ns.put(key, value, { expiration, expirationTtl: ttl });
1640
- } else {
1641
- // -- snip, extract --
1642
- const loggedIn = await loginOrRefreshIfRequired();
1643
- if (!loggedIn) {
1644
- // didn't login, let's just quit
1645
- console.log("Did not login, quitting...");
1646
- return;
1647
- }
1851
+ // -- snip, extract --
1852
+ const loggedIn = await loginOrRefreshIfRequired();
1853
+ if (!loggedIn) {
1854
+ // didn't login, let's just quit
1855
+ console.log("Did not login, quitting...");
1856
+ return;
1857
+ }
1648
1858
 
1859
+ if (!config.account_id) {
1860
+ config.account_id = await getAccountId();
1649
1861
  if (!config.account_id) {
1650
- config.account_id = await getAccountId();
1651
- if (!config.account_id) {
1652
- throw new Error("No account id found, quitting...");
1653
- }
1862
+ throw new Error("No account id found, quitting...");
1654
1863
  }
1655
- // -- snip, end --
1656
-
1657
- await putKeyValue(config.account_id, namespaceId, key, value, {
1658
- expiration,
1659
- expiration_ttl: ttl,
1660
- });
1661
1864
  }
1865
+ // -- snip, end --
1866
+
1867
+ await putKeyValue(config.account_id, namespaceId, key, value, {
1868
+ expiration,
1869
+ expiration_ttl: ttl,
1870
+ });
1662
1871
  }
1663
1872
  )
1664
1873
  .command(
@@ -1692,44 +1901,31 @@ export async function main(argv: string[]): Promise<void> {
1692
1901
  },
1693
1902
  async ({ prefix, ...args }) => {
1694
1903
  // TODO: support for limit+cursor (pagination)
1904
+ const config = readConfig(args.config as ConfigPath);
1905
+ const namespaceId = getNamespaceId(args, config);
1906
+
1907
+ // -- snip, extract --
1908
+ const loggedIn = await loginOrRefreshIfRequired();
1909
+ if (!loggedIn) {
1910
+ // didn't login, let's just quit
1911
+ console.log("Did not login, quitting...");
1912
+ return;
1913
+ }
1695
1914
 
1696
- const namespaceId = getNamespaceId(args);
1697
- const config = args.config as Config;
1698
-
1699
- if (args.local) {
1700
- const { Miniflare } = await import("miniflare");
1701
- const mf = new Miniflare({
1702
- kvPersist: (args.kvPersist as string) || true,
1703
- // TODO: these options shouldn't be required
1704
- script: ` `, // has to be a string with at least one char
1705
- });
1706
- const ns = await mf.getKVNamespace(namespaceId);
1707
- const listResponse = await ns.list({ prefix });
1708
- console.log(JSON.stringify(listResponse.keys, null, " ")); // TODO: paginate, collate
1709
- } else {
1710
- // -- snip, extract --
1711
- const loggedIn = await loginOrRefreshIfRequired();
1712
- if (!loggedIn) {
1713
- // didn't login, let's just quit
1714
- console.log("Did not login, quitting...");
1715
- return;
1716
- }
1717
-
1915
+ if (!config.account_id) {
1916
+ config.account_id = await getAccountId();
1718
1917
  if (!config.account_id) {
1719
- config.account_id = await getAccountId();
1720
- if (!config.account_id) {
1721
- throw new Error("No account id found, quitting...");
1722
- }
1918
+ throw new Error("No account id found, quitting...");
1723
1919
  }
1724
- // -- snip, end --
1725
-
1726
- const results = await listNamespaceKeys(
1727
- config.account_id,
1728
- namespaceId,
1729
- prefix
1730
- );
1731
- console.log(JSON.stringify(results, undefined, 2));
1732
1920
  }
1921
+ // -- snip, end --
1922
+
1923
+ const results = await listNamespaceKeys(
1924
+ config.account_id,
1925
+ namespaceId,
1926
+ prefix
1927
+ );
1928
+ console.log(JSON.stringify(results, undefined, 2));
1733
1929
  }
1734
1930
  )
1735
1931
  .command(
@@ -1767,42 +1963,26 @@ export async function main(argv: string[]): Promise<void> {
1767
1963
  });
1768
1964
  },
1769
1965
  async ({ key, ...args }) => {
1770
- const namespaceId = getNamespaceId(args);
1771
- const config = args.config as Config;
1772
-
1773
- if (args.local) {
1774
- const { Miniflare } = await import("miniflare");
1775
- const mf = new Miniflare({
1776
- kvPersist: (args.kvPersist as string) || true,
1777
- // TODO: these options shouldn't be required
1778
- script: ` `, // has to be a string with at least one char
1779
- });
1780
- const ns = await mf.getKVNamespace(namespaceId);
1781
- console.log(await ns.get(key));
1966
+ const config = readConfig(args.config as ConfigPath);
1967
+ const namespaceId = getNamespaceId(args, config);
1968
+
1969
+ // -- snip, extract --
1970
+ const loggedIn = await loginOrRefreshIfRequired();
1971
+ if (!loggedIn) {
1972
+ // didn't login, let's just quit
1973
+ console.log("Did not login, quitting...");
1782
1974
  return;
1783
1975
  }
1784
1976
 
1785
- if (!args.local) {
1786
- // -- snip, extract --
1787
- const loggedIn = await loginOrRefreshIfRequired();
1788
- if (!loggedIn) {
1789
- // didn't login, let's just quit
1790
- console.log("Did not login, quitting...");
1791
- return;
1792
- }
1793
-
1977
+ if (!config.account_id) {
1978
+ config.account_id = await getAccountId();
1794
1979
  if (!config.account_id) {
1795
- config.account_id = await getAccountId();
1796
- if (!config.account_id) {
1797
- throw new Error("No account id found, quitting...");
1798
- }
1980
+ throw new Error("No account id found, quitting...");
1799
1981
  }
1800
- // -- snip, end --
1801
-
1802
- console.log(
1803
- await getKeyValue(config.account_id, namespaceId, key)
1804
- );
1805
1982
  }
1983
+ // -- snip, end --
1984
+
1985
+ console.log(await getKeyValue(config.account_id, namespaceId, key));
1806
1986
  }
1807
1987
  )
1808
1988
  .command(
@@ -1834,43 +2014,28 @@ export async function main(argv: string[]): Promise<void> {
1834
2014
  });
1835
2015
  },
1836
2016
  async ({ key, ...args }) => {
1837
- const namespaceId = getNamespaceId(args);
2017
+ const config = readConfig(args.config as ConfigPath);
2018
+ const namespaceId = getNamespaceId(args, config);
1838
2019
 
1839
2020
  console.log(
1840
2021
  `deleting the key "${key}" on namespace ${namespaceId}`
1841
2022
  );
1842
2023
 
1843
- if (args.local) {
1844
- const { Miniflare } = await import("miniflare");
1845
- const mf = new Miniflare({
1846
- kvPersist: (args.kvPersist as string) || true,
1847
- // TODO: these options shouldn't be required
1848
- script: ` `, // has to be a string with at least one char
1849
- });
1850
- const ns = await mf.getKVNamespace(namespaceId);
1851
- console.log(await ns.delete(key));
2024
+ // -- snip, extract --
2025
+ const loggedIn = await loginOrRefreshIfRequired();
2026
+ if (!loggedIn) {
2027
+ // didn't login, let's just quit
2028
+ console.log("Did not login, quitting...");
1852
2029
  return;
1853
2030
  }
1854
2031
 
1855
- const config = args.config as Config;
1856
-
1857
- if (!args.local) {
1858
- // -- snip, extract --
1859
- const loggedIn = await loginOrRefreshIfRequired();
1860
- if (!loggedIn) {
1861
- // didn't login, let's just quit
1862
- console.log("Did not login, quitting...");
1863
- return;
1864
- }
1865
-
2032
+ if (!config.account_id) {
2033
+ config.account_id = await getAccountId();
1866
2034
  if (!config.account_id) {
1867
- config.account_id = await getAccountId();
1868
- if (!config.account_id) {
1869
- throw new Error("No account id found, quitting...");
1870
- }
2035
+ throw new Error("No account id found, quitting...");
1871
2036
  }
1872
- // -- snip, end --
1873
2037
  }
2038
+ // -- snip, end --
1874
2039
 
1875
2040
  await fetchResult(
1876
2041
  `/accounts/${config.account_id}/storage/kv/namespaces/${namespaceId}/values/${key}`,
@@ -1899,11 +2064,11 @@ export async function main(argv: string[]): Promise<void> {
1899
2064
  })
1900
2065
  .option("binding", {
1901
2066
  type: "string",
1902
- describe: "The name of the namespace to put to",
2067
+ describe: "The name of the namespace to insert values into",
1903
2068
  })
1904
2069
  .option("namespace-id", {
1905
2070
  type: "string",
1906
- describe: "The id of the namespace to put to",
2071
+ describe: "The id of the namespace to insert values into",
1907
2072
  })
1908
2073
  .check(demandOneOfOption("binding", "namespace-id"))
1909
2074
  .option("env", {
@@ -1920,58 +2085,29 @@ export async function main(argv: string[]): Promise<void> {
1920
2085
  // This could be made more efficient with a streaming parser/uploader
1921
2086
  // but we'll do that in the future if needed.
1922
2087
 
1923
- const namespaceId = getNamespaceId(args);
1924
- const config = args.config as Config;
1925
- const content = await readFile(filename, "utf-8");
1926
- let parsedContent;
1927
- try {
1928
- parsedContent = JSON.parse(content);
1929
- } catch (err) {
1930
- throw new Error(
1931
- `Could not parse json from ${filename}.\n${err.message ?? err}`
1932
- );
1933
- }
2088
+ const config = readConfig(args.config as ConfigPath);
2089
+ const namespaceId = getNamespaceId(args, config);
2090
+ const content = parseJSON(readFileSync(filename), filename);
1934
2091
 
1935
- if (args.local) {
1936
- const { Miniflare } = await import("miniflare");
1937
- const mf = new Miniflare({
1938
- kvPersist: (args.kvPersist as string) || true,
1939
- // TODO: these options shouldn't be required
1940
- script: ` `, // has to be a string with at least one char
1941
- });
1942
- const ns = await mf.getKVNamespace(namespaceId);
1943
- for (const {
1944
- key,
1945
- value,
1946
- expiration,
1947
- expiration_ttl,
1948
- } of parsedContent) {
1949
- await ns.put(key, value, {
1950
- expiration,
1951
- expirationTtl: expiration_ttl,
1952
- });
1953
- }
1954
- } else {
1955
- // -- snip, extract --
1956
- const loggedIn = await loginOrRefreshIfRequired();
1957
- if (!loggedIn) {
1958
- // didn't login, let's just quit
1959
- console.log("Did not login, quitting...");
1960
- return;
1961
- }
2092
+ // -- snip, extract --
2093
+ const loggedIn = await loginOrRefreshIfRequired();
2094
+ if (!loggedIn) {
2095
+ // didn't login, let's just quit
2096
+ console.log("Did not login, quitting...");
2097
+ return;
2098
+ }
1962
2099
 
2100
+ if (!config.account_id) {
2101
+ config.account_id = await getAccountId();
1963
2102
  if (!config.account_id) {
1964
- config.account_id = await getAccountId();
1965
- if (!config.account_id) {
1966
- throw new Error("No account id found, quitting...");
1967
- }
2103
+ throw new Error("No account id found, quitting...");
1968
2104
  }
1969
- // -- snip, end --
1970
-
1971
- console.log(
1972
- await putBulkKeyValue(config.account_id, namespaceId, content)
1973
- );
1974
2105
  }
2106
+ // -- snip, end --
2107
+
2108
+ console.log(
2109
+ await putBulkKeyValue(config.account_id, namespaceId, content)
2110
+ );
1975
2111
  }
1976
2112
  )
1977
2113
  .command(
@@ -2003,54 +2139,29 @@ export async function main(argv: string[]): Promise<void> {
2003
2139
  });
2004
2140
  },
2005
2141
  async ({ filename, ...args }) => {
2006
- const namespaceId = getNamespaceId(args);
2007
- const config = args.config as Config;
2008
- const content = await readFile(filename, "utf-8");
2009
- let parsedContent;
2010
- try {
2011
- parsedContent = JSON.parse(content);
2012
- } catch (err) {
2013
- throw new Error(
2014
- `Could not parse json from ${filename}.\n${err.message ?? err}`
2015
- );
2142
+ const config = readConfig(args.config as ConfigPath);
2143
+ const namespaceId = getNamespaceId(args, config);
2144
+ const content = parseJSON(readFileSync(filename), filename);
2145
+
2146
+ // -- snip, extract --
2147
+ const loggedIn = await loginOrRefreshIfRequired();
2148
+ if (!loggedIn) {
2149
+ // didn't login, let's just quit
2150
+ console.log("Did not login, quitting...");
2151
+ return;
2016
2152
  }
2017
2153
 
2018
- if (args.local) {
2019
- const { Miniflare } = await import("miniflare");
2020
- const mf = new Miniflare({
2021
- kvPersist: (args.kvPersist as string) || true,
2022
- // TODO: these options shouldn't be required
2023
- script: ` `, // has to be a string with at least one char
2024
- });
2025
- const ns = await mf.getKVNamespace(namespaceId);
2026
- for (const key of parsedContent) {
2027
- await ns.delete(key);
2028
- }
2029
- } else {
2030
- // -- snip, extract --
2031
- const loggedIn = await loginOrRefreshIfRequired();
2032
- if (!loggedIn) {
2033
- // didn't login, let's just quit
2034
- console.log("Did not login, quitting...");
2035
- return;
2036
- }
2037
-
2154
+ if (!config.account_id) {
2155
+ config.account_id = await getAccountId();
2038
2156
  if (!config.account_id) {
2039
- config.account_id = await getAccountId();
2040
- if (!config.account_id) {
2041
- throw new Error("No account id found, quitting...");
2042
- }
2157
+ throw new Error("No account id found, quitting...");
2043
2158
  }
2044
- // -- snip, end --
2045
-
2046
- console.log(
2047
- await deleteBulkKeyValue(
2048
- config.account_id,
2049
- namespaceId,
2050
- content
2051
- )
2052
- );
2053
2159
  }
2160
+ // -- snip, end --
2161
+
2162
+ console.log(
2163
+ await deleteBulkKeyValue(config.account_id, namespaceId, content)
2164
+ );
2054
2165
  }
2055
2166
  );
2056
2167
  }
@@ -2058,38 +2169,153 @@ export async function main(argv: string[]): Promise<void> {
2058
2169
 
2059
2170
  wrangler.command("pages", "⚡️ Configure Cloudflare Pages", pages);
2060
2171
 
2172
+ wrangler.command("r2", "📦 Interact with an R2 store", (r2Yargs) => {
2173
+ return r2Yargs.command("bucket", "Manage R2 buckets", (r2BucketYargs) => {
2174
+ r2BucketYargs.command(
2175
+ "create <name>",
2176
+ "Create a new R2 bucket",
2177
+ (yargs) => {
2178
+ return yargs.positional("name", {
2179
+ describe: "The name of the new bucket",
2180
+ type: "string",
2181
+ demandOption: true,
2182
+ });
2183
+ },
2184
+ async (args) => {
2185
+ // We expect three values in `_`: `r2`, `bucket`, `create`.
2186
+ if (args._.length > 3) {
2187
+ const extraArgs = args._.slice(3).join(" ");
2188
+ throw new CommandLineArgsError(
2189
+ `Unexpected additional positional arguments "${extraArgs}".`
2190
+ );
2191
+ }
2192
+
2193
+ const config = readConfig(args.config as ConfigPath);
2194
+
2195
+ // -- snip, extract --
2196
+ const loggedIn = await loginOrRefreshIfRequired();
2197
+ if (!loggedIn) {
2198
+ // didn't login, let's just quit
2199
+ console.log("Did not login, quitting...");
2200
+ return;
2201
+ }
2202
+
2203
+ if (!config.account_id) {
2204
+ config.account_id = await getAccountId();
2205
+ if (!config.account_id) {
2206
+ throw new Error("No account id found, quitting...");
2207
+ }
2208
+ }
2209
+ // -- snip, end --
2210
+
2211
+ console.log(`Creating bucket ${args.name}.`);
2212
+ await createR2Bucket(config.account_id, args.name);
2213
+ console.log(`Created bucket ${args.name}.`);
2214
+ }
2215
+ );
2216
+
2217
+ r2BucketYargs.command("list", "List R2 buckets", {}, async (args) => {
2218
+ const config = readConfig(args.config as ConfigPath);
2219
+
2220
+ // -- snip, extract --
2221
+ const loggedIn = await loginOrRefreshIfRequired();
2222
+ if (!loggedIn) {
2223
+ // didn't login, let's just quit
2224
+ console.log("Did not login, quitting...");
2225
+ return;
2226
+ }
2227
+
2228
+ if (!config.account_id) {
2229
+ config.account_id = await getAccountId();
2230
+ if (!config.account_id) {
2231
+ throw new Error("No account id found, quitting...");
2232
+ }
2233
+ }
2234
+ // -- snip, end --
2235
+
2236
+ console.log(
2237
+ JSON.stringify(await listR2Buckets(config.account_id), null, 2)
2238
+ );
2239
+ });
2240
+
2241
+ r2BucketYargs.command(
2242
+ "delete <name>",
2243
+ "Delete an R2 bucket",
2244
+ (yargs) => {
2245
+ return yargs.positional("name", {
2246
+ describe: "The name of the bucket to delete",
2247
+ type: "string",
2248
+ demandOption: true,
2249
+ });
2250
+ },
2251
+ async (args) => {
2252
+ // We expect three values in `_`: `r2`, `bucket`, `delete`.
2253
+ if (args._.length > 3) {
2254
+ const extraArgs = args._.slice(3).join(" ");
2255
+ throw new CommandLineArgsError(
2256
+ `Unexpected additional positional arguments "${extraArgs}".`
2257
+ );
2258
+ }
2259
+
2260
+ const config = readConfig(args.config as ConfigPath);
2261
+
2262
+ // -- snip, extract --
2263
+ const loggedIn = await loginOrRefreshIfRequired();
2264
+ if (!loggedIn) {
2265
+ // didn't login, let's just quit
2266
+ console.log("Did not login, quitting...");
2267
+ return;
2268
+ }
2269
+
2270
+ if (!config.account_id) {
2271
+ config.account_id = await getAccountId();
2272
+ if (!config.account_id) {
2273
+ throw new Error("No account id found, quitting...");
2274
+ }
2275
+ }
2276
+ // -- snip, end --
2277
+
2278
+ console.log(`Deleting bucket ${args.name}.`);
2279
+ await deleteR2Bucket(config.account_id, args.name);
2280
+ console.log(`Deleted bucket ${args.name}.`);
2281
+ }
2282
+ );
2283
+ return r2BucketYargs;
2284
+ });
2285
+ });
2286
+
2061
2287
  wrangler
2288
+ .option("legacy-env", {
2289
+ type: "boolean",
2290
+ describe: "Use legacy environments",
2291
+ })
2062
2292
  .option("config", {
2063
2293
  alias: "c",
2064
2294
  describe: "Path to .toml configuration file",
2065
2295
  type: "string",
2066
- async coerce(arg) {
2067
- return await readConfig(arg);
2068
- },
2069
- })
2070
- .option("local", {
2071
- alias: "l",
2072
- describe: "Run on my machine",
2073
- type: "boolean",
2074
- default: false, // I bet this will a point of contention. We'll revisit it.
2075
2296
  });
2076
2297
 
2077
- wrangler.group(["config", "help", "version"], "Flags:");
2298
+ wrangler.group(["config", "help", "version", "legacy-env"], "Flags:");
2078
2299
  wrangler.help().alias("h", "help");
2079
2300
  wrangler.version(wranglerVersion).alias("v", "version");
2080
2301
  wrangler.exitProcess(false);
2081
2302
 
2082
- await initialiseUserConfig();
2083
-
2084
2303
  try {
2304
+ await initialiseUserConfig();
2085
2305
  await wrangler.parse();
2086
2306
  } catch (e) {
2087
2307
  if (e instanceof CommandLineArgsError) {
2088
2308
  wrangler.showHelp("error");
2089
2309
  console.error(""); // Just adds a bit of space
2090
2310
  console.error(e.message);
2311
+ } else if (e instanceof ParseError) {
2312
+ console.error("");
2313
+ e.notes.push({
2314
+ text: "\nIf you think this is a bug, please open an issue at: https://github.com/cloudflare/wrangler2/issues/new",
2315
+ });
2316
+ console.error(formatMessage(e));
2091
2317
  } else {
2092
- console.error(e.message);
2318
+ console.error(e instanceof Error ? e.message : e);
2093
2319
  console.error(""); // Just adds a bit of space
2094
2320
  console.error(
2095
2321
  `${fgGreenColor}%s${resetColor}`,