wrangler 0.0.0-fb43821 → 0.0.0-fe5dcfd

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 (41) hide show
  1. package/package.json +25 -10
  2. package/pages/functions/buildWorker.ts +62 -0
  3. package/pages/functions/filepath-routing.test.ts +39 -0
  4. package/pages/functions/filepath-routing.ts +228 -0
  5. package/pages/functions/identifiers.ts +78 -0
  6. package/pages/functions/routes.ts +158 -0
  7. package/pages/functions/template-worker.ts +136 -0
  8. package/src/__tests__/dev.test.tsx +67 -0
  9. package/src/__tests__/index.test.ts +42 -175
  10. package/src/__tests__/jest.setup.ts +22 -0
  11. package/src/__tests__/kv.test.ts +1199 -0
  12. package/src/__tests__/mock-cfetch.ts +173 -0
  13. package/src/__tests__/mock-dialogs.ts +65 -0
  14. package/src/__tests__/publish.test.ts +737 -0
  15. package/src/__tests__/run-in-tmp.ts +19 -0
  16. package/src/__tests__/run-wrangler.ts +32 -0
  17. package/src/__tests__/whoami.test.tsx +130 -0
  18. package/src/api/form_data.ts +66 -76
  19. package/src/api/preview.ts +4 -4
  20. package/src/api/worker.ts +38 -44
  21. package/src/cfetch/index.ts +110 -0
  22. package/src/cfetch/internal.ts +71 -0
  23. package/src/cli.ts +6 -7
  24. package/src/config.ts +634 -110
  25. package/src/dev.tsx +275 -272
  26. package/src/dialogs.tsx +2 -2
  27. package/src/index.tsx +504 -403
  28. package/src/inspect.ts +543 -0
  29. package/src/kv.tsx +109 -44
  30. package/src/pages.tsx +444 -222
  31. package/src/proxy.ts +302 -0
  32. package/src/publish.ts +123 -87
  33. package/src/sites.tsx +169 -41
  34. package/src/tail.tsx +5 -3
  35. package/src/user.tsx +64 -74
  36. package/src/whoami.tsx +69 -0
  37. package/wrangler-dist/cli.js +42792 -42619
  38. package/wrangler-dist/cli.js.map +3 -3
  39. package/src/__tests__/mock-cfetch.js +0 -42
  40. package/src/api/inspect.ts +0 -430
  41. package/src/cfetch.ts +0 -72
package/src/index.tsx CHANGED
@@ -3,11 +3,12 @@ import { render } from "ink";
3
3
  import Dev from "./dev";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import makeCLI from "yargs";
6
- import { hideBin } from "yargs/helpers";
7
- import type yargs from "yargs";
6
+ import type Yargs from "yargs";
8
7
  import { findUp } from "find-up";
9
8
  import TOML from "@iarna/toml";
9
+ import { normaliseAndValidateEnvironmentsConfig } from "./config";
10
10
  import type { Config } from "./config";
11
+ import { getAssetPaths } from "./sites";
11
12
  import { confirm, prompt } from "./dialogs";
12
13
  import { version as wranglerVersion } from "../package.json";
