wrangler 0.0.0-e14e589 → 0.0.0-e2a26a0

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 (61) hide show
  1. package/README.md +51 -0
  2. package/bin/wrangler.js +2 -2
  3. package/package.json +49 -30
  4. package/pages/functions/buildWorker.ts +62 -0
  5. package/pages/functions/filepath-routing.test.ts +47 -0
  6. package/pages/functions/filepath-routing.ts +231 -0
  7. package/pages/functions/identifiers.ts +78 -0
  8. package/pages/functions/routes.ts +160 -0
  9. package/pages/functions/template-worker.ts +136 -0
  10. package/src/__tests__/dev.test.tsx +71 -0
  11. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  12. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  13. package/src/__tests__/helpers/mock-account-id.ts +30 -0
  14. package/src/__tests__/helpers/mock-bin.ts +33 -0
  15. package/src/__tests__/helpers/mock-cfetch.ts +207 -0
  16. package/src/__tests__/helpers/mock-console.ts +56 -0
  17. package/src/__tests__/helpers/mock-dialogs.ts +65 -0
  18. package/src/__tests__/helpers/mock-kv.ts +40 -0
  19. package/src/__tests__/helpers/mock-user.ts +27 -0
  20. package/src/__tests__/helpers/run-in-tmp.ts +29 -0
  21. package/src/__tests__/helpers/run-wrangler.ts +14 -0
  22. package/src/__tests__/index.test.ts +509 -119
  23. package/src/__tests__/jest.setup.ts +37 -0
  24. package/src/__tests__/kv.test.ts +1139 -0
  25. package/src/__tests__/logout.test.ts +50 -0
  26. package/src/__tests__/package-manager.test.ts +206 -0
  27. package/src/__tests__/publish.test.ts +1222 -0
  28. package/src/__tests__/secret.test.ts +210 -0
  29. package/src/__tests__/whoami.test.tsx +86 -0
  30. package/src/api/form_data.ts +85 -76
  31. package/src/api/preview.ts +29 -24
  32. package/src/api/worker.ts +50 -35
  33. package/src/cfetch/index.ts +102 -0
  34. package/src/cfetch/internal.ts +101 -0
  35. package/src/cli.ts +6 -7
  36. package/src/config.ts +630 -85
  37. package/src/dev.tsx +446 -282
  38. package/src/dialogs.tsx +6 -14
  39. package/src/index.tsx +773 -426
  40. package/src/inspect.ts +596 -0
  41. package/src/kv.tsx +118 -45
  42. package/src/module-collection.ts +63 -0
  43. package/src/package-manager.ts +120 -0
  44. package/src/pages.tsx +917 -211
  45. package/src/paths.ts +26 -0
  46. package/src/proxy.ts +370 -0
  47. package/src/publish.ts +220 -88
  48. package/src/sites.tsx +180 -55
  49. package/src/tail.tsx +6 -4
  50. package/src/user.tsx +77 -87
  51. package/src/whoami.tsx +69 -0
  52. package/templates/new-worker.js +15 -0
  53. package/templates/new-worker.ts +15 -0
  54. package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
  55. package/wrangler-dist/cli.js +125330 -557
  56. package/wrangler-dist/cli.js.map +3 -3
  57. package/src/__tests__/fixtures/init/.gitkeep +0 -0
  58. package/src/__tests__/mock-cfetch.js +0 -46
  59. package/src/api/inspect.ts +0 -441
  60. package/src/cfetch.ts +0 -72
  61. package/src/util/fetch.ts +0 -74
package/src/index.tsx CHANGED
@@ -1,23 +1,19 @@
1
- import React from "react";
1
+ import * as fs from "node:fs";
2
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { setTimeout } from "node:timers/promises";
5
+ import TOML from "@iarna/toml";
6
+ import { findUp } from "find-up";
2
7
  import { render } from "ink";
3
- import { Dev } from "./dev";
4
- import { readFile } from "node:fs/promises";
8
+ import React from "react";
9
+ import onExit from "signal-exit";
5
10
  import makeCLI from "yargs";
6
- import { hideBin } from "yargs/helpers";
7
- import type yargs from "yargs";
8
- import { findUp } from "find-up";
9
- import TOML from "@iarna/toml";
10
- import type { Config } from "./config";
11
- import { confirm, prompt } from "./dialogs";
12
11
  import { version as wranglerVersion } from "../package.json";
13
- import {
14
- login,
15
- logout,
16
- listScopes,
17
- initialise as initialiseUserConfig,
18
- loginOrRefreshIfRequired,
19
- getAccountId,
20
- } from "./user";
12
+ import { toFormData } from "./api/form_data";
13
+ import { fetchResult } from "./cfetch";
14
+ import { normaliseAndValidateEnvironmentsConfig } from "./config";
15
+ import Dev from "./dev";
16
+ import { confirm, prompt } from "./dialogs";
21
17
  import {
22
18
  getNamespaceId,
23
19
  listNamespaces,
@@ -25,41 +21,58 @@ import {
25
21
  putKeyValue,
26
22
  putBulkKeyValue,
27
23
  deleteBulkKeyValue,
24
+ createNamespace,
25
+ isValidNamespaceBinding,
26
+ getKeyValue,
28
27
  } from "./kv";
29
-
28
+ import { getPackageManager } from "./package-manager";
30
29
  import { pages } from "./pages";
31
-
32
- import cfetch from "./cfetch";
33
-
34
30
  import publish from "./publish";
35
- import path from "path/posix";
36
- import { writeFile } from "node:fs/promises";
37
- import { toFormData } from "./api/form_data";
38
-
31
+ import { getAssetPaths } from "./sites";
39
32
  import { createTail } from "./tail";
40
- import onExit from "signal-exit";
41
- import { setTimeout } from "node:timers/promises";
42
- import * as fs from "node:fs";
43
- import execa from "execa";
33
+ import {
34
+ login,
35
+ logout,
36
+ listScopes,
37
+ initialise as initialiseUserConfig,
38
+ loginOrRefreshIfRequired,
39
+ getAccountId,
40
+ validateScopeKeys,
41
+ } from "./user";
42
+ import { whoami } from "./whoami";
43
+
44
+ import type { Config } from "./config";
45
+ import type Yargs from "yargs";
44
46
 
45
- async function readConfig(path?: string): Promise<Config> {
47
+ const resetColor = "\x1b[0m";
48
+ const fgGreenColor = "\x1b[32m";
49
+
50
+ async function readConfig(configPath?: string): Promise<Config> {
46
51
  const config: Config = {};
47
- if (!path) {
48
- path = await findUp("wrangler.toml");
52
+ if (!configPath) {
53
+ configPath = await findUp("wrangler.toml");
49
54
  // TODO - terminate this early instead of going all the way to the root
50
55
  }
51
56
 
52
- if (path) {
53
- const tml: string = await readFile(path, "utf-8");
57
+ if (configPath) {
58
+ const tml: string = await readFile(configPath, "utf-8");
54
59
  const parsed = TOML.parse(tml) as Config;
55
60
  Object.assign(config, parsed);
56
61
  }
57
62
 
63
+ normaliseAndValidateEnvironmentsConfig(config);
64
+
65
+ if ("experimental_services" in config) {
66
+ 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"
68
+ );
69
+ }
70
+
58
71
  // todo: validate, add defaults
59
72
  // let's just do some basics for now
60
73
 
61
74
  // @ts-expect-error we're being sneaky here for now
62
- config.__path__ = path;
75
+ config.__path__ = configPath;
63
76
 
64
77
  return config;
65
78
  }
@@ -67,18 +80,18 @@ async function readConfig(path?: string): Promise<Config> {
67
80
  // a helper to demand one of a set of options
68
81
  // via https://github.com/yargs/yargs/issues/1093#issuecomment-491299261
69
82
  function demandOneOfOption(...options: string[]) {
70
- return function (argv: yargs.Arguments) {
83
+ return function (argv: Yargs.Arguments) {
71
84
  const count = options.filter((option) => argv[option]).length;
72
85
  const lastOption = options.pop();
73
86
 
74
87
  if (count === 0) {
75
- throw new Error(
88
+ throw new CommandLineArgsError(
76
89
  `Exactly one of the arguments ${options.join(
77
90
  ", "
78
91
  )} and ${lastOption} is required`
79
92
  );
80
93
  } else if (count > 1) {
81
- throw new Error(
94
+ throw new CommandLineArgsError(
82
95
  `Arguments ${options.join(
83
96
  ", "
84
97
  )} and ${lastOption} are mutually exclusive`
@@ -89,30 +102,49 @@ function demandOneOfOption(...options: string[]) {
89
102
  };
90
103
  }
91
104
 
105
+ class CommandLineArgsError extends Error {}
106
+ class DeprecationError extends Error {}
107
+ class NotImplementedError extends Error {}
108
+
92
109
  export async function main(argv: string[]): Promise<void> {
93
- const yargs = makeCLI(hideBin(process.argv))
94
- .command(
95
- // the default is to simply print the help menu
96
- ["*"],
97
- false,
98
- () => {},
99
- () => {
100
- yargs.showHelp("log");
110
+ const wrangler = makeCLI(argv)
111
+ // We handle errors ourselves in a try-catch around `yargs.parse`.
112
+ // If you want the "help info" to be displayed then throw an instance of `CommandLineArgsError`.
113
+ // Otherwise we just log the error that was thrown without any "help info".
114
+ .showHelpOnFail(false)
115
+ .fail((msg, error) => {
116
+ if (!error) {
117
+ // If there is only a `msg` then this came from yargs own validation, so wrap in a `CommandLineArgsError`.
118
+ error = new CommandLineArgsError(msg);
101
119
  }
102
- )
120
+ throw error;
121
+ })
103
122
  .scriptName("wrangler")
104
123
  .wrap(null);
105
124
 
106
- // you will note that we use the form for all commands where we use the builder function
107
- // to define options and subcommands. Further we return the result of this builder even
108
- // tho it's not completely necessary. The reason is that it's required for type inference
109
- // of the args in the handle function.I wish we could enforce this pattern, but this
110
- // comment will have to do for now.
125
+ // the default is to simply print the help menu
126
+ wrangler.command(
127
+ ["*"],
128
+ false,
129
+ () => {},
130
+ (args) => {
131
+ if (args._.length > 0) {
132
+ throw new CommandLineArgsError(`Unknown command: ${args._}.`);
133
+ } else {
134
+ wrangler.showHelp("log");
135
+ }
136
+ }
137
+ );
111
138
 
112
- // also annoying that choices[] doesn't get inferred as an enum. bleh.
139
+ // You will note that we use the form for all commands where we use the builder function
140
+ // to define options and subcommands.
141
+ // Further we return the result of this builder even though it's not completely necessary.
142
+ // The reason is that it's required for type inference of the args in the handle function.
143
+ // I wish we could enforce this pattern, but this comment will have to do for now.
144
+ // (It's also annoying that choices[] doesn't get inferred as an enum. 🤷‍♂.)
113
145
 
114
146
  // [DEPRECATED] generate
115
- yargs.command(
147
+ wrangler.command(
116
148
  // we definitely want to move away from us cloning github templates
117
149
  // we can do something better here, let's see
118
150
  "generate [name] [template]",
@@ -130,14 +162,14 @@ export async function main(argv: string[]): Promise<void> {
130
162
  },
131
163
  () => {
132
164
  // "👯 [DEPRECATED]. Scaffold a Cloudflare Workers project from a public GitHub repository.",
133
- console.error(
165
+ throw new DeprecationError(
134
166
  "`wrangler generate` has been deprecated, please refer to TODO://some/path for alternatives"
135
167
  );
136
168
  }
137
169
  );
138
170
 
139
171
  // init
140
- yargs.command(
172
+ wrangler.command(
141
173
  "init [name]",
142
174
  "📥 Create a wrangler.toml configuration file",
143
175
  (yargs) => {
@@ -146,66 +178,115 @@ export async function main(argv: string[]): Promise<void> {
146
178
  type: "string",
147
179
  });
148
180
  },
149
- async () => {
150
- const destination = path.join(process.cwd(), "wrangler.toml");
181
+ async (args) => {
182
+ if ("type" in args) {
183
+ let message = "The --type option is no longer supported.";
184
+ if (args.type === "webpack") {
185
+ message +=
186
+ "\nIf you wish to use webpack then you will need to create a custom build.";
187
+ // TODO: Add a link to docs
188
+ }
189
+ throw new CommandLineArgsError(message);
190
+ }
191
+
192
+ const packageManager = await getPackageManager(process.cwd());
193
+
194
+ const destination = "./wrangler.toml";
151
195
  if (fs.existsSync(destination)) {
152
- console.error(`${destination} already exists.`);
196
+ console.warn(`${destination} file already exists!`);
197
+ const shouldContinue = await confirm(
198
+ "Do you want to continue initializing this project?"
199
+ );
200
+ if (!shouldContinue) {
201
+ return;
202
+ }
153
203
  } else {
154
204
  const compatibilityDate = new Date().toISOString().substring(0, 10);
155
205
  try {
156
206
  await writeFile(
157
207
  destination,
158
- `compatibility_date = "${compatibilityDate}"
159
- `
208
+ `compatibility_date = "${compatibilityDate}"` + "\n"
160
209
  );
161
- console.log(`✨ Succesfully created wrangler.toml`);
210
+ console.log(`✨ Successfully created wrangler.toml`);
162
211
  // TODO: suggest next steps?
163
212
  } catch (err) {
164
- console.error(`Failed to create wrangler.toml`);
165
- console.error(err);
166
- throw err;
213
+ throw new Error(
214
+ `Failed to create wrangler.toml.\n${err.message ?? err}`
215
+ );
167
216
  }
168
217
  }
169
218
 
170
- // if no package.json, ask, and if yes, create one
171
219
  let pathToPackageJson = await findUp("package.json");
172
-
173
220
  if (!pathToPackageJson) {
174
- if (
175
- await confirm("No package.json found. Would you like to create one?")
176
- ) {
221
+ // 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
+ );
225
+ if (shouldCreatePackageJson) {
177
226
  await writeFile(
178
- path.join(process.cwd(), "package.json"),
227
+ "./package.json",
179
228
  JSON.stringify(
180
229
  {
181
230
  name: "worker",
182
- version: "0.0.1",
231
+ version: "0.0.0",
232
+ devDependencies: {
233
+ wrangler: wranglerVersion,
234
+ },
235
+ private: true,
183
236
  },
184
237
  null,
185
238
  " "
186
239
  ) + "\n"
187
240
  );
241
+ await packageManager.install();
188
242
  console.log(`✨ Created package.json`);
189
243
  pathToPackageJson = path.join(process.cwd(), "package.json");
190
244
  } else {
191
245
  return;
192
246
  }
247
+ } else {
248
+ // If package.json exists and wrangler isn't installed,
249
+ // then ask to add wrangler to devDependencies
250
+ const packageJson = JSON.parse(
251
+ await readFile(pathToPackageJson, "utf-8")
252
+ );
253
+ if (
254
+ !(
255
+ packageJson.devDependencies?.wrangler ||
256
+ packageJson.dependencies?.wrangler
257
+ )
258
+ ) {
259
+ const shouldInstall = await confirm(
260
+ "Would you like to install wrangler into your package.json?"
261
+ );
262
+ if (shouldInstall) {
263
+ await packageManager.addDevDeps(`wrangler@${wranglerVersion}`);
264
+ console.log(`✨ Installed wrangler`);
265
+ }
266
+ }
193
267
  }
194
268
 
195
- // if workers-types doesn't exist as a dependency
196
- // offer to install it
197
- // and make a tsconfig?
269
+ let isTypescriptProject = false;
198
270
  let pathToTSConfig = await findUp("tsconfig.json");
199
271
  if (!pathToTSConfig) {
200
- if (await confirm("Would you like to use typescript?")) {
272
+ // If there's no tsconfig, offer to create one
273
+ // and install @cloudflare/workers-types
274
+ if (await confirm("Would you like to use TypeScript?")) {
275
+ isTypescriptProject = true;
201
276
  await writeFile(
202
- path.join(process.cwd(), "tsconfig.json"),
277
+ "./tsconfig.json",
203
278
  JSON.stringify(
204
279
  {
205
280
  compilerOptions: {
206
- target: "ES2020",
207
- module: "CommonJS",
208
- lib: ["ES2020"],
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,
209
290
  types: ["@cloudflare/workers-types"],
210
291
  },
211
292
  },
@@ -213,22 +294,85 @@ export async function main(argv: string[]): Promise<void> {
213
294
  " "
214
295
  ) + "\n"
215
296
  );
216
- await execa("npm", [
217
- "install",
297
+ await packageManager.addDevDeps(
218
298
  "@cloudflare/workers-types",
219
- "--save-dev",
220
- ]);
299
+ "typescript"
300
+ );
301
+
221
302
  console.log(
222
303
  `✨ Created tsconfig.json, installed @cloudflare/workers-types into devDependencies`
223
304
  );
224
305
  pathToTSConfig = path.join(process.cwd(), "tsconfig.json");
225
306
  }
307
+ } else {
308
+ isTypescriptProject = true;
309
+ // If there's a tsconfig, check if @cloudflare/workers-types
310
+ // is already installed, and offer to install it if not
311
+ const packageJson = JSON.parse(
312
+ await readFile(pathToPackageJson, "utf-8")
313
+ );
314
+ if (
315
+ !(
316
+ packageJson.devDependencies?.["@cloudflare/workers-types"] ||
317
+ packageJson.dependencies?.["@cloudflare/workers-types"]
318
+ )
319
+ ) {
320
+ const shouldInstall = await confirm(
321
+ "Would you like to install the type definitions for Workers into your package.json?"
322
+ );
323
+ if (shouldInstall) {
324
+ await packageManager.addDevDeps("@cloudflare/workers-types");
325
+ // We don't update the tsconfig.json because
326
+ // it could be complicated in existing projects
327
+ // and we don't want to break them. Instead, we simply
328
+ // tell the user that they need to update their tsconfig.json
329
+ console.log(
330
+ `✨ Installed @cloudflare/workers-types.\nPlease add "@cloudflare/workers-types" to compilerOptions.types in your tsconfig.json`
331
+ );
332
+ }
333
+ }
334
+ }
335
+
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?`
340
+ );
341
+ if (shouldCreateSource) {
342
+ await mkdir("./src", { recursive: true });
343
+ await writeFile(
344
+ "./src/index.ts",
345
+ await readFile(
346
+ path.join(__dirname, "../templates/new-worker.ts"),
347
+ "utf-8"
348
+ )
349
+ );
350
+ console.log(`✨ Created src/index.ts`);
351
+ }
352
+ }
353
+ } else {
354
+ if (!fs.existsSync("./src/index.js")) {
355
+ const shouldCreateSource = await confirm(
356
+ `Would you like to create a Worker at src/index.js?`
357
+ );
358
+ if (shouldCreateSource) {
359
+ await mkdir("./src", { recursive: true });
360
+ await writeFile(
361
+ path.join("./src/index.js"),
362
+ await readFile(
363
+ path.join(__dirname, "../templates/new-worker.js"),
364
+ "utf-8"
365
+ )
366
+ );
367
+ console.log(`✨ Created src/index.js`);
368
+ }
369
+ }
226
370
  }
227
371
  }
228
372
  );
229
373
 
230
374
  // build
231
- yargs.command(
375
+ wrangler.command(
232
376
  "build",
233
377
  false,
234
378
  (yargs) => {
@@ -238,14 +382,14 @@ export async function main(argv: string[]): Promise<void> {
238
382
  },
239
383
  () => {
240
384
  // "[DEPRECATED] 🦀 Build your project (if applicable)",
241
- console.error(
385
+ throw new DeprecationError(
242
386
  "`wrangler build` has been deprecated, please refer to TODO://some/path for alternatives"
243
387
  );
244
388
  }
245
389
  );
246
390
 
247
391
  // login
248
- yargs.command(
392
+ wrangler.command(
249
393
  // this needs scopes as an option?
250
394
  "login",
251
395
  false, // we don't need to show this in the menu
@@ -276,19 +420,24 @@ export async function main(argv: string[]): Promise<void> {
276
420
  listScopes();
277
421
  return;
278
422
  }
423
+ if (!validateScopeKeys(args.scopes)) {
424
+ throw new CommandLineArgsError(
425
+ `One of ${args.scopes} is not a valid authentication scope. Run "wrangler login --list-scopes" to see the valid scopes.`
426
+ );
427
+ }
279
428
  await login({ scopes: args.scopes });
280
429
  return;
281
430
  }
282
431
  await login();
283
432
 
284
433
  // TODO: would be nice if it optionally saved login
285
- // creds inside node_modules/.cache or something
434
+ // credentials inside node_modules/.cache or something
286
435
  // this way you could have multiple users on a single machine
287
436
  }
288
437
  );
289
438
 
290
439
  // logout
291
- yargs.command(
440
+ wrangler.command(
292
441
  // this needs scopes as an option?
293
442
  "logout",
294
443
  false, // we don't need to show this in the menu
@@ -300,36 +449,39 @@ export async function main(argv: string[]): Promise<void> {
300
449
  );
301
450
 
302
451
  // whoami
303
- yargs.command(
452
+ wrangler.command(
304
453
  "whoami",
305
- false, // we don't need to show this the menu
306
- // "🕵️ Retrieve your user info and test your auth config",
454
+ "🕵️ Retrieve your user info and test your auth config",
307
455
  () => {},
308
- (args) => {
309
- console.log(":whoami", args);
456
+ async () => {
457
+ await whoami();
310
458
  }
311
459
  );
312
460
 
313
461
  // config
314
- yargs.command(
462
+ wrangler.command(
315
463
  "config",
316
464
  false,
317
465
  () => {},
318
466
  () => {
319
467
  // "🕵️ Authenticate Wrangler with a Cloudflare API Token",
320
- console.error(
468
+ throw new DeprecationError(
321
469
  "`wrangler config` has been deprecated, please refer to TODO://some/path for alternatives"
322
470
  );
323
471
  }
324
472
  );
325
473
 
326
474
  // dev
327
- yargs.command(
475
+ wrangler.command(
328
476
  "dev <filename>",
329
477
  "👂 Start a local server for developing your worker",
330
478
  (yargs) => {
331
479
  return yargs
332
- .positional("filename", { describe: "entry point", type: "string" })
480
+ .positional("filename", {
481
+ describe: "entry point",
482
+ type: "string",
483
+ demandOption: true,
484
+ })
333
485
  .option("name", {
334
486
  describe: "name of the script",
335
487
  type: "string",
@@ -343,6 +495,20 @@ export async function main(argv: string[]): Promise<void> {
343
495
  type: "string",
344
496
  // TODO: get choices for the toml file?
345
497
  })
498
+ .option("compatibility-date", {
499
+ describe: "Date to use for compatibility checks",
500
+ type: "string",
501
+ })
502
+ .option("compatibility-flags", {
503
+ describe: "Flags to use for compatibility checks",
504
+ type: "array",
505
+ alias: "compatibility-flag",
506
+ })
507
+ .option("latest", {
508
+ describe: "Use the latest version of the worker runtime",
509
+ type: "boolean",
510
+ default: true,
511
+ })
346
512
  .option("ip", {
347
513
  describe: "IP address to listen on",
348
514
  type: "string",
@@ -356,14 +522,14 @@ export async function main(argv: string[]): Promise<void> {
356
522
  .option("host", {
357
523
  type: "string",
358
524
  describe:
359
- "Host to forward requests to, defaults to the zone of project or to tutorial.cloudflareworkers.com if unauthenticated",
525
+ "Host to forward requests to, defaults to the zone of project",
360
526
  })
361
527
  .option("local-protocol", {
362
528
  default: "http",
363
529
  describe: "Protocol to listen to requests on, defaults to http.",
364
530
  choices: ["http", "https"],
365
531
  })
366
- .option("public", {
532
+ .option("experimental-public", {
367
533
  describe: "Static assets to be served",
368
534
  type: "string",
369
535
  })
@@ -371,72 +537,159 @@ export async function main(argv: string[]): Promise<void> {
371
537
  describe: "Root folder of static assets for Workers Sites",
372
538
  type: "string",
373
539
  })
540
+ .option("site-include", {
541
+ describe:
542
+ "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.",
543
+ type: "string",
544
+ array: true,
545
+ })
546
+ .option("site-exclude", {
547
+ describe:
548
+ "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.",
549
+ type: "string",
550
+ array: true,
551
+ })
374
552
  .option("upstream-protocol", {
375
553
  default: "https",
376
554
  describe:
377
555
  "Protocol to forward requests to host on, defaults to https.",
378
556
  choices: ["http", "https"],
557
+ })
558
+ .option("jsx-factory", {
559
+ describe: "The function that is called for each JSX element",
560
+ type: "string",
561
+ })
562
+ .option("jsx-fragment", {
563
+ describe: "The function that is called for each JSX fragment",
564
+ type: "string",
565
+ })
566
+ .option("experimental-enable-local-persistence", {
567
+ describe: "Enable persistence for this session (only for local mode)",
568
+ type: "boolean",
379
569
  });
380
570
  },
381
571
  async (args) => {
382
572
  const { filename, format } = args;
383
573
  const config = args.config as Config;
384
574
 
385
- // -- snip, extract --
575
+ if (args["experimental-public"]) {
576
+ console.warn(
577
+ "🚨 The --experimental-public field is experimental and will change in the future."
578
+ );
579
+ }
580
+
581
+ if (args.public) {
582
+ throw new Error(
583
+ "🚨 The --public field has been renamed to --experimental-public, and will change behaviour in the future."
584
+ );
585
+ }
586
+
587
+ if (config.site?.["entry-point"]) {
588
+ console.warn(
589
+ "Deprecation notice: The `site.entry-point` config field is no longer used.\n" +
590
+ "The entry-point is specified via the command line (e.g. `wrangler dev path/to/script`).\n" +
591
+ "Please remove the `site.entry-point` field from the `wrangler.toml` file."
592
+ );
593
+ }
386
594
 
387
595
  if (!args.local) {
596
+ // -- snip, extract --
388
597
  const loggedIn = await loginOrRefreshIfRequired();
389
598
  if (!loggedIn) {
390
599
  // didn't login, let's just quit
391
600
  console.log("Did not login, quitting...");
392
601
  return;
393
602
  }
603
+
394
604
  if (!config.account_id) {
395
605
  config.account_id = await getAccountId();
396
606
  if (!config.account_id) {
397
- console.error("No account id found, quitting...");
398
- return;
607
+ throw new Error("No account id found, quitting...");
399
608
  }
400
609
  }
610
+ // -- snip, end --
401
611
  }
402
612
 
403
- // -- snip, end --
613
+ const environments = config.env ?? {};
614
+ const envRootObj = args.env ? environments[args.env] || {} : config;
404
615
 
405
- const envRootObj = args.env ? config[`env.${args.env}`] : config;
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
+ }
406
626
 
407
- render(
627
+ const { waitUntilExit } = render(
408
628
  <Dev
409
629
  name={args.name || config.name}
410
- entry={filename}
630
+ entry={path.relative(process.cwd(), filename)}
631
+ env={args.env}
632
+ buildCommand={config.build || {}}
411
633
  format={format}
412
634
  initialMode={args.local ? "local" : "remote"}
635
+ jsxFactory={args["jsx-factory"] || envRootObj?.jsx_factory}
636
+ jsxFragment={args["jsx-fragment"] || envRootObj?.jsx_fragment}
637
+ enableLocalPersistence={
638
+ args["experimental-enable-local-persistence"] || false
639
+ }
413
640
  accountId={config.account_id}
414
- site={args.site || config.site?.bucket}
641
+ assetPaths={getAssetPaths(
642
+ config,
643
+ args.site,
644
+ args.siteInclude,
645
+ args.siteExclude
646
+ )}
415
647
  port={args.port || config.dev?.port}
416
- public={args.public}
417
- variables={{
418
- ...(envRootObj?.vars || {}),
419
- ...(envRootObj?.kv_namespaces || []).reduce(
420
- (obj, { binding, preview_id }) => {
648
+ public={args["experimental-public"]}
649
+ compatibilityDate={
650
+ args["compatibility-date"] ||
651
+ config.compatibility_date ||
652
+ new Date().toISOString().substring(0, 10)
653
+ }
654
+ compatibilityFlags={
655
+ (args["compatibility-flags"] as string[]) ||
656
+ config.compatibility_flags
657
+ }
658
+ usageModel={config.usage_model}
659
+ bindings={{
660
+ kv_namespaces: envRootObj.kv_namespaces?.map(
661
+ ({ binding, preview_id, id: _id }) => {
662
+ // In `dev`, we make folks use a separate kv namespace called
663
+ // `preview_id` instead of `id` so that they don't
664
+ // break production data. So here we check that a `preview_id`
665
+ // has actually been configured.
666
+ // This whole block of code will be obsoleted in the future
667
+ // when we have copy-on-write for previews on edge workers.
421
668
  if (!preview_id) {
422
669
  // TODO: This error has to be a _lot_ better, ideally just asking
423
670
  // to create a preview namespace for the user automatically
424
671
  throw new Error(
425
- "kv namespaces need a preview id during dev mode"
426
- );
672
+ `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`
673
+ ); // Ugh, I really don't like this message very much
427
674
  }
428
- return { ...obj, [binding]: { namespaceId: preview_id } };
429
- },
430
- {}
675
+ return {
676
+ binding,
677
+ id: preview_id,
678
+ };
679
+ }
431
680
  ),
681
+ vars: envRootObj.vars,
682
+ durable_objects: envRootObj.durable_objects,
683
+ services: envRootObj.experimental_services,
432
684
  }}
433
685
  />
434
686
  );
687
+ await waitUntilExit();
435
688
  }
436
689
  );
437
690
 
438
691
  // publish
439
- yargs.command(
692
+ wrangler.command(
440
693
  "publish [script]",
441
694
  "🆙 Publish your Worker to Cloudflare.",
442
695
  (yargs) => {
@@ -453,7 +706,21 @@ export async function main(argv: string[]): Promise<void> {
453
706
  describe: "name to use when uploading",
454
707
  type: "string",
455
708
  })
456
- .option("public", {
709
+ .option("compatibility-date", {
710
+ describe: "Date to use for compatibility checks",
711
+ type: "string",
712
+ })
713
+ .option("compatibility-flags", {
714
+ describe: "Flags to use for compatibility checks",
715
+ type: "array",
716
+ alias: "compatibility-flag",
717
+ })
718
+ .option("latest", {
719
+ describe: "Use the latest version of the worker runtime",
720
+ type: "boolean",
721
+ default: false,
722
+ })
723
+ .option("experimental-public", {
457
724
  describe: "Static assets to be served",
458
725
  type: "string",
459
726
  })
@@ -461,6 +728,18 @@ export async function main(argv: string[]): Promise<void> {
461
728
  describe: "Root folder of static assets for Workers Sites",
462
729
  type: "string",
463
730
  })
731
+ .option("site-include", {
732
+ describe:
733
+ "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.",
734
+ type: "string",
735
+ array: true,
736
+ })
737
+ .option("site-exclude", {
738
+ describe:
739
+ "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.",
740
+ type: "string",
741
+ array: true,
742
+ })
464
743
  .option("triggers", {
465
744
  describe: "cron schedules to attach",
466
745
  alias: ["schedule", "schedules"],
@@ -476,49 +755,89 @@ export async function main(argv: string[]): Promise<void> {
476
755
  type: "boolean",
477
756
  default: "false",
478
757
  hidden: true,
758
+ })
759
+ .option("jsx-factory", {
760
+ describe: "The function that is called for each JSX element",
761
+ type: "string",
762
+ })
763
+ .option("jsx-fragment", {
764
+ describe: "The function that is called for each JSX fragment",
765
+ type: "string",
479
766
  });
480
767
  },
481
768
  async (args) => {
482
769
  if (args.local) {
483
- console.error("🚫 Local publishing is not yet supported");
484
- return;
770
+ throw new NotImplementedError(
771
+ "🚫 Local publishing is not yet supported"
772
+ );
485
773
  }
774
+
775
+ if (args["experimental-public"]) {
776
+ console.warn(
777
+ "🚨 The --experimental-public field is experimental and will change in the future."
778
+ );
779
+ }
780
+ if (args.public) {
781
+ throw new Error(
782
+ "🚨 The --public field has been renamed to --experimental-public, and will change behaviour in the future."
783
+ );
784
+ }
785
+
486
786
  const config = args.config as Config;
487
787
 
488
- // -- snip, extract --
788
+ if (args.latest) {
789
+ console.warn(
790
+ "⚠️ 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"
791
+ );
792
+ }
793
+
489
794
  if (!args.local) {
795
+ // -- snip, extract --
490
796
  const loggedIn = await loginOrRefreshIfRequired();
491
797
  if (!loggedIn) {
492
798
  // didn't login, let's just quit
493
799
  console.log("Did not login, quitting...");
494
800
  return;
495
801
  }
802
+
496
803
  if (!config.account_id) {
497
804
  config.account_id = await getAccountId();
498
805
  if (!config.account_id) {
499
- console.error("No account id found, quitting...");
500
- return;
806
+ throw new Error("No account id found, quitting...");
501
807
  }
502
808
  }
809
+ // -- snip, end --
503
810
  }
504
811
 
505
- // -- snip, end --
506
-
812
+ const assetPaths = getAssetPaths(
813
+ config,
814
+ args["experimental-public"] || args.site,
815
+ args.siteInclude,
816
+ args.siteExclude
817
+ );
507
818
  await publish({
508
819
  config: args.config as Config,
509
820
  name: args.name,
510
821
  script: args.script,
511
822
  env: args.env,
823
+ compatibilityDate: args.latest
824
+ ? new Date().toISOString().substring(0, 10)
825
+ : args["compatibility-date"],
826
+ compatibilityFlags: args["compatibility-flags"] as string[],
512
827
  triggers: args.triggers,
828
+ jsxFactory: args["jsx-factory"],
829
+ jsxFragment: args["jsx-fragment"],
513
830
  routes: args.routes,
514
- public: args.public,
515
- site: args.site,
831
+ assetPaths,
832
+ format: undefined, // TODO: add args for this
833
+ legacyEnv: undefined, // TODO: get this from somewhere... config?
834
+ experimentalPublic: args["experimental-public"] !== undefined,
516
835
  });
517
836
  }
518
837
  );
519
838
 
520
839
  // tail
521
- yargs.command(
840
+ wrangler.command(
522
841
  "tail [name]",
523
842
  "🦚 Starts a log tailing session for a deployed Worker.",
524
843
  (yargs) => {
@@ -562,34 +881,35 @@ export async function main(argv: string[]): Promise<void> {
562
881
  // TODO: filter by client ip, which can be 'self' or an ip address
563
882
  },
564
883
  async (args) => {
884
+ if (args.local) {
885
+ throw new NotImplementedError(
886
+ `local mode is not yet supported for this command`
887
+ );
888
+ }
889
+
565
890
  const config = args.config as Config;
566
891
 
567
892
  if (!(args.name || config.name)) {
568
- console.error("Missing script name");
569
- return;
893
+ throw new Error("Missing script name");
570
894
  }
571
895
  const scriptName = `${args.name || config.name}${
572
896
  args.env ? `-${args.env}` : ""
573
897
  }`;
574
898
 
575
899
  // -- snip, extract --
900
+ const loggedIn = await loginOrRefreshIfRequired();
901
+ if (!loggedIn) {
902
+ // didn't login, let's just quit
903
+ console.log("Did not login, quitting...");
904
+ return;
905
+ }
576
906
 
577
- if (!args.local) {
578
- const loggedIn = await loginOrRefreshIfRequired();
579
- if (!loggedIn) {
580
- // didn't login, let's just quit
581
- console.log("Did not login, quitting...");
582
- return;
583
- }
907
+ if (!config.account_id) {
908
+ config.account_id = await getAccountId();
584
909
  if (!config.account_id) {
585
- config.account_id = await getAccountId();
586
- if (!config.account_id) {
587
- console.error("No account id found, quitting...");
588
- return;
589
- }
910
+ throw new Error("No account id found, quitting...");
590
911
  }
591
912
  }
592
-
593
913
  // -- snip, end --
594
914
 
595
915
  const accountId = config.account_id;
@@ -609,9 +929,9 @@ export async function main(argv: string[]): Promise<void> {
609
929
  `successfully created tail, expires at ${expiration.toLocaleString()}`
610
930
  );
611
931
 
612
- onExit(() => {
932
+ onExit(async () => {
613
933
  tail.terminate();
614
- deleteTail();
934
+ await deleteTail();
615
935
  });
616
936
 
617
937
  tail.on("message", (data) => {
@@ -636,7 +956,7 @@ export async function main(argv: string[]): Promise<void> {
636
956
  );
637
957
 
638
958
  // preview
639
- yargs.command(
959
+ wrangler.command(
640
960
  "preview [method] [body]",
641
961
  false,
642
962
  (yargs) => {
@@ -662,20 +982,20 @@ export async function main(argv: string[]): Promise<void> {
662
982
  });
663
983
  },
664
984
  () => {
665
- // "🔬 [DEPRECATED] Preview your code temporarily on cloudflareworkers.com"
666
- console.error(
985
+ // "🔬 [DEPRECATED] Preview your code temporarily on https://cloudflareworkers.com"
986
+ throw new DeprecationError(
667
987
  "`wrangler preview` has been deprecated, please refer to TODO://some/path for alternatives"
668
988
  );
669
989
  }
670
990
  );
671
991
 
672
992
  // route
673
- yargs.command(
993
+ wrangler.command(
674
994
  "route",
675
995
  false, // I think we want to hide this command
676
996
  // "➡️ List or delete worker routes",
677
- (yargs) => {
678
- return yargs
997
+ (routeYargs) => {
998
+ return routeYargs
679
999
  .command(
680
1000
  "list",
681
1001
  "List a route associated with a zone",
@@ -699,11 +1019,10 @@ export async function main(argv: string[]): Promise<void> {
699
1019
  // TODO: use environment (current wrangler doesn't do so?)
700
1020
  const zone = args.zone || (args.config as Config).zone_id;
701
1021
  if (!zone) {
702
- console.error("missing zone id");
703
- return;
1022
+ throw new Error("missing zone id");
704
1023
  }
705
1024
 
706
- console.log(await cfetch(`/zones/${zone}/workers/routes`));
1025
+ console.log(await fetchResult(`/zones/${zone}/workers/routes`));
707
1026
  }
708
1027
  )
709
1028
  .command(
@@ -733,7 +1052,7 @@ export async function main(argv: string[]): Promise<void> {
733
1052
  }
734
1053
 
735
1054
  console.log(
736
- await cfetch(`/zones/${zone}/workers/routes/${args.id}`, {
1055
+ await fetchResult(`/zones/${zone}/workers/routes/${args.id}`, {
737
1056
  method: "DELETE",
738
1057
  })
739
1058
  );
@@ -743,7 +1062,7 @@ export async function main(argv: string[]): Promise<void> {
743
1062
  );
744
1063
 
745
1064
  // subdomain
746
- yargs.command(
1065
+ wrangler.command(
747
1066
  "subdomain [name]",
748
1067
  false,
749
1068
  // "👷 Create or change your workers.dev subdomain.",
@@ -751,18 +1070,18 @@ export async function main(argv: string[]): Promise<void> {
751
1070
  return yargs.positional("name", { type: "string" });
752
1071
  },
753
1072
  () => {
754
- console.error(
1073
+ throw new DeprecationError(
755
1074
  "`wrangler subdomain` has been deprecated, please refer to TODO://some/path for alternatives"
756
1075
  );
757
1076
  }
758
1077
  );
759
1078
 
760
1079
  // secret
761
- yargs.command(
1080
+ wrangler.command(
762
1081
  "secret",
763
1082
  "🤫 Generate a secret that can be referenced in the worker script",
764
- (yargs) => {
765
- return yargs
1083
+ (secretYargs) => {
1084
+ return secretYargs
766
1085
  .command(
767
1086
  "put <key>",
768
1087
  "Create or update a secret variable for a script",
@@ -783,45 +1102,49 @@ export async function main(argv: string[]): Promise<void> {
783
1102
  });
784
1103
  },
785
1104
  async (args) => {
786
- if (args.local) {
787
- console.error("--local not implemented for this command yet");
788
- return;
789
- }
790
1105
  const config = args.config as Config;
791
1106
 
792
1107
  // TODO: use environment (how does current wrangler do it?)
793
1108
  const scriptName = args.name || config.name;
794
1109
  if (!scriptName) {
795
- console.error("Missing script name");
796
- return;
1110
+ throw new Error("Missing script name");
797
1111
  }
798
1112
 
799
- // -- snip, extract --
1113
+ if (args.local) {
1114
+ console.warn("`wrangler secret put` is a no-op in --local mode");
1115
+ }
800
1116
 
801
1117
  if (!args.local) {
1118
+ // -- snip, extract --
802
1119
  const loggedIn = await loginOrRefreshIfRequired();
803
1120
  if (!loggedIn) {
804
1121
  // didn't login, let's just quit
805
1122
  console.log("Did not login, quitting...");
806
1123
  return;
807
1124
  }
1125
+
808
1126
  if (!config.account_id) {
809
1127
  config.account_id = await getAccountId();
810
1128
  if (!config.account_id) {
811
- console.error("No account id found, quitting...");
812
- return;
1129
+ throw new Error("No account id found, quitting...");
813
1130
  }
814
1131
  }
1132
+ // -- snip, end --
815
1133
  }
816
1134
 
817
- // -- snip, end --
818
-
819
1135
  const secretValue = await prompt(
820
1136
  "Enter a secret value:",
821
1137
  "password"
822
1138
  );
1139
+
1140
+ if (args.local) {
1141
+ return;
1142
+ }
1143
+
1144
+ console.log(`🌀 Creating the secret for script ${scriptName}`);
1145
+
823
1146
  async function submitSecret() {
824
- return await cfetch(
1147
+ return await fetchResult(
825
1148
  `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/`,
826
1149
  {
827
1150
  method: "PUT",
@@ -836,32 +1159,41 @@ export async function main(argv: string[]): Promise<void> {
836
1159
  }
837
1160
 
838
1161
  try {
839
- console.log(await submitSecret());
1162
+ await submitSecret();
840
1163
  } catch (e) {
841
1164
  if (e.code === 10007) {
842
1165
  // upload a draft worker
843
- await cfetch(
1166
+ await fetchResult(
844
1167
  `/accounts/${config.account_id}/workers/scripts/${scriptName}`,
845
1168
  {
846
1169
  method: "PUT",
847
- // @ts-expect-error TODO: fix this error!
848
1170
  body: toFormData({
1171
+ name: scriptName,
849
1172
  main: {
850
1173
  name: scriptName,
851
1174
  content: `export default { fetch() {} }`,
852
1175
  type: "esm",
853
1176
  },
854
- variables: {},
1177
+ bindings: {
1178
+ kv_namespaces: [],
1179
+ vars: {},
1180
+ durable_objects: { bindings: [] },
1181
+ },
855
1182
  modules: [],
1183
+ migrations: undefined,
1184
+ compatibility_date: undefined,
1185
+ compatibility_flags: undefined,
1186
+ usage_model: undefined,
856
1187
  }),
857
1188
  }
858
1189
  );
859
1190
 
860
1191
  // and then try again
861
- console.log(await submitSecret());
1192
+ await submitSecret();
862
1193
  // TODO: delete the draft worker if this failed too?
863
1194
  }
864
1195
  }
1196
+ console.log(`✨ Success! Uploaded secret ${args.key}`);
865
1197
  }
866
1198
  )
867
1199
  .command(
@@ -884,10 +1216,6 @@ export async function main(argv: string[]): Promise<void> {
884
1216
  });
885
1217
  },
886
1218
  async (args) => {
887
- if (args.local) {
888
- console.error("--local not implemented for this command yet");
889
- return;
890
- }
891
1219
  const config = args.config as Config;
892
1220
 
893
1221
  // TODO: use environment (how does current wrangler do it?)
@@ -896,37 +1224,48 @@ export async function main(argv: string[]): Promise<void> {
896
1224
  throw new Error("Missing script name");
897
1225
  }
898
1226
 
899
- // -- snip, extract --
1227
+ if (args.local) {
1228
+ console.warn(
1229
+ "`wrangler secret delete` is a no-op in --local mode"
1230
+ );
1231
+ }
900
1232
 
901
1233
  if (!args.local) {
1234
+ // -- snip, extract --
902
1235
  const loggedIn = await loginOrRefreshIfRequired();
903
1236
  if (!loggedIn) {
904
1237
  // didn't login, let's just quit
905
1238
  console.log("Did not login, quitting...");
906
1239
  return;
907
1240
  }
1241
+
908
1242
  if (!config.account_id) {
909
1243
  config.account_id = await getAccountId();
910
1244
  if (!config.account_id) {
911
- console.error("No account id found, quitting...");
912
- return;
1245
+ throw new Error("No account id found, quitting...");
913
1246
  }
914
1247
  }
1248
+ // -- snip, end --
915
1249
  }
916
1250
 
917
- // -- snip, end --
918
-
919
- if (await confirm("Are you sure you want to delete this secret?")) {
1251
+ if (
1252
+ await confirm(
1253
+ `Are you sure you want to permanently delete the variable ${args.key} on the script ${scriptName}?`
1254
+ )
1255
+ ) {
920
1256
  console.log(
921
- `Deleting the secret ${args.key} on script ${scriptName}.`
1257
+ `🌀 Deleting the secret ${args.key} on script ${scriptName}.`
922
1258
  );
923
1259
 
924
- console.log(
925
- await cfetch(
926
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/${args.key}`,
927
- { method: "DELETE" }
928
- )
1260
+ if (args.local) {
1261
+ return;
1262
+ }
1263
+
1264
+ await fetchResult(
1265
+ `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/${args.key}`,
1266
+ { method: "DELETE" }
929
1267
  );
1268
+ console.log(`✨ Success! Deleted secret ${args.key}`);
930
1269
  }
931
1270
  }
932
1271
  )
@@ -946,42 +1285,47 @@ export async function main(argv: string[]): Promise<void> {
946
1285
  });
947
1286
  },
948
1287
  async (args) => {
949
- if (args.local) {
950
- console.error("--local not implemented for this command yet");
951
- return;
952
- }
953
1288
  const config = args.config as Config;
954
1289
 
955
1290
  // TODO: use environment (how does current wrangler do it?)
956
1291
  const scriptName = args.name || config.name;
957
1292
  if (!scriptName) {
958
- console.error("Missing script name");
959
- return;
1293
+ throw new Error("Missing script name");
960
1294
  }
961
1295
 
962
- // -- snip, extract --
1296
+ if (args.local) {
1297
+ console.warn("`wrangler secret list` is a no-op in --local mode");
1298
+ }
963
1299
 
964
1300
  if (!args.local) {
1301
+ // -- snip, extract --
965
1302
  const loggedIn = await loginOrRefreshIfRequired();
966
1303
  if (!loggedIn) {
967
1304
  // didn't login, let's just quit
968
1305
  console.log("Did not login, quitting...");
969
1306
  return;
970
1307
  }
1308
+
971
1309
  if (!config.account_id) {
972
1310
  config.account_id = await getAccountId();
973
1311
  if (!config.account_id) {
974
- console.error("No account id found, quitting...");
975
- return;
1312
+ throw new Error("No account id found, quitting...");
976
1313
  }
977
1314
  }
1315
+ // -- snip, end --
978
1316
  }
979
1317
 
980
- // -- snip, end --
1318
+ if (args.local) {
1319
+ return;
1320
+ }
981
1321
 
982
1322
  console.log(
983
- await cfetch(
984
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets`
1323
+ JSON.stringify(
1324
+ await fetchResult(
1325
+ `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets`
1326
+ ),
1327
+ null,
1328
+ " "
985
1329
  )
986
1330
  );
987
1331
  }
@@ -991,11 +1335,11 @@ export async function main(argv: string[]): Promise<void> {
991
1335
 
992
1336
  // kv
993
1337
  // :namespace
994
- yargs.command(
1338
+ wrangler.command(
995
1339
  "kv:namespace",
996
1340
  "🗂️ Interact with your Workers KV Namespaces",
997
- (yargs) => {
998
- return yargs
1341
+ (namespaceYargs) => {
1342
+ return namespaceYargs
999
1343
  .command(
1000
1344
  "create <namespace>",
1001
1345
  "Create a new namespace",
@@ -1004,6 +1348,7 @@ export async function main(argv: string[]): Promise<void> {
1004
1348
  .positional("namespace", {
1005
1349
  describe: "The name of the new namespace",
1006
1350
  type: "string",
1351
+ demandOption: true,
1007
1352
  })
1008
1353
  .option("env", {
1009
1354
  type: "string",
@@ -1015,13 +1360,19 @@ export async function main(argv: string[]): Promise<void> {
1015
1360
  });
1016
1361
  },
1017
1362
  async (args) => {
1018
- if (args._.length !== 2) {
1019
- throw new Error(
1020
- `Did you forget to add quotes around "${
1021
- args.namespace
1022
- } ${args._.slice(2).join(" ")}"?`
1363
+ if (args._.length > 2) {
1364
+ const extraArgs = args._.slice(2).join(" ");
1365
+ throw new CommandLineArgsError(
1366
+ `Unexpected additional positional arguments "${extraArgs}".`
1023
1367
  );
1024
1368
  }
1369
+
1370
+ if (!isValidNamespaceBinding(args.namespace)) {
1371
+ throw new CommandLineArgsError(
1372
+ `The namespace binding name "${args.namespace}" is invalid. It can only have alphanumeric and _ characters, and cannot begin with a number.`
1373
+ );
1374
+ }
1375
+
1025
1376
  const config = args.config as Config;
1026
1377
  if (!config.name) {
1027
1378
  console.warn(
@@ -1029,13 +1380,10 @@ export async function main(argv: string[]): Promise<void> {
1029
1380
  );
1030
1381
  }
1031
1382
 
1032
- const title = `${config.name || "worker"}${
1033
- args.env ? `-${args.env}` : ""
1034
- }-${args.namespace}${args.preview ? "_preview" : ""}`;
1035
-
1036
- if (/[\W]+/.test(args.namespace)) {
1037
- throw new Error("invalid binding name, needs to be js friendly");
1038
- }
1383
+ const name = config.name || "worker";
1384
+ const environment = args.env ? `-${args.env}` : "";
1385
+ const preview = args.preview ? "_preview" : "";
1386
+ const title = `${name}${environment}-${args.namespace}${preview}`;
1039
1387
 
1040
1388
  if (args.local) {
1041
1389
  const { Miniflare } = await import("miniflare");
@@ -1044,59 +1392,45 @@ export async function main(argv: string[]): Promise<void> {
1044
1392
  // TODO: these options shouldn't be required
1045
1393
  script: ` `, // has to be a string with at least one char
1046
1394
  });
1047
- mf.getKVNamespace(title); // this should "create" the namespace
1395
+ await mf.getKVNamespace(title); // this should "create" the namespace
1048
1396
  console.log(`✨ Success! Created KV namespace ${title}`);
1049
- return;
1050
- }
1051
-
1052
- // -- snip, extract --
1053
-
1054
- if (!args.local) {
1397
+ } else {
1398
+ // -- snip, extract --
1055
1399
  const loggedIn = await loginOrRefreshIfRequired();
1056
1400
  if (!loggedIn) {
1057
1401
  // didn't login, let's just quit
1058
1402
  console.log("Did not login, quitting...");
1059
1403
  return;
1060
1404
  }
1405
+
1061
1406
  if (!config.account_id) {
1062
1407
  config.account_id = await getAccountId();
1063
1408
  if (!config.account_id) {
1064
- console.error("No account id found, quitting...");
1065
- return;
1409
+ throw new Error("No account id found, quitting...");
1066
1410
  }
1067
1411
  }
1068
- }
1069
-
1070
- // -- snip, end --
1412
+ // -- snip, end --
1071
1413
 
1072
- // TODO: generate a binding name stripping non alphanumeric chars
1414
+ // TODO: generate a binding name stripping non alphanumeric chars
1073
1415
 
1074
- console.log(`🌀 Creating namespace with title "${title}"`);
1416
+ console.log(`🌀 Creating namespace with title "${title}"`);
1417
+ const namespaceId = await createNamespace(
1418
+ config.account_id,
1419
+ title
1420
+ );
1075
1421
 
1076
- const response = await cfetch<{ id: string }>(
1077
- `/accounts/${config.account_id}/storage/kv/namespaces`,
1078
- {
1079
- method: "POST",
1080
- headers: {
1081
- "Content-Type": "application/json",
1082
- },
1083
- body: JSON.stringify({
1084
- title,
1085
- }),
1086
- }
1087
- );
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
+ );
1088
1431
 
1089
- console.log("✨ Success!");
1090
- console.log(
1091
- `Add the following to your configuration file in your kv_namespaces array${
1092
- args.env ? ` under [env.${args.env}]` : ""
1093
- }:`
1094
- );
1095
- console.log(
1096
- `{ binding = "${args.namespace}", ${
1097
- args.preview ? "preview_" : ""
1098
- }id = ${response.id} }`
1099
- );
1432
+ // TODO: automatically write this block to the wrangler.toml config file??
1433
+ }
1100
1434
  }
1101
1435
  )
1102
1436
  .command(
@@ -1104,42 +1438,39 @@ export async function main(argv: string[]): Promise<void> {
1104
1438
  "Outputs a list of all KV namespaces associated with your account id.",
1105
1439
  {},
1106
1440
  async (args) => {
1107
- if (args.local) {
1108
- console.error(`local mode is not yet supported for this command`);
1109
- return;
1110
- }
1111
-
1112
1441
  const config = args.config as Config;
1113
1442
 
1114
- // -- snip, extract --
1115
-
1116
- if (!args.local) {
1443
+ if (args.local) {
1444
+ throw new NotImplementedError(
1445
+ `local mode is not yet supported for this command`
1446
+ );
1447
+ } else {
1448
+ // -- snip, extract --
1117
1449
  const loggedIn = await loginOrRefreshIfRequired();
1118
1450
  if (!loggedIn) {
1119
1451
  // didn't login, let's just quit
1120
1452
  console.log("Did not login, quitting...");
1121
1453
  return;
1122
1454
  }
1455
+
1123
1456
  if (!config.account_id) {
1124
1457
  config.account_id = await getAccountId();
1125
1458
  if (!config.account_id) {
1126
- console.error("No account id found, quitting...");
1127
- return;
1459
+ throw new Error("No account id found, quitting...");
1128
1460
  }
1129
1461
  }
1130
- }
1462
+ // -- snip, end --
1131
1463
 
1132
- // -- snip, end --
1464
+ // TODO: we should show bindings if they exist for given ids
1133
1465
 
1134
- // TODO: we should show bindings if they exist for given ids
1135
-
1136
- console.log(
1137
- JSON.stringify(
1138
- await listNamespaces(config.account_id),
1139
- null,
1140
- " "
1141
- )
1142
- );
1466
+ console.log(
1467
+ JSON.stringify(
1468
+ await listNamespaces(config.account_id),
1469
+ null,
1470
+ " "
1471
+ )
1472
+ );
1473
+ }
1143
1474
  }
1144
1475
  )
1145
1476
  .command(
@@ -1166,27 +1497,23 @@ export async function main(argv: string[]): Promise<void> {
1166
1497
  });
1167
1498
  },
1168
1499
  async (args) => {
1169
- if (args.local) {
1170
- console.error(`local mode is not yet supported for this command`);
1171
- return;
1172
- }
1173
1500
  const config = args.config as Config;
1174
1501
 
1175
- const id =
1176
- args["namespace-id"] ||
1177
- (args.env
1178
- ? config[`env.${args.env}`]
1179
- : config
1180
- ).kv_namespaces.find(
1181
- (namespace) => namespace.binding === args.binding
1182
- )[args.preview ? "preview_id" : "id"];
1183
- if (!id) {
1184
- throw new Error("Are you sure? id not found");
1185
- }
1186
-
1187
- // -- snip, extract --
1502
+ if (args.local) {
1503
+ throw new NotImplementedError(
1504
+ `local mode is not yet supported for this command`
1505
+ );
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
+ }
1188
1515
 
1189
- if (!args.local) {
1516
+ // -- snip, extract --
1190
1517
  const loggedIn = await loginOrRefreshIfRequired();
1191
1518
  if (!loggedIn) {
1192
1519
  // didn't login, let's just quit
@@ -1197,55 +1524,54 @@ export async function main(argv: string[]): Promise<void> {
1197
1524
  if (!config.account_id) {
1198
1525
  config.account_id = await getAccountId();
1199
1526
  if (!config.account_id) {
1200
- console.error("No account id found, quitting...");
1201
- return;
1527
+ throw new Error("No account id found, quitting...");
1202
1528
  }
1203
1529
  }
1204
- }
1530
+ // -- snip, end --
1205
1531
 
1206
- // -- snip, end --
1207
-
1208
- await cfetch<{ id: string }>(
1209
- `/accounts/${config.account_id}/storage/kv/namespaces/${id}`,
1210
- { method: "DELETE" }
1211
- );
1532
+ await fetchResult<{ id: string }>(
1533
+ `/accounts/${config.account_id}/storage/kv/namespaces/${id}`,
1534
+ { method: "DELETE" }
1535
+ );
1212
1536
 
1213
- // TODO: recommend they remove it from wrangler.toml
1537
+ // TODO: recommend they remove it from wrangler.toml
1214
1538
 
1215
- // test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1216
- // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1217
- // n
1218
- // 💁 Not deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1219
- // ➜ test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1220
- // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1221
- // y
1222
- // 🌀 Deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1223
- // ✨ Success
1224
- // ⚠️ Make sure to remove this "kv-namespace" entry from your configuration file!
1225
- // ➜ test-mf
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
1226
1550
 
1227
- // TODO: do it automatically
1551
+ // TODO: do it automatically
1228
1552
 
1229
- // TODO: delete the preview namespace as well?
1553
+ // TODO: delete the preview namespace as well?
1554
+ }
1230
1555
  }
1231
1556
  );
1232
1557
  }
1233
1558
  );
1234
1559
 
1235
1560
  // :key
1236
- yargs.command(
1561
+ wrangler.command(
1237
1562
  "kv:key",
1238
1563
  "🔑 Individually manage Workers KV key-value pairs",
1239
- (yargs) => {
1240
- return yargs
1564
+ (kvKeyYargs) => {
1565
+ return kvKeyYargs
1241
1566
  .command(
1242
- "put <key> <value>",
1567
+ "put <key> [value]",
1243
1568
  "Writes a single key/value pair to the given namespace.",
1244
1569
  (yargs) => {
1245
1570
  return yargs
1246
1571
  .positional("key", {
1247
1572
  type: "string",
1248
1573
  describe: "The key to write to.",
1574
+ demandOption: true,
1249
1575
  })
1250
1576
  .positional("value", {
1251
1577
  type: "string",
@@ -1280,16 +1606,28 @@ export async function main(argv: string[]): Promise<void> {
1280
1606
  .option("path", {
1281
1607
  type: "string",
1282
1608
  describe: "Read value from the file at a given path.",
1283
- });
1609
+ })
1610
+ .check(demandOneOfOption("value", "path"));
1284
1611
  },
1285
1612
  async ({ key, ttl, expiration, ...args }) => {
1286
1613
  const namespaceId = getNamespaceId(args);
1614
+ // One of `args.path` and `args.value` must be defined
1287
1615
  const value = args.path
1288
1616
  ? await readFile(args.path, "utf-8")
1289
- : args.value;
1617
+ : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1618
+ args.value!;
1290
1619
  const config = args.config as Config;
1291
1620
 
1292
- console.log(`writing ${key}=${value} to namespace ${namespaceId}`);
1621
+ if (args.path) {
1622
+ console.log(
1623
+ `writing the contents of ${args.path} to the key "${key}" on namespace ${namespaceId}`
1624
+ );
1625
+ } else {
1626
+ console.log(
1627
+ `writing the value "${value}" to key "${key}" on namespace ${namespaceId}`
1628
+ );
1629
+ }
1630
+
1293
1631
  if (args.local) {
1294
1632
  const { Miniflare } = await import("miniflare");
1295
1633
  const mf = new Miniflare({
@@ -1299,11 +1637,8 @@ export async function main(argv: string[]): Promise<void> {
1299
1637
  });
1300
1638
  const ns = await mf.getKVNamespace(namespaceId);
1301
1639
  await ns.put(key, value, { expiration, expirationTtl: ttl });
1302
- return;
1303
- }
1304
- // -- snip, extract --
1305
-
1306
- if (!args.local) {
1640
+ } else {
1641
+ // -- snip, extract --
1307
1642
  const loggedIn = await loginOrRefreshIfRequired();
1308
1643
  if (!loggedIn) {
1309
1644
  // didn't login, let's just quit
@@ -1314,18 +1649,16 @@ export async function main(argv: string[]): Promise<void> {
1314
1649
  if (!config.account_id) {
1315
1650
  config.account_id = await getAccountId();
1316
1651
  if (!config.account_id) {
1317
- console.error("No account id found, quitting...");
1318
- return;
1652
+ throw new Error("No account id found, quitting...");
1319
1653
  }
1320
1654
  }
1321
- }
1322
-
1323
- // -- snip, end --
1655
+ // -- snip, end --
1324
1656
 
1325
- await putKeyValue(config.account_id, namespaceId, key, value, {
1326
- expiration,
1327
- expiration_ttl: ttl,
1328
- });
1657
+ await putKeyValue(config.account_id, namespaceId, key, value, {
1658
+ expiration,
1659
+ expiration_ttl: ttl,
1660
+ });
1661
+ }
1329
1662
  }
1330
1663
  )
1331
1664
  .command(
@@ -1346,6 +1679,12 @@ export async function main(argv: string[]): Promise<void> {
1346
1679
  type: "string",
1347
1680
  describe: "Perform on a specific environment",
1348
1681
  })
1682
+ .option("preview", {
1683
+ type: "boolean",
1684
+ // In the case of listing keys we will default to non-preview mode
1685
+ default: false,
1686
+ describe: "Interact with a preview namespace",
1687
+ })
1349
1688
  .option("prefix", {
1350
1689
  type: "string",
1351
1690
  describe: "A prefix to filter listed keys",
@@ -1367,12 +1706,8 @@ export async function main(argv: string[]): Promise<void> {
1367
1706
  const ns = await mf.getKVNamespace(namespaceId);
1368
1707
  const listResponse = await ns.list({ prefix });
1369
1708
  console.log(JSON.stringify(listResponse.keys, null, " ")); // TODO: paginate, collate
1370
- return;
1371
- }
1372
-
1373
- // -- snip, extract --
1374
-
1375
- if (!args.local) {
1709
+ } else {
1710
+ // -- snip, extract --
1376
1711
  const loggedIn = await loginOrRefreshIfRequired();
1377
1712
  if (!loggedIn) {
1378
1713
  // didn't login, let's just quit
@@ -1383,17 +1718,18 @@ export async function main(argv: string[]): Promise<void> {
1383
1718
  if (!config.account_id) {
1384
1719
  config.account_id = await getAccountId();
1385
1720
  if (!config.account_id) {
1386
- console.error("No account id found, quitting...");
1387
- return;
1721
+ throw new Error("No account id found, quitting...");
1388
1722
  }
1389
1723
  }
1390
- }
1391
-
1392
- // -- snip, end --
1724
+ // -- snip, end --
1393
1725
 
1394
- console.log(
1395
- await listNamespaceKeys(config.account_id, namespaceId, prefix)
1396
- );
1726
+ const results = await listNamespaceKeys(
1727
+ config.account_id,
1728
+ namespaceId,
1729
+ prefix
1730
+ );
1731
+ console.log(JSON.stringify(results, undefined, 2));
1732
+ }
1397
1733
  }
1398
1734
  )
1399
1735
  .command(
@@ -1404,6 +1740,7 @@ export async function main(argv: string[]): Promise<void> {
1404
1740
  .positional("key", {
1405
1741
  describe: "The key value to get.",
1406
1742
  type: "string",
1743
+ demandOption: true,
1407
1744
  })
1408
1745
  .option("binding", {
1409
1746
  type: "string",
@@ -1421,6 +1758,12 @@ export async function main(argv: string[]): Promise<void> {
1421
1758
  .option("preview", {
1422
1759
  type: "boolean",
1423
1760
  describe: "Interact with a preview namespace",
1761
+ })
1762
+ .option("preview", {
1763
+ type: "boolean",
1764
+ // In the case of getting key values we will default to non-preview mode
1765
+ default: false,
1766
+ describe: "Interact with a preview namespace",
1424
1767
  });
1425
1768
  },
1426
1769
  async ({ key, ...args }) => {
@@ -1439,9 +1782,8 @@ export async function main(argv: string[]): Promise<void> {
1439
1782
  return;
1440
1783
  }
1441
1784
 
1442
- // -- snip, extract --
1443
-
1444
1785
  if (!args.local) {
1786
+ // -- snip, extract --
1445
1787
  const loggedIn = await loginOrRefreshIfRequired();
1446
1788
  if (!loggedIn) {
1447
1789
  // didn't login, let's just quit
@@ -1452,23 +1794,15 @@ export async function main(argv: string[]): Promise<void> {
1452
1794
  if (!config.account_id) {
1453
1795
  config.account_id = await getAccountId();
1454
1796
  if (!config.account_id) {
1455
- console.error("No account id found, quitting...");
1456
- return;
1797
+ throw new Error("No account id found, quitting...");
1457
1798
  }
1458
1799
  }
1459
- }
1460
-
1461
- // -- snip, end --
1800
+ // -- snip, end --
1462
1801
 
1463
- // annoyingly, the API for this one doesn't return the
1464
- // data in the 'standard' format. goddamit.
1465
- // That's why we have the fallthrough response in cfetch.
1466
- // Oh well.
1467
- console.log(
1468
- await cfetch(
1469
- `/accounts/${config.account_id}/storage/kv/namespaces/${namespaceId}/values/${key}`
1470
- )
1471
- );
1802
+ console.log(
1803
+ await getKeyValue(config.account_id, namespaceId, key)
1804
+ );
1805
+ }
1472
1806
  }
1473
1807
  )
1474
1808
  .command(
@@ -1479,6 +1813,7 @@ export async function main(argv: string[]): Promise<void> {
1479
1813
  .positional("key", {
1480
1814
  describe: "The key value to delete",
1481
1815
  type: "string",
1816
+ demandOption: true,
1482
1817
  })
1483
1818
  .option("binding", {
1484
1819
  type: "string",
@@ -1501,6 +1836,10 @@ export async function main(argv: string[]): Promise<void> {
1501
1836
  async ({ key, ...args }) => {
1502
1837
  const namespaceId = getNamespaceId(args);
1503
1838
 
1839
+ console.log(
1840
+ `deleting the key "${key}" on namespace ${namespaceId}`
1841
+ );
1842
+
1504
1843
  if (args.local) {
1505
1844
  const { Miniflare } = await import("miniflare");
1506
1845
  const mf = new Miniflare({
@@ -1515,9 +1854,8 @@ export async function main(argv: string[]): Promise<void> {
1515
1854
 
1516
1855
  const config = args.config as Config;
1517
1856
 
1518
- // -- snip, extract --
1519
-
1520
1857
  if (!args.local) {
1858
+ // -- snip, extract --
1521
1859
  const loggedIn = await loginOrRefreshIfRequired();
1522
1860
  if (!loggedIn) {
1523
1861
  // didn't login, let's just quit
@@ -1528,15 +1866,13 @@ export async function main(argv: string[]): Promise<void> {
1528
1866
  if (!config.account_id) {
1529
1867
  config.account_id = await getAccountId();
1530
1868
  if (!config.account_id) {
1531
- console.error("No account id found, quitting...");
1532
- return;
1869
+ throw new Error("No account id found, quitting...");
1533
1870
  }
1534
1871
  }
1872
+ // -- snip, end --
1535
1873
  }
1536
1874
 
1537
- // -- snip, end --
1538
-
1539
- await cfetch(
1875
+ await fetchResult(
1540
1876
  `/accounts/${config.account_id}/storage/kv/namespaces/${namespaceId}/values/${key}`,
1541
1877
  { method: "DELETE" }
1542
1878
  );
@@ -1546,11 +1882,11 @@ export async function main(argv: string[]): Promise<void> {
1546
1882
  );
1547
1883
 
1548
1884
  // :bulk
1549
- yargs.command(
1885
+ wrangler.command(
1550
1886
  "kv:bulk",
1551
1887
  "💪 Interact with multiple Workers KV key-value pairs at once",
1552
- (yargs) => {
1553
- return yargs
1888
+ (kvBulkYargs) => {
1889
+ return kvBulkYargs
1554
1890
  .command(
1555
1891
  "put <filename>",
1556
1892
  "Upload multiple key-value pairs to a namespace",
@@ -1559,6 +1895,7 @@ export async function main(argv: string[]): Promise<void> {
1559
1895
  .positional("filename", {
1560
1896
  describe: `The JSON file of key-value pairs to upload, in form [{"key":..., "value":...}"...]`,
1561
1897
  type: "string",
1898
+ demandOption: true,
1562
1899
  })
1563
1900
  .option("binding", {
1564
1901
  type: "string",
@@ -1590,8 +1927,9 @@ export async function main(argv: string[]): Promise<void> {
1590
1927
  try {
1591
1928
  parsedContent = JSON.parse(content);
1592
1929
  } catch (err) {
1593
- console.error(`could not parse json from ${filename}`);
1594
- throw err;
1930
+ throw new Error(
1931
+ `Could not parse json from ${filename}.\n${err.message ?? err}`
1932
+ );
1595
1933
  }
1596
1934
 
1597
1935
  if (args.local) {
@@ -1613,13 +1951,8 @@ export async function main(argv: string[]): Promise<void> {
1613
1951
  expirationTtl: expiration_ttl,
1614
1952
  });
1615
1953
  }
1616
-
1617
- return;
1618
- }
1619
-
1620
- // -- snip, extract --
1621
-
1622
- if (!args.local) {
1954
+ } else {
1955
+ // -- snip, extract --
1623
1956
  const loggedIn = await loginOrRefreshIfRequired();
1624
1957
  if (!loggedIn) {
1625
1958
  // didn't login, let's just quit
@@ -1630,27 +1963,26 @@ export async function main(argv: string[]): Promise<void> {
1630
1963
  if (!config.account_id) {
1631
1964
  config.account_id = await getAccountId();
1632
1965
  if (!config.account_id) {
1633
- console.error("No account id found, quitting...");
1634
- return;
1966
+ throw new Error("No account id found, quitting...");
1635
1967
  }
1636
1968
  }
1637
- }
1638
-
1639
- // -- snip, end --
1969
+ // -- snip, end --
1640
1970
 
1641
- console.log(
1642
- await putBulkKeyValue(config.account_id, namespaceId, content)
1643
- );
1971
+ console.log(
1972
+ await putBulkKeyValue(config.account_id, namespaceId, content)
1973
+ );
1974
+ }
1644
1975
  }
1645
1976
  )
1646
1977
  .command(
1647
1978
  "delete <filename>",
1648
- "Upload multiple key-value pairs to a namespace",
1979
+ "Delete multiple key-value pairs from a namespace",
1649
1980
  (yargs) => {
1650
1981
  return yargs
1651
1982
  .positional("filename", {
1652
- describe: `The JSON file of key-value pairs to upload, in form ["key1", "key2", ...]`,
1983
+ describe: `The JSON file of keys to delete, in the form ["key1", "key2", ...]`,
1653
1984
  type: "string",
1985
+ demandOption: true,
1654
1986
  })
1655
1987
  .option("binding", {
1656
1988
  type: "string",
@@ -1678,8 +2010,9 @@ export async function main(argv: string[]): Promise<void> {
1678
2010
  try {
1679
2011
  parsedContent = JSON.parse(content);
1680
2012
  } catch (err) {
1681
- console.error(`could not parse json from ${filename}`);
1682
- throw err;
2013
+ throw new Error(
2014
+ `Could not parse json from ${filename}.\n${err.message ?? err}`
2015
+ );
1683
2016
  }
1684
2017
 
1685
2018
  if (args.local) {
@@ -1693,13 +2026,8 @@ export async function main(argv: string[]): Promise<void> {
1693
2026
  for (const key of parsedContent) {
1694
2027
  await ns.delete(key);
1695
2028
  }
1696
-
1697
- return;
1698
- }
1699
-
1700
- // -- snip, extract --
1701
-
1702
- if (!args.local) {
2029
+ } else {
2030
+ // -- snip, extract --
1703
2031
  const loggedIn = await loginOrRefreshIfRequired();
1704
2032
  if (!loggedIn) {
1705
2033
  // didn't login, let's just quit
@@ -1710,26 +2038,29 @@ export async function main(argv: string[]): Promise<void> {
1710
2038
  if (!config.account_id) {
1711
2039
  config.account_id = await getAccountId();
1712
2040
  if (!config.account_id) {
1713
- console.error("No account id found, quitting...");
1714
- return;
2041
+ throw new Error("No account id found, quitting...");
1715
2042
  }
1716
2043
  }
1717
- }
1718
-
1719
- // -- snip, end --
2044
+ // -- snip, end --
1720
2045
 
1721
- console.log(
1722
- await deleteBulkKeyValue(config.account_id, namespaceId, content)
1723
- );
2046
+ console.log(
2047
+ await deleteBulkKeyValue(
2048
+ config.account_id,
2049
+ namespaceId,
2050
+ content
2051
+ )
2052
+ );
2053
+ }
1724
2054
  }
1725
2055
  );
1726
2056
  }
1727
2057
  );
1728
2058
 
1729
- yargs.command("pages", "⚡️ Configure Cloudflare Pages", pages);
2059
+ wrangler.command("pages", "⚡️ Configure Cloudflare Pages", pages);
1730
2060
 
1731
- yargs
2061
+ wrangler
1732
2062
  .option("config", {
2063
+ alias: "c",
1733
2064
  describe: "Path to .toml configuration file",
1734
2065
  type: "string",
1735
2066
  async coerce(arg) {
@@ -1737,18 +2068,34 @@ export async function main(argv: string[]): Promise<void> {
1737
2068
  },
1738
2069
  })
1739
2070
  .option("local", {
2071
+ alias: "l",
1740
2072
  describe: "Run on my machine",
1741
2073
  type: "boolean",
1742
2074
  default: false, // I bet this will a point of contention. We'll revisit it.
1743
2075
  });
1744
2076
 
1745
- yargs.group(["config", "help", "version"], "Flags:");
2077
+ wrangler.group(["config", "help", "version"], "Flags:");
2078
+ wrangler.help().alias("h", "help");
2079
+ wrangler.version(wranglerVersion).alias("v", "version");
2080
+ wrangler.exitProcess(false);
1746
2081
 
1747
2082
  await initialiseUserConfig();
1748
- await yargs.parse(argv, (err, argv, output) => {
1749
- if (output) {
1750
- console.log(output);
2083
+
2084
+ try {
2085
+ await wrangler.parse();
2086
+ } catch (e) {
2087
+ if (e instanceof CommandLineArgsError) {
2088
+ wrangler.showHelp("error");
2089
+ console.error(""); // Just adds a bit of space
2090
+ console.error(e.message);
2091
+ } else {
2092
+ console.error(e.message);
2093
+ console.error(""); // Just adds a bit of space
2094
+ console.error(
2095
+ `${fgGreenColor}%s${resetColor}`,
2096
+ "If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
2097
+ );
1751
2098
  }
1752
- });
1753
- yargs.version(wranglerVersion);
2099
+ throw e;
2100
+ }
1754
2101
  }