13
14
  import {
@@ -17,6 +18,7 @@ import {
17
18
  initialise as initialiseUserConfig,
18
19
  loginOrRefreshIfRequired,
19
20
  getAccountId,
21
+ validateScopeKeys,
20
22
  } from "./user";
21
23
  import {
22
24
  getNamespaceId,
@@ -25,11 +27,13 @@ import {
25
27
  putKeyValue,
26
28
  putBulkKeyValue,
27
29
  deleteBulkKeyValue,
30
+ createNamespace,
31
+ isValidNamespaceBinding,
28
32
  } from "./kv";
29
33
 
30
34
  import { pages } from "./pages";
31
35
 
32
- import cfetch from "./cfetch";
36
+ import { fetchResult, fetchRaw } from "./cfetch";
33
37
 
34
38
  import publish from "./publish";
35
39
  import path from "path/posix";
@@ -41,63 +45,37 @@ import onExit from "signal-exit";
41
45
  import { setTimeout } from "node:timers/promises";
42
46
  import * as fs from "node:fs";
43
47
  import { execa } from "execa";
48
+ import { whoami } from "./whoami";
44
49
 
45
- async function readConfig(path?: string): Promise<Config> {
50
+ const resetColor = "\x1b[0m";
51
+ const fgGreenColor = "\x1b[32m";
52
+
53
+ async function readConfig(configPath?: string): Promise<Config> {
46
54
  const config: Config = {};
47
- if (!path) {
48
- path = await findUp("wrangler.toml");
55
+ if (!configPath) {
56
+ configPath = await findUp("wrangler.toml");
49
57
  // TODO - terminate this early instead of going all the way to the root
50
58
  }
51
59
 
52
- if (path) {
53
- const tml: string = await readFile(path, "utf-8");
60
+ if (configPath) {
61
+ const tml: string = await readFile(configPath, "utf-8");
54
62
  const parsed = TOML.parse(tml) as Config;
55
63
  Object.assign(config, parsed);
56
64
  }
57
65
 
58
- const inheritedFields = [
59
- "name",
60
- "account_id",
61
- "workers_dev",
62
- "compatibility_date",
63
- "compatibility_flags",
64
- "zone_id",
65
- "routes",
66
- "route",
67
- "jsx_factory",
68
- "jsx_fragment",
69
- "site",
70
- "triggers",
71
- "usage_model",
72
- ];
73
-
74
- Object.keys(config.env || {}).forEach((env) => {
75
- inheritedFields.forEach((field) => {
76
- if (config[field] !== undefined && config.env[env][field] === undefined) {
77
- config.env[env][field] = config[field]; // TODO: - shallow copy?
78
- }
79
- });
80
- });
81
-
82
- const mirroredFields = ["vars", "kv_namespaces", "durable_objects"];
83
- Object.keys(config.env || {}).forEach((env) => {
84
- mirroredFields.forEach((field) => {
85
- // if it exists on top level, it should exist on env defns
86
- Object.keys(config[field] || {}).forEach((fieldKey) => {
87
- if (!(fieldKey in config.env[env][field])) {
88
- console.error(
89
- `In your configuration, "${field}.${fieldKey}" exists at a top level, but not on "env.${env}". This is not what you probably want, since the field "${field}" is not inherited by environments. Please add "${field}.${fieldKey}" to "env.${env}".`
90
- );
91
- }
92
- });
93
- });
94
- });
66
+ normaliseAndValidateEnvironmentsConfig(config);
67
+
68
+ if ("experimental_services" in config) {
69
+ console.warn(
70
+ "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"
71
+ );
72
+ }
95
73
 
96
74
  // todo: validate, add defaults
97
75
  // let's just do some basics for now
98
76
 
99
77
  // @ts-expect-error we're being sneaky here for now
100
- config.__path__ = path;
78
+ config.__path__ = configPath;
101
79
 
102
80
  return config;
103
81
  }
@@ -105,18 +83,18 @@ async function readConfig(path?: string): Promise<Config> {
105
83
  // a helper to demand one of a set of options
106
84
  // via https://github.com/yargs/yargs/issues/1093#issuecomment-491299261
107
85
  function demandOneOfOption(...options: string[]) {
108
- return function (argv: yargs.Arguments) {
86
+ return function (argv: Yargs.Arguments) {
109
87
  const count = options.filter((option) => argv[option]).length;
110
88
  const lastOption = options.pop();
111
89
 
112
90
  if (count === 0) {
113
- throw new Error(
91
+ throw new CommandLineArgsError(
114
92
  `Exactly one of the arguments ${options.join(
115
93
  ", "
116
94
  )} and ${lastOption} is required`
117
95
  );
118
96
  } else if (count > 1) {
119
- throw new Error(
97
+ throw new CommandLineArgsError(
120
98
  `Arguments ${options.join(
121
99
  ", "
122
100
  )} and ${lastOption} are mutually exclusive`
@@ -127,33 +105,49 @@ function demandOneOfOption(...options: string[]) {
127
105
  };
128
106
  }
129
107
 
108
+ class CommandLineArgsError extends Error {}
109
+ class DeprecationError extends Error {}
110
+ class NotImplementedError extends Error {}
111
+
130
112
  export async function main(argv: string[]): Promise<void> {
131
- const yargs = makeCLI(hideBin(process.argv))
132
- .command(
133
- // the default is to simply print the help menu
134
- ["*"],
135
- false,
136
- () => {},
137
- (args) => {
138
- yargs.showHelp("log");
139
- if (args._.length > 0) {
140
- console.error(`\nUnknown command: ${args._}.`);
141
- }
113
+ const wrangler = makeCLI(argv)
114
+ // We handle errors ourselves in a try-catch around `yargs.parse`.
115
+ // If you want the "help info" to be displayed then throw an instance of `CommandLineArgsError`.
116
+ // Otherwise we just log the error that was thrown without any "help info".
117
+ .showHelpOnFail(false)
118
+ .fail((msg, error) => {
119
+ if (!error) {
120
+ // If there is only a `msg` then this came from yargs own validation, so wrap in a `CommandLineArgsError`.
121
+ error = new CommandLineArgsError(msg);
142
122
  }
143
- )
123
+ throw error;
124
+ })
144
125
  .scriptName("wrangler")
145
126
  .wrap(null);
146
127
 
147
- // you will note that we use the form for all commands where we use the builder function
148
- // to define options and subcommands. Further we return the result of this builder even
149
- // tho it's not completely necessary. The reason is that it's required for type inference
150
- // of the args in the handle function.I wish we could enforce this pattern, but this
151
- // comment will have to do for now.
128
+ // the default is to simply print the help menu
129
+ wrangler.command(
130
+ ["*"],
131
+ false,
132
+ () => {},
133
+ (args) => {
134
+ if (args._.length > 0) {
135
+ throw new CommandLineArgsError(`Unknown command: ${args._}.`);
136
+ } else {
137
+ wrangler.showHelp("log");
138
+ }
139
+ }
140
+ );
152
141
 
153
- // also annoying that choices[] doesn't get inferred as an enum. bleh.
142
+ // You will note that we use the form for all commands where we use the builder function
143
+ // to define options and subcommands.
144
+ // Further we return the result of this builder even though it's not completely necessary.
145
+ // The reason is that it's required for type inference of the args in the handle function.
146
+ // I wish we could enforce this pattern, but this comment will have to do for now.
147
+ // (It's also annoying that choices[] doesn't get inferred as an enum. 🤷‍♂.)
154
148
 
155
149
  // [DEPRECATED] generate
156
- yargs.command(
150
+ wrangler.command(
157
151
  // we definitely want to move away from us cloning github templates
158
152
  // we can do something better here, let's see
159
153
  "generate [name] [template]",
@@ -171,14 +165,14 @@ export async function main(argv: string[]): Promise<void> {
171
165
  },
172
166
  () => {
173
167
  // "👯 [DEPRECATED]. Scaffold a Cloudflare Workers project from a public GitHub repository.",
174
- console.error(
168
+ throw new DeprecationError(
175
169
  "`wrangler generate` has been deprecated, please refer to TODO://some/path for alternatives"
176
170
  );
177
171
  }
178
172
  );
179
173
 
180
174
  // init
181
- yargs.command(
175
+ wrangler.command(
182
176
  "init [name]",
183
177
  "📥 Create a wrangler.toml configuration file",
184
178
  (yargs) => {
@@ -195,13 +189,12 @@ export async function main(argv: string[]): Promise<void> {
195
189
  "\nIf you wish to use webpack then you will need to create a custom build.";
196
190
  // TODO: Add a link to docs
197
191
  }
198
- console.error(message);
199
- return;
192
+ throw new CommandLineArgsError(message);
200
193
  }
201
194
 
202
195
  const destination = path.join(process.cwd(), "wrangler.toml");
203
196
  if (fs.existsSync(destination)) {
204
- console.error(`${destination} file already exists!`);
197
+ console.warn(`${destination} file already exists!`);
205
198
  const result = await confirm(
206
199
  "Do you want to continue initializing this project?"
207
200
  );
@@ -219,9 +212,9 @@ export async function main(argv: string[]): Promise<void> {
219
212
  console.log(`✨ Successfully created wrangler.toml`);
220
213
  // TODO: suggest next steps?
221
214
  } catch (err) {
222
- console.error(`Failed to create wrangler.toml`);
223
- console.error(err);
224
- throw err;
215
+ throw new Error(
216
+ `Failed to create wrangler.toml.\n${err.message ?? err}`
217
+ );
225
218
  }
226
219
 
227
220
  // if no package.json, ask, and if yes, create one
@@ -293,7 +286,7 @@ export async function main(argv: string[]): Promise<void> {
293
286
  );
294
287
 
295
288
  // build
296
- yargs.command(
289
+ wrangler.command(
297
290
  "build",
298
291
  false,
299
292
  (yargs) => {
@@ -303,14 +296,14 @@ export async function main(argv: string[]): Promise<void> {
303
296
  },
304
297
  () => {
305
298
  // "[DEPRECATED] 🦀 Build your project (if applicable)",
306
- console.error(
299
+ throw new DeprecationError(
307
300
  "`wrangler build` has been deprecated, please refer to TODO://some/path for alternatives"
308
301
  );
309
302
  }
310
303
  );
311
304
 
312
305
  // login
313
- yargs.command(
306
+ wrangler.command(
314
307
  // this needs scopes as an option?
315
308
  "login",
316
309
  false, // we don't need to show this in the menu
@@ -341,19 +334,24 @@ export async function main(argv: string[]): Promise<void> {
341
334
  listScopes();
342
335
  return;
343
336
  }
337
+ if (!validateScopeKeys(args.scopes)) {
338
+ throw new CommandLineArgsError(
339
+ `One of ${args.scopes} is not a valid authentication scope. Run "wrangler login --list-scopes" to see the valid scopes.`
340
+ );
341
+ }
344
342
  await login({ scopes: args.scopes });
345
343
  return;
346
344
  }
347
345
  await login();
348
346
 
349
347
  // TODO: would be nice if it optionally saved login
350
- // creds inside node_modules/.cache or something
348
+ // credentials inside node_modules/.cache or something
351
349
  // this way you could have multiple users on a single machine
352
350
  }
353
351
  );
354
352
 
355
353
  // logout
356
- yargs.command(
354
+ wrangler.command(
357
355
  // this needs scopes as an option?
358
356
  "logout",
359
357
  false, // we don't need to show this in the menu
@@ -365,36 +363,39 @@ export async function main(argv: string[]): Promise<void> {
365
363
  );
366
364
 
367
365
  // whoami
368
- yargs.command(
366
+ wrangler.command(
369
367
  "whoami",
370
- false, // we don't need to show this the menu
371
- // "🕵️ Retrieve your user info and test your auth config",
368
+ "🕵️ Retrieve your user info and test your auth config",
372
369
  () => {},
373
- (args) => {
374
- console.log(":whoami", args);
370
+ async () => {
371
+ await whoami();
375
372
  }
376
373
  );
377
374
 
378
375
  // config
379
- yargs.command(
376
+ wrangler.command(
380
377
  "config",
381
378
  false,
382
379
  () => {},
383
380
  () => {
384
381
  // "🕵️ Authenticate Wrangler with a Cloudflare API Token",
385
- console.error(
382
+ throw new DeprecationError(
386
383
  "`wrangler config` has been deprecated, please refer to TODO://some/path for alternatives"
387
384
  );
388
385
  }
389
386
  );
390
387
 
391
388
  // dev
392
- yargs.command(
389
+ wrangler.command(
393
390
  "dev <filename>",
394
391
  "👂 Start a local server for developing your worker",
395
392
  (yargs) => {
396
393
  return yargs
397
- .positional("filename", { describe: "entry point", type: "string" })
394
+ .positional("filename", {
395
+ describe: "entry point",
396
+ type: "string",
397
+ demandOption: true,
398
+ })
398
399
  .option("name", {
399
400
  describe: "name of the script",
400
401
  type: "string",
@@ -408,6 +409,20 @@ export async function main(argv: string[]): Promise<void> {
408
409
  type: "string",
409
410
  // TODO: get choices for the toml file?
410
411
  })
412
+ .option("compatibility-date", {
413
+ describe: "Date to use for compatibility checks",
414
+ type: "string",
415
+ })
416
+ .option("compatibility-flags", {
417
+ describe: "Flags to use for compatibility checks",
418
+ type: "array",
419
+ alias: "compatibility-flag",
420
+ })
421
+ .option("latest", {
422
+ describe: "Use the latest version of the worker runtime",
423
+ type: "boolean",
424
+ default: true,
425
+ })
411
426
  .option("ip", {
412
427
  describe: "IP address to listen on",
413
428
  type: "string",
@@ -428,7 +443,7 @@ export async function main(argv: string[]): Promise<void> {
428
443
  describe: "Protocol to listen to requests on, defaults to http.",
429
444
  choices: ["http", "https"],
430
445
  })
431
- .option("public", {
446
+ .option("experimental-public", {
432
447
  describe: "Static assets to be served",
433
448
  type: "string",
434
449
  })
@@ -436,6 +451,18 @@ export async function main(argv: string[]): Promise<void> {
436
451
  describe: "Root folder of static assets for Workers Sites",
437
452
  type: "string",
438
453
  })
454
+ .option("site-include", {
455
+ describe:
456
+ "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.",
457
+ type: "string",
458
+ array: true,
459
+ })
460
+ .option("site-exclude", {
461
+ describe:
462
+ "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.",
463
+ type: "string",
464
+ array: true,
465
+ })
439
466
  .option("upstream-protocol", {
440
467
  default: "https",
441
468
  describe:
@@ -455,83 +482,112 @@ export async function main(argv: string[]): Promise<void> {
455
482
  const { filename, format } = args;
456
483
  const config = args.config as Config;
457
484
 
458
- // -- snip, extract --
485
+ if (args["experimental-public"]) {
486
+ console.warn(
487
+ "🚨 The --experimental-public field is experimental and will change in the future."
488
+ );
489
+ }
490
+
491
+ if (args.public) {
492
+ throw new Error(
493
+ "🚨 The --public field has been renamed to --experimental-public, and will change behaviour in the future."
494
+ );
495
+ }
459
496
 
460
497
  if (!args.local) {
498
+ // -- snip, extract --
461
499
  const loggedIn = await loginOrRefreshIfRequired();
462
500
  if (!loggedIn) {
463
501
  // didn't login, let's just quit
464
502
  console.log("Did not login, quitting...");
465
503
  return;
466
504
  }
505
+
467
506
  if (!config.account_id) {
468
507
  config.account_id = await getAccountId();
469
508
  if (!config.account_id) {
470
- console.error("No account id found, quitting...");
471
- return;
509
+ throw new Error("No account id found, quitting...");
472
510
  }
473
511
  }
512
+ // -- snip, end --
474
513
  }
475
514
 
476
- // -- snip, end --
477
-
478
- const envRootObj = args.env ? config.env[args.env] || {} : config;
515
+ const environments = config.env ?? {};
516
+ const envRootObj = args.env ? environments[args.env] || {} : config;
479
517
 
480
518
  // TODO: this error shouldn't actually happen,
481
519
  // but we haven't fixed it internally yet
482
520
  if ("durable_objects" in envRootObj) {
483
521
  if (!(args.name || config.name)) {
484
522
  console.warn(
485
- 'A worker with durable objects need 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.'
523
+ '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.'
486
524
  );
487
525
  }
488
526
  // TODO: if not already published, publish a draft worker
489
527
  }
490
528
 
491
- render(
529
+ const { waitUntilExit } = render(
492
530
  <Dev
493
531
  name={args.name || config.name}
494
- entry={filename}
532
+ entry={path.relative(process.cwd(), filename)}
533
+ buildCommand={config.build || {}}
495
534
  format={format}
496
535
  initialMode={args.local ? "local" : "remote"}
497
536
  jsxFactory={args["jsx-factory"] || envRootObj?.jsx_factory}
498
537
  jsxFragment={args["jsx-fragment"] || envRootObj?.jsx_fragment}
499
538
  accountId={config.account_id}
500
- site={args.site || config.site?.bucket}
539
+ assetPaths={getAssetPaths(
540
+ config,
541
+ args.site,
542
+ args.siteInclude,
543
+ args.siteExclude
544
+ )}
501
545
  port={args.port || config.dev?.port}
502
- public={args.public}
503
- compatibilityDate={config.compatibility_date}
504
- compatibilityFlags={config.compatibility_flags}
546
+ public={args["experimental-public"]}
547
+ compatibilityDate={
548
+ args["compatibility-date"] ||
549
+ config.compatibility_date ||
550
+ new Date().toISOString().substring(0, 10)
551
+ }
552
+ compatibilityFlags={
553
+ (args["compatibility-flags"] as string[]) ||
554
+ config.compatibility_flags
555
+ }
505
556
  usageModel={config.usage_model}
506
- variables={{
507
- ...(envRootObj?.vars || {}),
508
- ...(envRootObj?.kv_namespaces || []).reduce(
509
- (obj, { binding, preview_id }) => {
557
+ bindings={{
558
+ kv_namespaces: envRootObj.kv_namespaces?.map(
559
+ ({ binding, preview_id, id: _id }) => {
560
+ // In `dev`, we make folks use a separate kv namespace called
561
+ // `preview_id` instead of `id` so that they don't
562
+ // break production data. So here we check that a `preview_id`
563
+ // has actually been configured.
564
+ // This whole block of code will be obsoleted in the future
565
+ // when we have copy-on-write for previews on edge workers.
510
566
  if (!preview_id) {
511
567
  // TODO: This error has to be a _lot_ better, ideally just asking
512
568
  // to create a preview namespace for the user automatically
513
569
  throw new Error(
514
- "kv namespaces need a preview id during dev mode"
515
- );
570
+ `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`
571
+ ); // Ugh, I really don't like this message very much
516
572
  }
517
- return { ...obj, [binding]: { namespaceId: preview_id } };
518
- },
519
- {}
520
- ),
521
- ...(envRootObj?.durable_objects?.bindings || []).reduce(
522
- (obj, { name, class_name, script_name }) => {
523
- return { ...obj, [name]: { class_name, script_name } };
524
- },
525
- {}
573
+ return {
574
+ binding,
575
+ id: preview_id,
576
+ };
577
+ }
526
578
  ),
579
+ vars: envRootObj.vars,
580
+ durable_objects: envRootObj.durable_objects,
581
+ services: envRootObj.experimental_services,
527
582
  }}
528
583
  />
529
584
  );
585
+ await waitUntilExit();
530
586
  }
531
587
  );
532
588
 
533
589
  // publish
534
- yargs.command(
590
+ wrangler.command(
535
591
  "publish [script]",
536
592
  "🆙 Publish your Worker to Cloudflare.",
537
593
  (yargs) => {
@@ -548,7 +604,21 @@ export async function main(argv: string[]): Promise<void> {
548
604
  describe: "name to use when uploading",
549
605
  type: "string",
550
606
  })
551
- .option("public", {
607
+ .option("compatibility-date", {
608
+ describe: "Date to use for compatibility checks",
609
+ type: "string",
610
+ })
611
+ .option("compatibility-flags", {
612
+ describe: "Flags to use for compatibility checks",
613
+ type: "array",
614
+ alias: "compatibility-flag",
615
+ })
616
+ .option("latest", {
617
+ describe: "Use the latest version of the worker runtime",
618
+ type: "boolean",
619
+ default: false,
620
+ })
621
+ .option("experimental-public", {
552
622
  describe: "Static assets to be served",
553
623
  type: "string",
554
624
  })
@@ -556,6 +626,18 @@ export async function main(argv: string[]): Promise<void> {
556
626
  describe: "Root folder of static assets for Workers Sites",
557
627
  type: "string",
558
628
  })
629
+ .option("site-include", {
630
+ describe:
631
+ "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.",
632
+ type: "string",
633
+ array: true,
634
+ })
635
+ .option("site-exclude", {
636
+ describe:
637
+ "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.",
638
+ type: "string",
639
+ array: true,
640
+ })
559
641
  .option("triggers", {
560
642
  describe: "cron schedules to attach",
561
643
  alias: ["schedule", "schedules"],
@@ -583,47 +665,77 @@ export async function main(argv: string[]): Promise<void> {
583
665
  },
584
666
  async (args) => {
585
667
  if (args.local) {
586
- console.error("🚫 Local publishing is not yet supported");
587
- return;
668
+ throw new NotImplementedError(
669
+ "🚫 Local publishing is not yet supported"
670
+ );
671
+ }
672
+
673
+ if (args["experimental-public"]) {
674
+ console.warn(
675
+ "🚨 The --experimental-public field is experimental and will change in the future."
676
+ );
588
677
  }
678
+ if (args.public) {
679
+ throw new Error(
680
+ "🚨 The --public field has been renamed to --experimental-public, and will change behaviour in the future."
681
+ );
682
+ }
683
+
589
684
  const config = args.config as Config;
590
685
 
591
- // -- snip, extract --
686
+ if (args.latest) {
687
+ console.warn(
688
+ "⚠️ Using the latest version of the Workers runtime. To silence this warning, please choose a specific version of the runtime with --compatibility-date, or add a compatibility_date to your wrangler.toml.\n"
689
+ );
690
+ }
691
+
592
692
  if (!args.local) {
693
+ // -- snip, extract --
593
694
  const loggedIn = await loginOrRefreshIfRequired();
594
695
  if (!loggedIn) {
595
696
  // didn't login, let's just quit
596
697
  console.log("Did not login, quitting...");
597
698
  return;
598
699
  }
700
+
599
701
  if (!config.account_id) {
600
702
  config.account_id = await getAccountId();
601
703
  if (!config.account_id) {
602
- console.error("No account id found, quitting...");
603
- return;
704
+ throw new Error("No account id found, quitting...");
604
705
  }
605
706
  }
707
+ // -- snip, end --
606
708
  }
607
709
 
608
- // -- snip, end --
609
-
710
+ const assetPaths = getAssetPaths(
711
+ config,
712
+ args["experimental-public"] || args.site,
713
+ args.siteInclude,
714
+ args.siteExclude
715
+ );
610
716
  await publish({
611
717
  config: args.config as Config,
612
718
  name: args.name,
613
719
  script: args.script,
614
720
  env: args.env,
721
+ compatibilityDate: args.latest
722
+ ? new Date().toISOString().substring(0, 10)
723
+ : args["compatibility-date"],
724
+ compatibilityFlags: args["compatibility-flags"] as string[],
615
725
  triggers: args.triggers,
616
726
  jsxFactory: args["jsx-factory"],
617
727
  jsxFragment: args["jsx-fragment"],
618
728
  routes: args.routes,
619
- public: args.public,
620
- site: args.site,
729
+ assetPaths,
730
+ format: undefined, // TODO: add args for this
731
+ legacyEnv: undefined, // TODO: get this from somewhere... config?
732
+ experimentalPublic: args["experimental-public"] !== undefined,
621
733
  });
622
734
  }
623
735
  );
624
736
 
625
737
  // tail
626
- yargs.command(
738
+ wrangler.command(
627
739
  "tail [name]",
628
740
  "🦚 Starts a log tailing session for a deployed Worker.",
629
741
  (yargs) => {
@@ -667,34 +779,35 @@ export async function main(argv: string[]): Promise<void> {
667
779
  // TODO: filter by client ip, which can be 'self' or an ip address
668
780
  },
669
781
  async (args) => {
782
+ if (args.local) {
783
+ throw new NotImplementedError(
784
+ `local mode is not yet supported for this command`
785
+ );
786
+ }
787
+
670
788
  const config = args.config as Config;
671
789
 
672
790
  if (!(args.name || config.name)) {
673
- console.error("Missing script name");
674
- return;
791
+ throw new Error("Missing script name");
675
792
  }
676
793
  const scriptName = `${args.name || config.name}${
677
794
  args.env ? `-${args.env}` : ""
678
795
  }`;
679
796
 
680
797
  // -- snip, extract --
798
+ const loggedIn = await loginOrRefreshIfRequired();
799
+ if (!loggedIn) {
800
+ // didn't login, let's just quit
801
+ console.log("Did not login, quitting...");
802
+ return;
803
+ }
681
804
 
682
- if (!args.local) {
683
- const loggedIn = await loginOrRefreshIfRequired();
684
- if (!loggedIn) {
685
- // didn't login, let's just quit
686
- console.log("Did not login, quitting...");
687
- return;
688
- }
805
+ if (!config.account_id) {
806
+ config.account_id = await getAccountId();
689
807
  if (!config.account_id) {
690
- config.account_id = await getAccountId();
691
- if (!config.account_id) {
692
- console.error("No account id found, quitting...");
693
- return;
694
- }
808
+ throw new Error("No account id found, quitting...");
695
809
  }
696
810
  }
697
-
698
811
  // -- snip, end --
699
812
 
700
813
  const accountId = config.account_id;
@@ -741,7 +854,7 @@ export async function main(argv: string[]): Promise<void> {
741
854
  );
742
855
 
743
856
  // preview
744
- yargs.command(
857
+ wrangler.command(
745
858
  "preview [method] [body]",
746
859
  false,
747
860
  (yargs) => {
@@ -767,20 +880,20 @@ export async function main(argv: string[]): Promise<void> {
767
880
  });
768
881
  },
769
882
  () => {
770
- // "🔬 [DEPRECATED] Preview your code temporarily on cloudflareworkers.com"
771
- console.error(
883
+ // "🔬 [DEPRECATED] Preview your code temporarily on https://cloudflareworkers.com"
884
+ throw new DeprecationError(
772
885
  "`wrangler preview` has been deprecated, please refer to TODO://some/path for alternatives"
773
886
  );
774
887
  }
775
888
  );
776
889
 
777
890
  // route
778
- yargs.command(
891
+ wrangler.command(
779
892
  "route",
780
893
  false, // I think we want to hide this command
781
894
  // "➡️ List or delete worker routes",
782
- (yargs) => {
783
- return yargs
895
+ (routeYargs) => {
896
+ return routeYargs
784
897
  .command(
785
898
  "list",
786
899
  "List a route associated with a zone",
@@ -804,11 +917,10 @@ export async function main(argv: string[]): Promise<void> {
804
917
  // TODO: use environment (current wrangler doesn't do so?)
805
918
  const zone = args.zone || (args.config as Config).zone_id;
806
919
  if (!zone) {
807
- console.error("missing zone id");
808
- return;
920
+ throw new Error("missing zone id");
809
921
  }
810
922
 
811
- console.log(await cfetch(`/zones/${zone}/workers/routes`));
923
+ console.log(await fetchResult(`/zones/${zone}/workers/routes`));
812
924
  }
813
925
  )
814
926
  .command(
@@ -838,7 +950,7 @@ export async function main(argv: string[]): Promise<void> {
838
950
  }
839
951
 
840
952
  console.log(
841
- await cfetch(`/zones/${zone}/workers/routes/${args.id}`, {
953
+ await fetchResult(`/zones/${zone}/workers/routes/${args.id}`, {
842
954
  method: "DELETE",
843
955
  })
844
956
  );
@@ -848,7 +960,7 @@ export async function main(argv: string[]): Promise<void> {
848
960
  );
849
961
 
850
962
  // subdomain
851
- yargs.command(
963
+ wrangler.command(
852
964
  "subdomain [name]",
853
965
  false,
854
966
  // "👷 Create or change your workers.dev subdomain.",
@@ -856,18 +968,18 @@ export async function main(argv: string[]): Promise<void> {
856
968
  return yargs.positional("name", { type: "string" });
857
969
  },
858
970
  () => {
859
- console.error(
971
+ throw new DeprecationError(
860
972
  "`wrangler subdomain` has been deprecated, please refer to TODO://some/path for alternatives"
861
973
  );
862
974
  }
863
975
  );
864
976
 
865
977
  // secret
866
- yargs.command(
978
+ wrangler.command(
867
979
  "secret",
868
980
  "🤫 Generate a secret that can be referenced in the worker script",
869
- (yargs) => {
870
- return yargs
981
+ (secretYargs) => {
982
+ return secretYargs
871
983
  .command(
872
984
  "put <key>",
873
985
  "Create or update a secret variable for a script",
@@ -889,44 +1001,42 @@ export async function main(argv: string[]): Promise<void> {
889
1001
  },
890
1002
  async (args) => {
891
1003
  if (args.local) {
892
- console.error("--local not implemented for this command yet");
893
- return;
1004
+ throw new NotImplementedError(
1005
+ "--local not implemented for this command yet"
1006
+ );
894
1007
  }
895
1008
  const config = args.config as Config;
896
1009
 
897
1010
  // TODO: use environment (how does current wrangler do it?)
898
1011
  const scriptName = args.name || config.name;
899
1012
  if (!scriptName) {
900
- console.error("Missing script name");
901
- return;
1013
+ throw new Error("Missing script name");
902
1014
  }
903
1015
 
904
- // -- snip, extract --
905
-
906
1016
  if (!args.local) {
1017
+ // -- snip, extract --
907
1018
  const loggedIn = await loginOrRefreshIfRequired();
908
1019
  if (!loggedIn) {
909
1020
  // didn't login, let's just quit
910
1021
  console.log("Did not login, quitting...");
911
1022
  return;
912
1023
  }
1024
+
913
1025
  if (!config.account_id) {
914
1026
  config.account_id = await getAccountId();
915
1027
  if (!config.account_id) {
916
- console.error("No account id found, quitting...");
917
- return;
1028
+ throw new Error("No account id found, quitting...");
918
1029
  }
919
1030
  }
1031
+ // -- snip, end --
920
1032
  }
921
1033
 
922
- // -- snip, end --
923
-
924
1034
  const secretValue = await prompt(
925
1035
  "Enter a secret value:",
926
1036
  "password"
927
1037
  );
928
1038
  async function submitSecret() {
929
- return await cfetch(
1039
+ return await fetchResult(
930
1040
  `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/`,
931
1041
  {
932
1042
  method: "PUT",
@@ -945,7 +1055,7 @@ export async function main(argv: string[]): Promise<void> {
945
1055
  } catch (e) {
946
1056
  if (e.code === 10007) {
947
1057
  // upload a draft worker
948
- await cfetch(
1058
+ await fetchResult(
949
1059
  `/accounts/${config.account_id}/workers/scripts/${scriptName}`,
950
1060
  {
951
1061
  method: "PUT",
@@ -956,7 +1066,11 @@ export async function main(argv: string[]): Promise<void> {
956
1066
  content: `export default { fetch() {} }`,
957
1067
  type: "esm",
958
1068
  },
959
- variables: {},
1069
+ bindings: {
1070
+ kv_namespaces: [],
1071
+ vars: {},
1072
+ durable_objects: { bindings: [] },
1073
+ },
960
1074
  modules: [],
961
1075
  }),
962
1076
  }
@@ -990,8 +1104,9 @@ export async function main(argv: string[]): Promise<void> {
990
1104
  },
991
1105
  async (args) => {
992
1106
  if (args.local) {
993
- console.error("--local not implemented for this command yet");
994
- return;
1107
+ throw new NotImplementedError(
1108
+ "--local not implemented for this command yet"
1109
+ );
995
1110
  }
996
1111
  const config = args.config as Config;
997
1112
 
@@ -1001,33 +1116,31 @@ export async function main(argv: string[]): Promise<void> {
1001
1116
  throw new Error("Missing script name");
1002
1117
  }
1003
1118
 
1004
- // -- snip, extract --
1005
-
1006
1119
  if (!args.local) {
1120
+ // -- snip, extract --
1007
1121
  const loggedIn = await loginOrRefreshIfRequired();
1008
1122
  if (!loggedIn) {
1009
1123
  // didn't login, let's just quit
1010
1124
  console.log("Did not login, quitting...");
1011
1125
  return;
1012
1126
  }
1127
+
1013
1128
  if (!config.account_id) {
1014
1129
  config.account_id = await getAccountId();
1015
1130
  if (!config.account_id) {
1016
- console.error("No account id found, quitting...");
1017
- return;
1131
+ throw new Error("No account id found, quitting...");
1018
1132
  }
1019
1133
  }
1134
+ // -- snip, end --
1020
1135
  }
1021
1136
 
1022
- // -- snip, end --
1023
-
1024
1137
  if (await confirm("Are you sure you want to delete this secret?")) {
1025
1138
  console.log(
1026
1139
  `Deleting the secret ${args.key} on script ${scriptName}.`
1027
1140
  );
1028
1141
 
1029
1142
  console.log(
1030
- await cfetch(
1143
+ await fetchResult(
1031
1144
  `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/${args.key}`,
1032
1145
  { method: "DELETE" }
1033
1146
  )
@@ -1052,40 +1165,38 @@ export async function main(argv: string[]): Promise<void> {
1052
1165
  },
1053
1166
  async (args) => {
1054
1167
  if (args.local) {
1055
- console.error("--local not implemented for this command yet");
1056
- return;
1168
+ throw new NotImplementedError(
1169
+ "--local not implemented for this command yet"
1170
+ );
1057
1171
  }
1058
1172
  const config = args.config as Config;
1059
1173
 
1060
1174
  // TODO: use environment (how does current wrangler do it?)
1061
1175
  const scriptName = args.name || config.name;
1062
1176
  if (!scriptName) {
1063
- console.error("Missing script name");
1064
- return;
1177
+ throw new Error("Missing script name");
1065
1178
  }
1066
1179
 
1067
- // -- snip, extract --
1068
-
1069
1180
  if (!args.local) {
1181
+ // -- snip, extract --
1070
1182
  const loggedIn = await loginOrRefreshIfRequired();
1071
1183
  if (!loggedIn) {
1072
1184
  // didn't login, let's just quit
1073
1185
  console.log("Did not login, quitting...");
1074
1186
  return;
1075
1187
  }
1188
+
1076
1189
  if (!config.account_id) {
1077
1190
  config.account_id = await getAccountId();
1078
1191
  if (!config.account_id) {
1079
- console.error("No account id found, quitting...");
1080
- return;
1192
+ throw new Error("No account id found, quitting...");
1081
1193
  }
1082
1194
  }
1195
+ // -- snip, end --
1083
1196
  }
1084
1197
 
1085
- // -- snip, end --
1086
-
1087
1198
  console.log(
1088
- await cfetch(
1199
+ await fetchResult(
1089
1200
  `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets`
1090
1201
  )
1091
1202
  );
@@ -1096,11 +1207,11 @@ export async function main(argv: string[]): Promise<void> {
1096
1207
 
1097
1208
  // kv
1098
1209
  // :namespace
1099
- yargs.command(
1210
+ wrangler.command(
1100
1211
  "kv:namespace",
1101
1212
  "🗂️ Interact with your Workers KV Namespaces",
1102
- (yargs) => {
1103
- return yargs
1213
+ (namespaceYargs) => {
1214
+ return namespaceYargs
1104
1215
  .command(
1105
1216
  "create <namespace>",
1106
1217
  "Create a new namespace",
@@ -1109,6 +1220,7 @@ export async function main(argv: string[]): Promise<void> {
1109
1220
  .positional("namespace", {
1110
1221
  describe: "The name of the new namespace",
1111
1222
  type: "string",
1223
+ demandOption: true,
1112
1224
  })
1113
1225
  .option("env", {
1114
1226
  type: "string",
@@ -1120,13 +1232,19 @@ export async function main(argv: string[]): Promise<void> {
1120
1232
  });
1121
1233
  },
1122
1234
  async (args) => {
1123
- if (args._.length !== 2) {
1124
- throw new Error(
1125
- `Did you forget to add quotes around "${
1126
- args.namespace
1127
- } ${args._.slice(2).join(" ")}"?`
1235
+ if (args._.length > 2) {
1236
+ const extraArgs = args._.slice(2).join(" ");
1237
+ throw new CommandLineArgsError(
1238
+ `Unexpected additional positional arguments "${extraArgs}".`
1239
+ );
1240
+ }
1241
+
1242
+ if (!isValidNamespaceBinding(args.namespace)) {
1243
+ throw new CommandLineArgsError(
1244
+ `The namespace binding name "${args.namespace}" is invalid. It can only have alphanumeric and _ characters, and cannot begin with a number.`
1128
1245
  );
1129
1246
  }
1247
+
1130
1248
  const config = args.config as Config;
1131
1249
  if (!config.name) {
1132
1250
  console.warn(
@@ -1134,13 +1252,10 @@ export async function main(argv: string[]): Promise<void> {
1134
1252
  );
1135
1253
  }
1136
1254
 
1137
- const title = `${config.name || "worker"}${
1138
- args.env ? `-${args.env}` : ""
1139
- }-${args.namespace}${args.preview ? "_preview" : ""}`;
1140
-
1141
- if (/[\W]+/.test(args.namespace)) {
1142
- throw new Error("invalid binding name, needs to be js friendly");
1143
- }
1255
+ const name = config.name || "worker";
1256
+ const environment = args.env ? `-${args.env}` : "";
1257
+ const preview = args.preview ? "_preview" : "";
1258
+ const title = `${name}${environment}-${args.namespace}${preview}`;
1144
1259
 
1145
1260
  if (args.local) {
1146
1261
  const { Miniflare } = await import("miniflare");
@@ -1151,57 +1266,43 @@ export async function main(argv: string[]): Promise<void> {
1151
1266
  });
1152
1267
  await mf.getKVNamespace(title); // this should "create" the namespace
1153
1268
  console.log(`✨ Success! Created KV namespace ${title}`);
1154
- return;
1155
- }
1156
-
1157
- // -- snip, extract --
1158
-
1159
- if (!args.local) {
1269
+ } else {
1270
+ // -- snip, extract --
1160
1271
  const loggedIn = await loginOrRefreshIfRequired();
1161
1272
  if (!loggedIn) {
1162
1273
  // didn't login, let's just quit
1163
1274
  console.log("Did not login, quitting...");
1164
1275
  return;
1165
1276
  }
1277
+
1166
1278
  if (!config.account_id) {
1167
1279
  config.account_id = await getAccountId();
1168
1280
  if (!config.account_id) {
1169
- console.error("No account id found, quitting...");
1170
- return;
1281
+ throw new Error("No account id found, quitting...");
1171
1282
  }
1172
1283
  }
1173
- }
1174
-
1175
- // -- snip, end --
1284
+ // -- snip, end --
1176
1285
 
1177
- // TODO: generate a binding name stripping non alphanumeric chars
1286
+ // TODO: generate a binding name stripping non alphanumeric chars
1178
1287
 
1179
- console.log(`🌀 Creating namespace with title "${title}"`);
1288
+ console.log(`🌀 Creating namespace with title "${title}"`);
1289
+ const namespaceId = await createNamespace(
1290
+ config.account_id,
1291
+ title
1292
+ );
1180
1293
 
1181
- const response = await cfetch<{ id: string }>(
1182
- `/accounts/${config.account_id}/storage/kv/namespaces`,
1183
- {
1184
- method: "POST",
1185
- headers: {
1186
- "Content-Type": "application/json",
1187
- },
1188
- body: JSON.stringify({
1189
- title,
1190
- }),
1191
- }
1192
- );
1294
+ console.log("✨ Success!");
1295
+ const envString = args.env ? ` under [env.${args.env}]` : "";
1296
+ const previewString = args.preview ? "preview_" : "";
1297
+ console.log(
1298
+ `Add the following to your configuration file in your kv_namespaces array${envString}:`
1299
+ );
1300
+ console.log(
1301
+ `{ binding = "${args.namespace}", ${previewString}id = "${namespaceId}" }`
1302
+ );
1193
1303
 
1194
- console.log("✨ Success!");
1195
- console.log(
1196
- `Add the following to your configuration file in your kv_namespaces array${
1197
- args.env ? ` under [env.${args.env}]` : ""
1198
- }:`
1199
- );
1200
- console.log(
1201
- `{ binding = "${args.namespace}", ${
1202
- args.preview ? "preview_" : ""
1203
- }id = "${response.id}" }`
1204
- );
1304
+ // TODO: automatically write this block to the wrangler.toml config file??
1305
+ }
1205
1306
  }
1206
1307
  )
1207
1308
  .command(
@@ -1209,42 +1310,39 @@ export async function main(argv: string[]): Promise<void> {
1209
1310
  "Outputs a list of all KV namespaces associated with your account id.",
1210
1311
  {},
1211
1312
  async (args) => {
1212
- if (args.local) {
1213
- console.error(`local mode is not yet supported for this command`);
1214
- return;
1215
- }
1216
-
1217
1313
  const config = args.config as Config;
1218
1314
 
1219
- // -- snip, extract --
1220
-
1221
- if (!args.local) {
1315
+ if (args.local) {
1316
+ throw new NotImplementedError(
1317
+ `local mode is not yet supported for this command`
1318
+ );
1319
+ } else {
1320
+ // -- snip, extract --
1222
1321
  const loggedIn = await loginOrRefreshIfRequired();
1223
1322
  if (!loggedIn) {
1224
1323
  // didn't login, let's just quit
1225
1324
  console.log("Did not login, quitting...");
1226
1325
  return;
1227
1326
  }
1327
+
1228
1328
  if (!config.account_id) {
1229
1329
  config.account_id = await getAccountId();
1230
1330
  if (!config.account_id) {
1231
- console.error("No account id found, quitting...");
1232
- return;
1331
+ throw new Error("No account id found, quitting...");
1233
1332
  }
1234
1333
  }
1235
- }
1236
-
1237
- // -- snip, end --
1334
+ // -- snip, end --
1238
1335
 
1239
- // TODO: we should show bindings if they exist for given ids
1336
+ // TODO: we should show bindings if they exist for given ids
1240
1337
 
1241
- console.log(
1242
- JSON.stringify(
1243
- await listNamespaces(config.account_id),
1244
- null,
1245
- " "
1246
- )
1247
- );
1338
+ console.log(
1339
+ JSON.stringify(
1340
+ await listNamespaces(config.account_id),
1341
+ null,
1342
+ " "
1343
+ )
1344
+ );
1345
+ }
1248
1346
  }
1249
1347
  )
1250
1348
  .command(
@@ -1271,27 +1369,23 @@ export async function main(argv: string[]): Promise<void> {
1271
1369
  });
1272
1370
  },
1273
1371
  async (args) => {
1274
- if (args.local) {
1275
- console.error(`local mode is not yet supported for this command`);
1276
- return;
1277
- }
1278
1372
  const config = args.config as Config;
1279
1373
 
1280
- const id =
1281
- args["namespace-id"] ||
1282
- (args.env
1283
- ? config.env[args.env] || {}
1284
- : config
1285
- ).kv_namespaces.find(
1286
- (namespace) => namespace.binding === args.binding
1287
- )[args.preview ? "preview_id" : "id"];
1288
- if (!id) {
1289
- throw new Error("Are you sure? id not found");
1290
- }
1291
-
1292
- // -- snip, extract --
1374
+ if (args.local) {
1375
+ throw new NotImplementedError(
1376
+ `local mode is not yet supported for this command`
1377
+ );
1378
+ } else {
1379
+ let id;
1380
+ try {
1381
+ id = getNamespaceId(args);
1382
+ } catch (e) {
1383
+ throw new CommandLineArgsError(
1384
+ "Not able to delete namespace.\n" + e.message
1385
+ );
1386
+ }
1293
1387
 
1294
- if (!args.local) {
1388
+ // -- snip, extract --
1295
1389
  const loggedIn = await loginOrRefreshIfRequired();
1296
1390
  if (!loggedIn) {
1297
1391
  // didn't login, let's just quit
@@ -1302,47 +1396,45 @@ export async function main(argv: string[]): Promise<void> {
1302
1396
  if (!config.account_id) {
1303
1397
  config.account_id = await getAccountId();
1304
1398
  if (!config.account_id) {
1305
- console.error("No account id found, quitting...");
1306
- return;
1399
+ throw new Error("No account id found, quitting...");
1307
1400
  }
1308
1401
  }
1309
- }
1310
-
1311
- // -- snip, end --
1402
+ // -- snip, end --
1312
1403
 
1313
- await cfetch<{ id: string }>(
1314
- `/accounts/${config.account_id}/storage/kv/namespaces/${id}`,
1315
- { method: "DELETE" }
1316
- );
1404
+ await fetchResult<{ id: string }>(
1405
+ `/accounts/${config.account_id}/storage/kv/namespaces/${id}`,
1406
+ { method: "DELETE" }
1407
+ );
1317
1408
 
1318
- // TODO: recommend they remove it from wrangler.toml
1409
+ // TODO: recommend they remove it from wrangler.toml
1319
1410
 
1320
- // test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1321
- // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1322
- // n
1323
- // 💁 Not deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1324
- // ➜ test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1325
- // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1326
- // y
1327
- // 🌀 Deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1328
- // ✨ Success
1329
- // ⚠️ Make sure to remove this "kv-namespace" entry from your configuration file!
1330
- // ➜ test-mf
1411
+ // test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1412
+ // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1413
+ // n
1414
+ // 💁 Not deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1415
+ // ➜ test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1416
+ // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1417
+ // y
1418
+ // 🌀 Deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1419
+ // ✨ Success
1420
+ // ⚠️ Make sure to remove this "kv-namespace" entry from your configuration file!
1421
+ // ➜ test-mf
1331
1422
 
1332
- // TODO: do it automatically
1423
+ // TODO: do it automatically
1333
1424
 
1334
- // TODO: delete the preview namespace as well?
1425
+ // TODO: delete the preview namespace as well?
1426
+ }
1335
1427
  }
1336
1428
  );
1337
1429
  }
1338
1430
  );
1339
1431
 
1340
1432
  // :key
1341
- yargs.command(
1433
+ wrangler.command(
1342
1434
  "kv:key",
1343
1435
  "🔑 Individually manage Workers KV key-value pairs",
1344
- (yargs) => {
1345
- return yargs
1436
+ (kvKeyYargs) => {
1437
+ return kvKeyYargs
1346
1438
  .command(
1347
1439
  "put <key> [value]",
1348
1440
  "Writes a single key/value pair to the given namespace.",
@@ -1351,6 +1443,7 @@ export async function main(argv: string[]): Promise<void> {
1351
1443
  .positional("key", {
1352
1444
  type: "string",
1353
1445
  describe: "The key to write to.",
1446
+ demandOption: true,
1354
1447
  })
1355
1448
  .positional("value", {
1356
1449
  type: "string",
@@ -1390,9 +1483,11 @@ export async function main(argv: string[]): Promise<void> {
1390
1483
  },
1391
1484
  async ({ key, ttl, expiration, ...args }) => {
1392
1485
  const namespaceId = getNamespaceId(args);
1486
+ // One of `args.path` and `args.value` must be defined
1393
1487
  const value = args.path
1394
1488
  ? await readFile(args.path, "utf-8")
1395
- : args.value;
1489
+ : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1490
+ args.value!;
1396
1491
  const config = args.config as Config;
1397
1492
 
1398
1493
  if (args.path) {
@@ -1414,11 +1509,8 @@ export async function main(argv: string[]): Promise<void> {
1414
1509
  });
1415
1510
  const ns = await mf.getKVNamespace(namespaceId);
1416
1511
  await ns.put(key, value, { expiration, expirationTtl: ttl });
1417
- return;
1418
- }
1419
- // -- snip, extract --
1420
-
1421
- if (!args.local) {
1512
+ } else {
1513
+ // -- snip, extract --
1422
1514
  const loggedIn = await loginOrRefreshIfRequired();
1423
1515
  if (!loggedIn) {
1424
1516
  // didn't login, let's just quit
@@ -1429,18 +1521,16 @@ export async function main(argv: string[]): Promise<void> {
1429
1521
  if (!config.account_id) {
1430
1522
  config.account_id = await getAccountId();
1431
1523
  if (!config.account_id) {
1432
- console.error("No account id found, quitting...");
1433
- return;
1524
+ throw new Error("No account id found, quitting...");
1434
1525
  }
1435
1526
  }
1436
- }
1437
-
1438
- // -- snip, end --
1527
+ // -- snip, end --
1439
1528
 
1440
- await putKeyValue(config.account_id, namespaceId, key, value, {
1441
- expiration,
1442
- expiration_ttl: ttl,
1443
- });
1529
+ await putKeyValue(config.account_id, namespaceId, key, value, {
1530
+ expiration,
1531
+ expiration_ttl: ttl,
1532
+ });
1533
+ }
1444
1534
  }
1445
1535
  )
1446
1536
  .command(
@@ -1461,6 +1551,12 @@ export async function main(argv: string[]): Promise<void> {
1461
1551
  type: "string",
1462
1552
  describe: "Perform on a specific environment",
1463
1553
  })
1554
+ .option("preview", {
1555
+ type: "boolean",
1556
+ // In the case of listing keys we will default to non-preview mode
1557
+ default: false,
1558
+ describe: "Interact with a preview namespace",
1559
+ })
1464
1560
  .option("prefix", {
1465
1561
  type: "string",
1466
1562
  describe: "A prefix to filter listed keys",
@@ -1482,12 +1578,8 @@ export async function main(argv: string[]): Promise<void> {
1482
1578
  const ns = await mf.getKVNamespace(namespaceId);
1483
1579
  const listResponse = await ns.list({ prefix });
1484
1580
  console.log(JSON.stringify(listResponse.keys, null, " ")); // TODO: paginate, collate
1485
- return;
1486
- }
1487
-
1488
- // -- snip, extract --
1489
-
1490
- if (!args.local) {
1581
+ } else {
1582
+ // -- snip, extract --
1491
1583
  const loggedIn = await loginOrRefreshIfRequired();
1492
1584
  if (!loggedIn) {
1493
1585
  // didn't login, let's just quit
@@ -1498,17 +1590,18 @@ export async function main(argv: string[]): Promise<void> {
1498
1590
  if (!config.account_id) {
1499
1591
  config.account_id = await getAccountId();
1500
1592
  if (!config.account_id) {
1501
- console.error("No account id found, quitting...");
1502
- return;
1593
+ throw new Error("No account id found, quitting...");
1503
1594
  }
1504
1595
  }
1505
- }
1506
-
1507
- // -- snip, end --
1596
+ // -- snip, end --
1508
1597
 
1509
- console.log(
1510
- await listNamespaceKeys(config.account_id, namespaceId, prefix)
1511
- );
1598
+ const results = await listNamespaceKeys(
1599
+ config.account_id,
1600
+ namespaceId,
1601
+ prefix
1602
+ );
1603
+ console.log(JSON.stringify(results, undefined, 2));
1604
+ }
1512
1605
  }
1513
1606
  )
1514
1607
  .command(
@@ -1519,6 +1612,7 @@ export async function main(argv: string[]): Promise<void> {
1519
1612
  .positional("key", {
1520
1613
  describe: "The key value to get.",
1521
1614
  type: "string",
1615
+ demandOption: true,
1522
1616
  })
1523
1617
  .option("binding", {
1524
1618
  type: "string",
@@ -1536,6 +1630,12 @@ export async function main(argv: string[]): Promise<void> {
1536
1630
  .option("preview", {
1537
1631
  type: "boolean",
1538
1632
  describe: "Interact with a preview namespace",
1633
+ })
1634
+ .option("preview", {
1635
+ type: "boolean",
1636
+ // In the case of getting key values we will default to non-preview mode
1637
+ default: false,
1638
+ describe: "Interact with a preview namespace",
1539
1639
  });
1540
1640
  },
1541
1641
  async ({ key, ...args }) => {
@@ -1554,9 +1654,8 @@ export async function main(argv: string[]): Promise<void> {
1554
1654
  return;
1555
1655
  }
1556
1656
 
1557
- // -- snip, extract --
1558
-
1559
1657
  if (!args.local) {
1658
+ // -- snip, extract --
1560
1659
  const loggedIn = await loginOrRefreshIfRequired();
1561
1660
  if (!loggedIn) {
1562
1661
  // didn't login, let's just quit
@@ -1567,20 +1666,14 @@ export async function main(argv: string[]): Promise<void> {
1567
1666
  if (!config.account_id) {
1568
1667
  config.account_id = await getAccountId();
1569
1668
  if (!config.account_id) {
1570
- console.error("No account id found, quitting...");
1571
- return;
1669
+ throw new Error("No account id found, quitting...");
1572
1670
  }
1573
1671
  }
1672
+ // -- snip, end --
1574
1673
  }
1575
1674
 
1576
- // -- snip, end --
1577
-
1578
- // annoyingly, the API for this one doesn't return the
1579
- // data in the 'standard' format. goddammit.
1580
- // That's why we have the fallthrough response in cfetch.
1581
- // Oh well.
1582
1675
  console.log(
1583
- await cfetch(
1676
+ await fetchRaw(
1584
1677
  `/accounts/${config.account_id}/storage/kv/namespaces/${namespaceId}/values/${key}`
1585
1678
  )
1586
1679
  );
@@ -1594,6 +1687,7 @@ export async function main(argv: string[]): Promise<void> {
1594
1687
  .positional("key", {
1595
1688
  describe: "The key value to delete",
1596
1689
  type: "string",
1690
+ demandOption: true,
1597
1691
  })
1598
1692
  .option("binding", {
1599
1693
  type: "string",
@@ -1634,9 +1728,8 @@ export async function main(argv: string[]): Promise<void> {
1634
1728
 
1635
1729
  const config = args.config as Config;
1636
1730
 
1637
- // -- snip, extract --
1638
-
1639
1731
  if (!args.local) {
1732
+ // -- snip, extract --
1640
1733
  const loggedIn = await loginOrRefreshIfRequired();
1641
1734
  if (!loggedIn) {
1642
1735
  // didn't login, let's just quit
@@ -1647,15 +1740,13 @@ export async function main(argv: string[]): Promise<void> {
1647
1740
  if (!config.account_id) {
1648
1741
  config.account_id = await getAccountId();
1649
1742
  if (!config.account_id) {
1650
- console.error("No account id found, quitting...");
1651
- return;
1743
+ throw new Error("No account id found, quitting...");
1652
1744
  }
1653
1745
  }
1746
+ // -- snip, end --
1654
1747
  }
1655
1748
 
1656
- // -- snip, end --
1657
-
1658
- await cfetch(
1749
+ await fetchResult(
1659
1750
  `/accounts/${config.account_id}/storage/kv/namespaces/${namespaceId}/values/${key}`,
1660
1751
  { method: "DELETE" }
1661
1752
  );
@@ -1665,11 +1756,11 @@ export async function main(argv: string[]): Promise<void> {
1665
1756
  );
1666
1757
 
1667
1758
  // :bulk
1668
- yargs.command(
1759
+ wrangler.command(
1669
1760
  "kv:bulk",
1670
1761
  "💪 Interact with multiple Workers KV key-value pairs at once",
1671
- (yargs) => {
1672
- return yargs
1762
+ (kvBulkYargs) => {
1763
+ return kvBulkYargs
1673
1764
  .command(
1674
1765
  "put <filename>",
1675
1766
  "Upload multiple key-value pairs to a namespace",
@@ -1678,6 +1769,7 @@ export async function main(argv: string[]): Promise<void> {
1678
1769
  .positional("filename", {
1679
1770
  describe: `The JSON file of key-value pairs to upload, in form [{"key":..., "value":...}"...]`,
1680
1771
  type: "string",
1772
+ demandOption: true,
1681
1773
  })
1682
1774
  .option("binding", {
1683
1775
  type: "string",
@@ -1709,8 +1801,9 @@ export async function main(argv: string[]): Promise<void> {
1709
1801
  try {
1710
1802
  parsedContent = JSON.parse(content);
1711
1803
  } catch (err) {
1712
- console.error(`could not parse json from ${filename}`);
1713
- throw err;
1804
+ throw new Error(
1805
+ `Could not parse json from ${filename}.\n${err.message ?? err}`
1806
+ );
1714
1807
  }
1715
1808
 
1716
1809
  if (args.local) {
@@ -1732,13 +1825,8 @@ export async function main(argv: string[]): Promise<void> {
1732
1825
  expirationTtl: expiration_ttl,
1733
1826
  });
1734
1827
  }
1735
-
1736
- return;
1737
- }
1738
-
1739
- // -- snip, extract --
1740
-
1741
- if (!args.local) {
1828
+ } else {
1829
+ // -- snip, extract --
1742
1830
  const loggedIn = await loginOrRefreshIfRequired();
1743
1831
  if (!loggedIn) {
1744
1832
  // didn't login, let's just quit
@@ -1749,27 +1837,26 @@ export async function main(argv: string[]): Promise<void> {
1749
1837
  if (!config.account_id) {
1750
1838
  config.account_id = await getAccountId();
1751
1839
  if (!config.account_id) {
1752
- console.error("No account id found, quitting...");
1753
- return;
1840
+ throw new Error("No account id found, quitting...");
1754
1841
  }
1755
1842
  }
1756
- }
1843
+ // -- snip, end --
1757
1844
 
1758
- // -- snip, end --
1759
-
1760
- console.log(
1761
- await putBulkKeyValue(config.account_id, namespaceId, content)
1762
- );
1845
+ console.log(
1846
+ await putBulkKeyValue(config.account_id, namespaceId, content)
1847
+ );
1848
+ }
1763
1849
  }
1764
1850
  )
1765
1851
  .command(
1766
1852
  "delete <filename>",
1767
- "Upload multiple key-value pairs to a namespace",
1853
+ "Delete multiple key-value pairs from a namespace",
1768
1854
  (yargs) => {
1769
1855
  return yargs
1770
1856
  .positional("filename", {
1771
- describe: `The JSON file of key-value pairs to upload, in form ["key1", "key2", ...]`,
1857
+ describe: `The JSON file of keys to delete, in the form ["key1", "key2", ...]`,
1772
1858
  type: "string",
1859
+ demandOption: true,
1773
1860
  })
1774
1861
  .option("binding", {
1775
1862
  type: "string",
@@ -1797,8 +1884,9 @@ export async function main(argv: string[]): Promise<void> {
1797
1884
  try {
1798
1885
  parsedContent = JSON.parse(content);
1799
1886
  } catch (err) {
1800
- console.error(`could not parse json from ${filename}`);
1801
- throw err;
1887
+ throw new Error(
1888
+ `Could not parse json from ${filename}.\n${err.message ?? err}`
1889
+ );
1802
1890
  }
1803
1891
 
1804
1892
  if (args.local) {
@@ -1812,13 +1900,8 @@ export async function main(argv: string[]): Promise<void> {
1812
1900
  for (const key of parsedContent) {
1813
1901
  await ns.delete(key);
1814
1902
  }
1815
-
1816
- return;
1817
- }
1818
-
1819
- // -- snip, extract --
1820
-
1821
- if (!args.local) {
1903
+ } else {
1904
+ // -- snip, extract --
1822
1905
  const loggedIn = await loginOrRefreshIfRequired();
1823
1906
  if (!loggedIn) {
1824
1907
  // didn't login, let's just quit
@@ -1829,25 +1912,27 @@ export async function main(argv: string[]): Promise<void> {
1829
1912
  if (!config.account_id) {
1830
1913
  config.account_id = await getAccountId();
1831
1914
  if (!config.account_id) {
1832
- console.error("No account id found, quitting...");
1833
- return;
1915
+ throw new Error("No account id found, quitting...");
1834
1916
  }
1835
1917
  }
1836
- }
1837
-
1838
- // -- snip, end --
1918
+ // -- snip, end --
1839
1919
 
1840
- console.log(
1841
- await deleteBulkKeyValue(config.account_id, namespaceId, content)
1842
- );
1920
+ console.log(
1921
+ await deleteBulkKeyValue(
1922
+ config.account_id,
1923
+ namespaceId,
1924
+ content
1925
+ )
1926
+ );
1927
+ }
1843
1928
  }
1844
1929
  );
1845
1930
  }
1846
1931
  );
1847
1932
 
1848
- yargs.command("pages", "⚡️ Configure Cloudflare Pages", pages);
1933
+ wrangler.command("pages", "⚡️ Configure Cloudflare Pages", pages);
1849
1934
 
1850
- yargs
1935
+ wrangler
1851
1936
  .option("config", {
1852
1937
  alias: "c",
1853
1938
  describe: "Path to .toml configuration file",
@@ -1863,12 +1948,28 @@ export async function main(argv: string[]): Promise<void> {
1863
1948
  default: false, // I bet this will a point of contention. We'll revisit it.
1864
1949
  });
1865
1950
 
1866
- yargs.group(["config", "help", "version"], "Flags:");
1867
- yargs.help().alias("h", "help");
1868
- yargs.version(wranglerVersion).alias("v", "version");
1869
- yargs.exitProcess(false);
1951
+ wrangler.group(["config", "help", "version"], "Flags:");
1952
+ wrangler.help().alias("h", "help");
1953
+ wrangler.version(wranglerVersion).alias("v", "version");
1954
+ wrangler.exitProcess(false);
1870
1955
 
1871
1956
  await initialiseUserConfig();
1872
1957
 
1873
- await yargs.parse(argv);
1958
+ try {
1959
+ await wrangler.parse();
1960
+ } catch (e) {
1961
+ if (e instanceof CommandLineArgsError) {
1962
+ wrangler.showHelp("error");
1963
+ console.error(""); // Just adds a bit of space
1964
+ console.error(e.message);
1965
+ } else {
1966
+ console.error(e.message);
1967
+ console.error(""); // Just adds a bit of space
1968
+ console.error(
1969
+ `${fgGreenColor}%s${resetColor}`,
1970
+ "If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
1971
+ );
1972
+ }
1973
+ throw e;
1974
+ }
1874
1975
  }