wrangler 0.0.13 → 0.0.17

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 (67) hide show
  1. package/bin/wrangler.js +2 -2
  2. package/package.json +20 -11
  3. package/pages/functions/buildWorker.ts +1 -1
  4. package/pages/functions/filepath-routing.test.ts +112 -28
  5. package/pages/functions/filepath-routing.ts +44 -51
  6. package/pages/functions/routes.ts +11 -18
  7. package/pages/functions/template-worker.ts +3 -9
  8. package/src/__tests__/dev.test.tsx +42 -5
  9. package/src/__tests__/guess-worker-format.test.ts +66 -0
  10. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  11. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  12. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  13. package/src/__tests__/helpers/mock-account-id.ts +30 -0
  14. package/src/__tests__/helpers/mock-bin.ts +36 -0
  15. package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +43 -9
  16. package/src/__tests__/helpers/mock-console.ts +62 -0
  17. package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
  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/mock-web-socket.ts +37 -0
  21. package/src/__tests__/{run-in-tmp.ts → helpers/run-in-tmp.ts} +1 -1
  22. package/src/__tests__/helpers/run-wrangler.ts +16 -0
  23. package/src/__tests__/helpers/write-wrangler-toml.ts +20 -0
  24. package/src/__tests__/index.test.ts +418 -71
  25. package/src/__tests__/jest.setup.ts +30 -2
  26. package/src/__tests__/kv.test.ts +147 -252
  27. package/src/__tests__/logout.test.ts +50 -0
  28. package/src/__tests__/package-manager.test.ts +206 -0
  29. package/src/__tests__/publish.test.ts +1136 -291
  30. package/src/__tests__/r2.test.ts +206 -0
  31. package/src/__tests__/secret.test.ts +210 -0
  32. package/src/__tests__/sentry.test.ts +146 -0
  33. package/src/__tests__/tail.test.ts +246 -0
  34. package/src/__tests__/whoami.test.tsx +6 -47
  35. package/src/api/form_data.ts +75 -25
  36. package/src/api/preview.ts +2 -2
  37. package/src/api/worker.ts +34 -15
  38. package/src/bundle.ts +127 -0
  39. package/src/cfetch/index.ts +7 -15
  40. package/src/cfetch/internal.ts +41 -6
  41. package/src/cli.ts +10 -0
  42. package/src/config.ts +125 -95
  43. package/src/dev.tsx +300 -193
  44. package/src/dialogs.tsx +2 -2
  45. package/src/guess-worker-format.ts +68 -0
  46. package/src/index.tsx +578 -192
  47. package/src/inspect.ts +29 -10
  48. package/src/kv.tsx +23 -17
  49. package/src/module-collection.ts +32 -12
  50. package/src/open-in-browser.ts +13 -0
  51. package/src/package-manager.ts +120 -0
  52. package/src/pages.tsx +28 -23
  53. package/src/paths.ts +26 -0
  54. package/src/proxy.ts +88 -14
  55. package/src/publish.ts +260 -297
  56. package/src/r2.ts +50 -0
  57. package/src/reporting.ts +115 -0
  58. package/src/sites.tsx +28 -27
  59. package/src/tail.tsx +178 -9
  60. package/src/user.tsx +58 -44
  61. package/templates/new-worker.js +15 -0
  62. package/templates/new-worker.ts +15 -0
  63. package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
  64. package/wrangler-dist/cli.js +124315 -104677
  65. package/wrangler-dist/cli.js.map +3 -3
  66. package/src/__tests__/mock-console.ts +0 -34
  67. package/src/__tests__/run-wrangler.ts +0 -8
package/src/index.tsx CHANGED
@@ -1,25 +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 type Yargs from "yargs";
7
- import { findUp } from "find-up";
8
- import TOML from "@iarna/toml";
11
+ import { version as wranglerVersion } from "../package.json";
12
+ import { toFormData } from "./api/form_data";
13
+ import { fetchResult } from "./cfetch";
9
14
  import { normaliseAndValidateEnvironmentsConfig } from "./config";
10
- import type { Config } from "./config";
11
- import { getAssetPaths } from "./sites";
15
+ import Dev from "./dev";
12
16
  import { confirm, prompt } from "./dialogs";
13
- import { version as wranglerVersion } from "../package.json";
14
- import {
15
- login,
16
- logout,
17
- listScopes,
18
- initialise as initialiseUserConfig,
19
- loginOrRefreshIfRequired,
20
- getAccountId,
21
- validateScopeKeys,
22
- } from "./user";
23
17
  import {
24
18
  getNamespaceId,
25
19
  listNamespaces,
@@ -29,32 +23,60 @@ import {
29
23
  deleteBulkKeyValue,
30
24
  createNamespace,
31
25
  isValidNamespaceBinding,
26
+ getKeyValue,
32
27
  } from "./kv";
33
-
28
+ import { getPackageManager } from "./package-manager";
34
29
  import { pages } from "./pages";
35
-
36
- import { fetchResult, fetchRaw } from "./cfetch";
37
-
38
30
  import publish from "./publish";
39
- import path from "path/posix";
40
- import { writeFile } from "node:fs/promises";
41
- import { toFormData } from "./api/form_data";
42
-
43
- import { createTail } from "./tail";
44
- import onExit from "signal-exit";
45
- import { setTimeout } from "node:timers/promises";
46
- import * as fs from "node:fs";
47
- import { execa } from "execa";
31
+ import { createR2Bucket, deleteR2Bucket, listR2Buckets } from "./r2";
32
+ import { getAssetPaths } from "./sites";
33
+ import {
34
+ createTail,
35
+ jsonPrintLogs,
36
+ prettyPrintLogs,
37
+ translateCliFiltersToApiFilters,
38
+ } from "./tail";
39
+ import {
40
+ login,
41
+ logout,
42
+ listScopes,
43
+ initialise as initialiseUserConfig,
44
+ loginOrRefreshIfRequired,
45
+ getAccountId,
46
+ validateScopeKeys,
47
+ } from "./user";
48
48
  import { whoami } from "./whoami";
49
49
 
50
+ import type { Entry } from "./bundle";
51
+ import type { Config } from "./config";
52
+ import type { TailCLIFilters } from "./tail";
53
+ import type { RawData } from "ws";
54
+ import type Yargs from "yargs";
55
+
50
56
  const resetColor = "\x1b[0m";
51
57
  const fgGreenColor = "\x1b[32m";
52
58
 
59
+ // a set of binding types that are known to be supported by wrangler
60
+ const knownBindings = [
61
+ "plain_text",
62
+ "json",
63
+ "kv_namespace",
64
+ "durable_object_namespace",
65
+ ];
66
+
67
+ type ConfigPath = string | undefined;
68
+
69
+ async function findWranglerToml(
70
+ referencePath: string = process.cwd()
71
+ ): Promise<ConfigPath> {
72
+ const configPath = await findUp("wrangler.toml", { cwd: referencePath });
73
+ return configPath;
74
+ }
75
+
53
76
  async function readConfig(configPath?: string): Promise<Config> {
54
77
  const config: Config = {};
55
78
  if (!configPath) {
56
- configPath = await findUp("wrangler.toml");
57
- // TODO - terminate this early instead of going all the way to the root
79
+ configPath = await findWranglerToml();
58
80
  }
59
81
 
60
82
  if (configPath) {
@@ -65,21 +87,108 @@ async function readConfig(configPath?: string): Promise<Config> {
65
87
 
66
88
  normaliseAndValidateEnvironmentsConfig(config);
67
89
 
90
+ // The field "experimental_services" doesn't exist anymore
91
+ // in the config, but we still want to error about any older usage.
92
+ // TODO: remove this error before GA.
68
93
  if ("experimental_services" in config) {
94
+ throw new Error(
95
+ `The "experimental_services" field is no longer supported. Instead, use [[unsafe.bindings]] to enable experimental features. Add this to your wrangler.toml:
96
+
97
+ ${TOML.stringify({
98
+ unsafe: {
99
+ bindings: (config.experimental_services || []).map((serviceDefinition) => {
100
+ return {
101
+ name: serviceDefinition.name,
102
+ type: "service",
103
+ service: serviceDefinition.service,
104
+ environment: serviceDefinition.environment,
105
+ };
106
+ }),
107
+ },
108
+ })}`
109
+ );
110
+ }
111
+
112
+ if (configPath && "wasm_modules" in config) {
113
+ // rewrite wasm_module paths to be absolute
114
+ const modules: Record<string, string> = {};
115
+ for (const [name, filePath] of Object.entries(config.wasm_modules || {})) {
116
+ modules[name] = path.relative(
117
+ process.cwd(),
118
+ path.join(path.dirname(configPath), filePath)
119
+ );
120
+ }
121
+ config.wasm_modules = modules;
122
+ }
123
+
124
+ if ("unsafe" in config) {
69
125
  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"
126
+ "'unsafe' fields are experimental and may change or break at any time."
71
127
  );
72
128
  }
73
129
 
74
130
  // todo: validate, add defaults
75
131
  // let's just do some basics for now
76
132
 
77
- // @ts-expect-error we're being sneaky here for now
133
+ for (const binding of config.unsafe?.bindings ?? []) {
134
+ if (knownBindings.includes(binding.type)) {
135
+ console.warn(
136
+ `Raw '${binding.type}' bindings are not directly supported by wrangler. Consider migrating to a ` +
137
+ `format for '${binding.type}' bindings that is supported by wrangler for optimal support: ` +
138
+ "https://developers.cloudflare.com/workers/cli-wrangler/configuration"
139
+ );
140
+ }
141
+ }
142
+
143
+ // @ts-expect-error we're being sneaky here
78
144
  config.__path__ = configPath;
79
145
 
80
146
  return config;
81
147
  }
82
148
 
149
+ function getEntry(
150
+ args: { _: (string | number)[]; script: string | undefined },
151
+ config: Config
152
+ ): Entry {
153
+ // @ts-expect-error a hidden field
154
+ const wranglerTomlPath = config.__path__;
155
+ let file: string;
156
+ let directory = process.cwd();
157
+ if (args.script) {
158
+ // If the script name comes from the command line it is relative to the current working directory.
159
+ file = path.resolve(args.script);
160
+ } else {
161
+ // If the script name comes from the config, then it is relative to the wrangler.toml file.
162
+ if (config.build?.upload?.main === undefined) {
163
+ throw new Error(
164
+ `Missing entry-point: The entry-point should be specified via the command line (e.g. \`wrangler ${args._[0]} path/to/script\`) or the \`build.upload.main\` config field.`
165
+ );
166
+ }
167
+ directory = path.resolve(
168
+ path.dirname(wranglerTomlPath),
169
+ config.build.upload.dir || ""
170
+ );
171
+ file = path.resolve(directory, config.build.upload.main);
172
+ }
173
+
174
+ if (!config.build?.command) {
175
+ let fileExists = false;
176
+ try {
177
+ // Use require.resolve to use node's resolution algorithm,
178
+ // this lets us use paths without explicit .js extension
179
+ // TODO: we should probably remove this, because it doesn't
180
+ // take into consideration other extensions like .tsx, .ts, .jsx, etc
181
+ fileExists = fs.existsSync(require.resolve(file));
182
+ } catch (e) {
183
+ // fail silently, usually means require.resolve threw MODULE_NOT_FOUND
184
+ }
185
+ if (fileExists === false) {
186
+ throw new Error(`Could not resolve "${file}".`);
187
+ }
188
+ }
189
+ return { file, directory };
190
+ }
191
+
83
192
  // a helper to demand one of a set of options
84
193
  // via https://github.com/yargs/yargs/issues/1093#issuecomment-491299261
85
194
  function demandOneOfOption(...options: string[]) {
@@ -176,10 +285,16 @@ export async function main(argv: string[]): Promise<void> {
176
285
  "init [name]",
177
286
  "📥 Create a wrangler.toml configuration file",
178
287
  (yargs) => {
179
- return yargs.positional("name", {
180
- describe: "The name of your worker.",
181
- type: "string",
182
- });
288
+ return yargs
289
+ .positional("name", {
290
+ describe: "The name of your worker.",
291
+ type: "string",
292
+ })
293
+ .option("yes", {
294
+ describe: 'Answer "yes" to any prompts for new projects.',
295
+ type: "boolean",
296
+ alias: "y",
297
+ });
183
298
  },
184
299
  async (args) => {
185
300
  if ("type" in args) {
@@ -192,7 +307,9 @@ export async function main(argv: string[]): Promise<void> {
192
307
  throw new CommandLineArgsError(message);
193
308
  }
194
309
 
195
- const destination = path.join(process.cwd(), "wrangler.toml");
310
+ const packageManager = await getPackageManager(process.cwd());
311
+
312
+ const destination = "./wrangler.toml";
196
313
  if (fs.existsSync(destination)) {
197
314
  console.warn(`${destination} file already exists!`);
198
315
  const shouldContinue = await confirm(
@@ -206,41 +323,48 @@ export async function main(argv: string[]): Promise<void> {
206
323
  try {
207
324
  await writeFile(
208
325
  destination,
209
- `compatibility_date = "${compatibilityDate}"` + "\n"
326
+ TOML.stringify({
327
+ name: args.name || path.basename(path.resolve(process.cwd())),
328
+ compatibility_date: compatibilityDate,
329
+ }) + "\n"
210
330
  );
211
331
  console.log(`✨ Successfully created wrangler.toml`);
212
332
  // TODO: suggest next steps?
213
333
  } catch (err) {
214
334
  throw new Error(
215
- `Failed to create wrangler.toml.\n${err.message ?? err}`
335
+ `Failed to create wrangler.toml.\n${(err as Error).message ?? err}`
216
336
  );
217
337
  }
218
338
  }
219
339
 
220
340
  let pathToPackageJson = await findUp("package.json");
341
+ let shouldCreatePackageJson = false;
342
+ const yesFlag = args.yes ?? false;
221
343
  if (!pathToPackageJson) {
222
344
  // If no package.json exists, ask to create one
223
- const shouldCreatePackageJson = await confirm(
224
- "No package.json found. Would you like to create one?"
225
- );
345
+ shouldCreatePackageJson =
346
+ yesFlag ||
347
+ (await confirm(
348
+ "No package.json found. Would you like to create one?"
349
+ ));
350
+
226
351
  if (shouldCreatePackageJson) {
227
352
  await writeFile(
228
- path.join(process.cwd(), "package.json"),
353
+ "./package.json",
229
354
  JSON.stringify(
230
355
  {
231
- name: "worker",
232
- version: "0.0.1",
356
+ name: args.name || path.basename(path.resolve(process.cwd())),
357
+ version: "0.0.0",
233
358
  devDependencies: {
234
359
  wrangler: wranglerVersion,
235
360
  },
361
+ private: true,
236
362
  },
237
363
  null,
238
364
  " "
239
365
  ) + "\n"
240
366
  );
241
- await execa("npm", ["install", "--prefer-offline"], {
242
- stdio: "inherit",
243
- });
367
+ await packageManager.install();
244
368
  console.log(`✨ Created package.json`);
245
369
  pathToPackageJson = path.join(process.cwd(), "package.json");
246
370
  } else {
@@ -258,47 +382,38 @@ export async function main(argv: string[]): Promise<void> {
258
382
  packageJson.dependencies?.wrangler
259
383
  )
260
384
  ) {
261
- const shouldInstall = await confirm(
262
- "Would you like to install wrangler into your package.json?"
263
- );
385
+ const shouldInstall =
386
+ yesFlag ||
387
+ (await confirm(
388
+ "Would you like to install wrangler into your package.json?"
389
+ ));
264
390
  if (shouldInstall) {
265
- await execa(
266
- "npm",
267
- [
268
- "install",
269
- `wrangler@${wranglerVersion}`,
270
- "--save-dev",
271
- "--prefer-offline",
272
- ],
273
- {
274
- stdio: "inherit",
275
- }
276
- );
391
+ await packageManager.addDevDeps(`wrangler@${wranglerVersion}`);
277
392
  console.log(`✨ Installed wrangler`);
278
393
  }
279
394
  }
280
395
  }
281
396
 
397
+ let isTypescriptProject = false;
282
398
  let pathToTSConfig = await findUp("tsconfig.json");
283
399
  if (!pathToTSConfig) {
284
400
  // If there's no tsconfig, offer to create one
285
401
  // and install @cloudflare/workers-types
286
- if (await confirm("Would you like to use typescript?")) {
402
+ if (yesFlag || (await confirm("Would you like to use TypeScript?"))) {
403
+ isTypescriptProject = true;
287
404
  await writeFile(
288
- path.join(process.cwd(), "tsconfig.json"),
405
+ "./tsconfig.json",
289
406
  JSON.stringify(
290
407
  {
291
408
  compilerOptions: {
292
- target: "esnext",
293
- module: "esnext",
409
+ target: "es2021",
410
+ module: "es2022",
294
411
  moduleResolution: "node",
295
- esModuleInterop: true,
296
412
  allowJs: true,
297
413
  allowSyntheticDefaultImports: true,
298
414
  isolatedModules: true,
299
415
  noEmit: true,
300
- lib: ["esnext"],
301
- jsx: "react",
416
+ lib: ["es2021"],
302
417
  resolveJsonModule: true,
303
418
  types: ["@cloudflare/workers-types"],
304
419
  },
@@ -307,22 +422,18 @@ export async function main(argv: string[]): Promise<void> {
307
422
  " "
308
423
  ) + "\n"
309
424
  );
310
- await execa(
311
- "npm",
312
- [
313
- "install",
314
- "@cloudflare/workers-types",
315
- "--save-dev",
316
- "--prefer-offline",
317
- ],
318
- { stdio: "inherit" }
425
+ await packageManager.addDevDeps(
426
+ "@cloudflare/workers-types",
427
+ "typescript"
319
428
  );
429
+
320
430
  console.log(
321
431
  `✨ Created tsconfig.json, installed @cloudflare/workers-types into devDependencies`
322
432
  );
323
433
  pathToTSConfig = path.join(process.cwd(), "tsconfig.json");
324
434
  }
325
435
  } else {
436
+ isTypescriptProject = true;
326
437
  // If there's a tsconfig, check if @cloudflare/workers-types
327
438
  // is already installed, and offer to install it if not
328
439
  const packageJson = JSON.parse(
@@ -338,18 +449,7 @@ export async function main(argv: string[]): Promise<void> {
338
449
  "Would you like to install the type definitions for Workers into your package.json?"
339
450
  );
340
451
  if (shouldInstall) {
341
- await execa(
342
- "npm",
343
- [
344
- "install",
345
- "@cloudflare/workers-types",
346
- "--save-dev",
347
- "--prefer-offline",
348
- ],
349
- {
350
- stdio: "inherit",
351
- }
352
- );
452
+ await packageManager.addDevDeps("@cloudflare/workers-types");
353
453
  // We don't update the tsconfig.json because
354
454
  // it could be complicated in existing projects
355
455
  // and we don't want to break them. Instead, we simply
@@ -357,8 +457,102 @@ export async function main(argv: string[]): Promise<void> {
357
457
  console.log(
358
458
  `✨ Installed @cloudflare/workers-types.\nPlease add "@cloudflare/workers-types" to compilerOptions.types in your tsconfig.json`
359
459
  );
360
- } else {
361
- return;
460
+ }
461
+ }
462
+ }
463
+
464
+ const packageJsonContent = JSON.parse(
465
+ await readFile(pathToPackageJson, "utf-8")
466
+ );
467
+ const shouldWritePackageJsonScripts =
468
+ !packageJsonContent.scripts?.start &&
469
+ !packageJsonContent.scripts?.deploy &&
470
+ shouldCreatePackageJson;
471
+ async function writePackageJsonScripts(
472
+ isWritingScripts: boolean,
473
+ packagePath: string,
474
+ scriptPath: string
475
+ ) {
476
+ if (isWritingScripts) {
477
+ await writeFile(
478
+ packagePath,
479
+ JSON.stringify(
480
+ {
481
+ ...packageJsonContent,
482
+ scripts: {
483
+ ...packageJsonContent.scripts,
484
+ start: `wrangler dev ${scriptPath}`,
485
+ deploy: `wrangler publish ${scriptPath}`,
486
+ },
487
+ },
488
+ null,
489
+ " "
490
+ ) + "\n"
491
+ );
492
+ console.log(`To start developing on your worker, run npm start.`);
493
+ console.log(
494
+ `To publish your worker on to the internet, run npm run deploy.`
495
+ );
496
+ } else {
497
+ console.log(
498
+ `To start developing on your worker, npx wrangler dev ${scriptPath}`
499
+ );
500
+ console.log(
501
+ `To publish your worker on to the internet, npx wrangler publish ${scriptPath}`
502
+ );
503
+ }
504
+ }
505
+ if (isTypescriptProject) {
506
+ if (!fs.existsSync("./src/index.ts")) {
507
+ let shouldCreateSource = false;
508
+
509
+ shouldCreateSource =
510
+ yesFlag ||
511
+ (await confirm(
512
+ `Would you like to create a Worker at src/index.ts?`
513
+ ));
514
+
515
+ if (shouldCreateSource) {
516
+ await mkdir("./src", { recursive: true });
517
+ await writeFile(
518
+ "./src/index.ts",
519
+ await readFile(
520
+ path.join(__dirname, "../templates/new-worker.ts"),
521
+ "utf-8"
522
+ )
523
+ );
524
+
525
+ await writePackageJsonScripts(
526
+ shouldWritePackageJsonScripts,
527
+ pathToPackageJson,
528
+ "src/index.ts"
529
+ );
530
+
531
+ console.log(`✨ Created src/index.ts`);
532
+ }
533
+ }
534
+ } else {
535
+ if (!fs.existsSync("./src/index.js")) {
536
+ const shouldCreateSource = await confirm(
537
+ `Would you like to create a Worker at src/index.js?`
538
+ );
539
+ if (shouldCreateSource) {
540
+ await mkdir("./src", { recursive: true });
541
+ await writeFile(
542
+ path.join("./src/index.js"),
543
+ await readFile(
544
+ path.join(__dirname, "../templates/new-worker.js"),
545
+ "utf-8"
546
+ )
547
+ );
548
+
549
+ await writePackageJsonScripts(
550
+ shouldWritePackageJsonScripts,
551
+ pathToPackageJson,
552
+ "src/index.js"
553
+ );
554
+
555
+ console.log(`✨ Created src/index.js`);
362
556
  }
363
557
  }
364
558
  }
@@ -467,14 +661,13 @@ export async function main(argv: string[]): Promise<void> {
467
661
 
468
662
  // dev
469
663
  wrangler.command(
470
- "dev <filename>",
664
+ "dev [script]",
471
665
  "👂 Start a local server for developing your worker",
472
666
  (yargs) => {
473
667
  return yargs
474
- .positional("filename", {
668
+ .positional("script", {
475
669
  describe: "entry point",
476
670
  type: "string",
477
- demandOption: true,
478
671
  })
479
672
  .option("name", {
480
673
  describe: "name of the script",
@@ -556,11 +749,18 @@ export async function main(argv: string[]): Promise<void> {
556
749
  .option("jsx-fragment", {
557
750
  describe: "The function that is called for each JSX fragment",
558
751
  type: "string",
752
+ })
753
+ .option("experimental-enable-local-persistence", {
754
+ describe: "Enable persistence for this session (only for local mode)",
755
+ type: "boolean",
559
756
  });
560
757
  },
561
758
  async (args) => {
562
- const { filename, format } = args;
563
- const config = args.config as Config;
759
+ const config = await readConfig(
760
+ (args.config as ConfigPath) ||
761
+ (args.script && (await findWranglerToml(path.dirname(args.script))))
762
+ );
763
+ const entry = getEntry(args, config);
564
764
 
565
765
  if (args["experimental-public"]) {
566
766
  console.warn(
@@ -617,12 +817,16 @@ export async function main(argv: string[]): Promise<void> {
617
817
  const { waitUntilExit } = render(
618
818
  <Dev
619
819
  name={args.name || config.name}
620
- entry={path.relative(process.cwd(), filename)}
820
+ entry={entry}
821
+ env={args.env}
621
822
  buildCommand={config.build || {}}
622
- format={format}
823
+ format={args.format || config.build?.upload?.format}
623
824
  initialMode={args.local ? "local" : "remote"}
624
825
  jsxFactory={args["jsx-factory"] || envRootObj?.jsx_factory}
625
826
  jsxFragment={args["jsx-fragment"] || envRootObj?.jsx_fragment}
827
+ enableLocalPersistence={
828
+ args["experimental-enable-local-persistence"] || false
829
+ }
626
830
  accountId={config.account_id}
627
831
  assetPaths={getAssetPaths(
628
832
  config,
@@ -665,8 +869,10 @@ export async function main(argv: string[]): Promise<void> {
665
869
  }
666
870
  ),
667
871
  vars: envRootObj.vars,
872
+ wasm_modules: config.wasm_modules,
668
873
  durable_objects: envRootObj.durable_objects,
669
- services: envRootObj.experimental_services,
874
+ r2_buckets: envRootObj.r2_buckets,
875
+ unsafe: envRootObj.unsafe?.bindings,
670
876
  }}
671
877
  />
672
878
  );
@@ -692,6 +898,10 @@ export async function main(argv: string[]): Promise<void> {
692
898
  describe: "name to use when uploading",
693
899
  type: "string",
694
900
  })
901
+ .option("format", {
902
+ choices: ["modules", "service-worker"] as const,
903
+ describe: "Choose an entry type",
904
+ })
695
905
  .option("compatibility-date", {
696
906
  describe: "Date to use for compatibility checks",
697
907
  type: "string",
@@ -736,12 +946,6 @@ export async function main(argv: string[]): Promise<void> {
736
946
  alias: "route",
737
947
  type: "array",
738
948
  })
739
- .option("services", {
740
- describe: "experimental support for services",
741
- type: "boolean",
742
- default: "false",
743
- hidden: true,
744
- })
745
949
  .option("jsx-factory", {
746
950
  describe: "The function that is called for each JSX element",
747
951
  type: "string",
@@ -769,7 +973,11 @@ export async function main(argv: string[]): Promise<void> {
769
973
  );
770
974
  }
771
975
 
772
- const config = args.config as Config;
976
+ const config = await readConfig(
977
+ (args.config as ConfigPath) ||
978
+ (args.script && (await findWranglerToml(path.dirname(args.script))))
979
+ );
980
+ const entry = getEntry(args, config);
773
981
 
774
982
  if (args.latest) {
775
983
  console.warn(
@@ -802,9 +1010,10 @@ export async function main(argv: string[]): Promise<void> {
802
1010
  args.siteExclude
803
1011
  );
804
1012
  await publish({
805
- config: args.config as Config,
1013
+ config,
806
1014
  name: args.name,
807
- script: args.script,
1015
+ format: args.format || config.build?.upload?.format,
1016
+ entry,
808
1017
  env: args.env,
809
1018
  compatibilityDate: args.latest
810
1019
  ? new Date().toISOString().substring(0, 10)
@@ -815,7 +1024,6 @@ export async function main(argv: string[]): Promise<void> {
815
1024
  jsxFragment: args["jsx-fragment"],
816
1025
  routes: args.routes,
817
1026
  assetPaths,
818
- format: undefined, // TODO: add args for this
819
1027
  legacyEnv: undefined, // TODO: get this from somewhere... config?
820
1028
  experimentalPublic: args["experimental-public"] !== undefined,
821
1029
  });
@@ -842,6 +1050,7 @@ export async function main(argv: string[]): Promise<void> {
842
1050
  .option("status", {
843
1051
  choices: ["ok", "error", "canceled"],
844
1052
  describe: "Filter by invocation status",
1053
+ array: true,
845
1054
  })
846
1055
  .option("header", {
847
1056
  type: "string",
@@ -850,6 +1059,7 @@ export async function main(argv: string[]): Promise<void> {
850
1059
  .option("method", {
851
1060
  type: "string",
852
1061
  describe: "Filter by HTTP method",
1062
+ array: true,
853
1063
  })
854
1064
  .option("sampling-rate", {
855
1065
  type: "number",
@@ -859,12 +1069,18 @@ export async function main(argv: string[]): Promise<void> {
859
1069
  type: "string",
860
1070
  describe: "Filter by a text match in console.log messages",
861
1071
  })
1072
+ .option("ip", {
1073
+ type: "string",
1074
+ describe:
1075
+ 'Filter by the IP address the request originates from. Use "self" to filter for your own IP',
1076
+ array: true,
1077
+ })
1078
+ // TODO: is this deprecated now with services / environments / etc?
862
1079
  .option("env", {
863
1080
  type: "string",
864
1081
  describe: "Perform on a specific environment",
865
1082
  })
866
1083
  );
867
- // TODO: filter by client ip, which can be 'self' or an ip address
868
1084
  },
869
1085
  async (args) => {
870
1086
  if (args.local) {
@@ -873,14 +1089,13 @@ export async function main(argv: string[]): Promise<void> {
873
1089
  );
874
1090
  }
875
1091
 
876
- const config = args.config as Config;
1092
+ const config = await readConfig(args.config as ConfigPath);
877
1093
 
878
- if (!(args.name || config.name)) {
1094
+ const shortScriptName = args.name || config.name;
1095
+ if (!shortScriptName) {
879
1096
  throw new Error("Missing script name");
880
1097
  }
881
- const scriptName = `${args.name || config.name}${
882
- args.env ? `-${args.env}` : ""
883
- }`;
1098
+ const scriptName = `${shortScriptName}${args.env ? `-${args.env}` : ""}`;
884
1099
 
885
1100
  // -- snip, extract --
886
1101
  const loggedIn = await loginOrRefreshIfRequired();
@@ -900,14 +1115,17 @@ export async function main(argv: string[]): Promise<void> {
900
1115
 
901
1116
  const accountId = config.account_id;
902
1117
 
903
- const filters = {
904
- status: args.status as "ok" | "error" | "canceled",
1118
+ const cliFilters: TailCLIFilters = {
1119
+ status: args.status as Array<"ok" | "error" | "canceled">,
905
1120
  header: args.header,
906
1121
  method: args.method,
907
- "sampling-rate": args["sampling-rate"],
1122
+ samplingRate: args["sampling-rate"],
908
1123
  search: args.search,
1124
+ clientIp: args.ip,
909
1125
  };
910
1126
 
1127
+ const filters = translateCliFiltersToApiFilters(cliFilters);
1128
+
911
1129
  const { tail, expiration, /* sendHeartbeat, */ deleteTail } =
912
1130
  await createTail(accountId, scriptName, filters);
913
1131
 
@@ -920,9 +1138,10 @@ export async function main(argv: string[]): Promise<void> {
920
1138
  await deleteTail();
921
1139
  });
922
1140
 
923
- tail.on("message", (data) => {
924
- console.log(JSON.stringify(JSON.parse(data.toString()), null, " "));
925
- });
1141
+ const printLog: (data: RawData) => void =
1142
+ args.format === "pretty" ? prettyPrintLogs : jsonPrintLogs;
1143
+
1144
+ tail.on("message", printLog);
926
1145
 
927
1146
  while (tail.readyState !== tail.OPEN) {
928
1147
  switch (tail.readyState) {
@@ -938,6 +1157,11 @@ export async function main(argv: string[]): Promise<void> {
938
1157
  }
939
1158
 
940
1159
  console.log(`Connected to ${scriptName}, waiting for logs...`);
1160
+
1161
+ tail.on("close", async () => {
1162
+ tail.terminate();
1163
+ await deleteTail();
1164
+ });
941
1165
  }
942
1166
  );
943
1167
 
@@ -1003,7 +1227,8 @@ export async function main(argv: string[]): Promise<void> {
1003
1227
  async (args) => {
1004
1228
  console.log(":route list", args);
1005
1229
  // TODO: use environment (current wrangler doesn't do so?)
1006
- const zone = args.zone || (args.config as Config).zone_id;
1230
+ const config = await readConfig(args.config as ConfigPath);
1231
+ const zone = args.zone || config.zone_id;
1007
1232
  if (!zone) {
1008
1233
  throw new Error("missing zone id");
1009
1234
  }
@@ -1032,7 +1257,8 @@ export async function main(argv: string[]): Promise<void> {
1032
1257
  async (args) => {
1033
1258
  console.log(":route delete", args);
1034
1259
  // TODO: use environment (current wrangler doesn't do so?)
1035
- const zone = args.zone || (args.config as Config).zone_id;
1260
+ const config = await readConfig(args.config as ConfigPath);
1261
+ const zone = args.zone || config.zone_id;
1036
1262
  if (!zone) {
1037
1263
  throw new Error("missing zone id");
1038
1264
  }
@@ -1088,12 +1314,7 @@ export async function main(argv: string[]): Promise<void> {
1088
1314
  });
1089
1315
  },
1090
1316
  async (args) => {
1091
- if (args.local) {
1092
- throw new NotImplementedError(
1093
- "--local not implemented for this command yet"
1094
- );
1095
- }
1096
- const config = args.config as Config;
1317
+ const config = await readConfig(args.config as ConfigPath);
1097
1318
 
1098
1319
  // TODO: use environment (how does current wrangler do it?)
1099
1320
  const scriptName = args.name || config.name;
@@ -1101,6 +1322,10 @@ export async function main(argv: string[]): Promise<void> {
1101
1322
  throw new Error("Missing script name");
1102
1323
  }
1103
1324
 
1325
+ if (args.local) {
1326
+ console.warn("`wrangler secret put` is a no-op in --local mode");
1327
+ }
1328
+
1104
1329
  if (!args.local) {
1105
1330
  // -- snip, extract --
1106
1331
  const loggedIn = await loginOrRefreshIfRequired();
@@ -1123,6 +1348,13 @@ export async function main(argv: string[]): Promise<void> {
1123
1348
  "Enter a secret value:",
1124
1349
  "password"
1125
1350
  );
1351
+
1352
+ if (args.local) {
1353
+ return;
1354
+ }
1355
+
1356
+ console.log(`🌀 Creating the secret for script ${scriptName}`);
1357
+
1126
1358
  async function submitSecret() {
1127
1359
  return await fetchResult(
1128
1360
  `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/`,
@@ -1139,16 +1371,17 @@ export async function main(argv: string[]): Promise<void> {
1139
1371
  }
1140
1372
 
1141
1373
  try {
1142
- console.log(await submitSecret());
1374
+ await submitSecret();
1143
1375
  } catch (e) {
1376
+ // @ts-expect-error non-standard property on Error
1144
1377
  if (e.code === 10007) {
1145
1378
  // upload a draft worker
1146
1379
  await fetchResult(
1147
1380
  `/accounts/${config.account_id}/workers/scripts/${scriptName}`,
1148
1381
  {
1149
1382
  method: "PUT",
1150
- // @ts-expect-error TODO: fix this error!
1151
1383
  body: toFormData({
1384
+ name: scriptName,
1152
1385
  main: {
1153
1386
  name: scriptName,
1154
1387
  content: `export default { fetch() {} }`,
@@ -1158,17 +1391,25 @@ export async function main(argv: string[]): Promise<void> {
1158
1391
  kv_namespaces: [],
1159
1392
  vars: {},
1160
1393
  durable_objects: { bindings: [] },
1394
+ r2_buckets: [],
1395
+ wasm_modules: {},
1396
+ unsafe: [],
1161
1397
  },
1162
1398
  modules: [],
1399
+ migrations: undefined,
1400
+ compatibility_date: undefined,
1401
+ compatibility_flags: undefined,
1402
+ usage_model: undefined,
1163
1403
  }),
1164
1404
  }
1165
1405
  );
1166
1406
 
1167
1407
  // and then try again
1168
- console.log(await submitSecret());
1408
+ await submitSecret();
1169
1409
  // TODO: delete the draft worker if this failed too?
1170
1410
  }
1171
1411
  }
1412
+ console.log(`✨ Success! Uploaded secret ${args.key}`);
1172
1413
  }
1173
1414
  )
1174
1415
  .command(
@@ -1191,12 +1432,7 @@ export async function main(argv: string[]): Promise<void> {
1191
1432
  });
1192
1433
  },
1193
1434
  async (args) => {
1194
- if (args.local) {
1195
- throw new NotImplementedError(
1196
- "--local not implemented for this command yet"
1197
- );
1198
- }
1199
- const config = args.config as Config;
1435
+ const config = await readConfig(args.config as ConfigPath);
1200
1436
 
1201
1437
  // TODO: use environment (how does current wrangler do it?)
1202
1438
  const scriptName = args.name || config.name;
@@ -1204,6 +1440,12 @@ export async function main(argv: string[]): Promise<void> {
1204
1440
  throw new Error("Missing script name");
1205
1441
  }
1206
1442
 
1443
+ if (args.local) {
1444
+ console.warn(
1445
+ "`wrangler secret delete` is a no-op in --local mode"
1446
+ );
1447
+ }
1448
+
1207
1449
  if (!args.local) {
1208
1450
  // -- snip, extract --
1209
1451
  const loggedIn = await loginOrRefreshIfRequired();
@@ -1222,17 +1464,24 @@ export async function main(argv: string[]): Promise<void> {
1222
1464
  // -- snip, end --
1223
1465
  }
1224
1466
 
1225
- if (await confirm("Are you sure you want to delete this secret?")) {
1467
+ if (
1468
+ await confirm(
1469
+ `Are you sure you want to permanently delete the variable ${args.key} on the script ${scriptName}?`
1470
+ )
1471
+ ) {
1226
1472
  console.log(
1227
- `Deleting the secret ${args.key} on script ${scriptName}.`
1473
+ `🌀 Deleting the secret ${args.key} on script ${scriptName}.`
1228
1474
  );
1229
1475
 
1230
- console.log(
1231
- await fetchResult(
1232
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/${args.key}`,
1233
- { method: "DELETE" }
1234
- )
1476
+ if (args.local) {
1477
+ return;
1478
+ }
1479
+
1480
+ await fetchResult(
1481
+ `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/${args.key}`,
1482
+ { method: "DELETE" }
1235
1483
  );
1484
+ console.log(`✨ Success! Deleted secret ${args.key}`);
1236
1485
  }
1237
1486
  }
1238
1487
  )
@@ -1252,12 +1501,7 @@ export async function main(argv: string[]): Promise<void> {
1252
1501
  });
1253
1502
  },
1254
1503
  async (args) => {
1255
- if (args.local) {
1256
- throw new NotImplementedError(
1257
- "--local not implemented for this command yet"
1258
- );
1259
- }
1260
- const config = args.config as Config;
1504
+ const config = await readConfig(args.config as ConfigPath);
1261
1505
 
1262
1506
  // TODO: use environment (how does current wrangler do it?)
1263
1507
  const scriptName = args.name || config.name;
@@ -1265,6 +1509,10 @@ export async function main(argv: string[]): Promise<void> {
1265
1509
  throw new Error("Missing script name");
1266
1510
  }
1267
1511
 
1512
+ if (args.local) {
1513
+ console.warn("`wrangler secret list` is a no-op in --local mode");
1514
+ }
1515
+
1268
1516
  if (!args.local) {
1269
1517
  // -- snip, extract --
1270
1518
  const loggedIn = await loginOrRefreshIfRequired();
@@ -1283,9 +1531,17 @@ export async function main(argv: string[]): Promise<void> {
1283
1531
  // -- snip, end --
1284
1532
  }
1285
1533
 
1534
+ if (args.local) {
1535
+ return;
1536
+ }
1537
+
1286
1538
  console.log(
1287
- await fetchResult(
1288
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets`
1539
+ JSON.stringify(
1540
+ await fetchResult(
1541
+ `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets`
1542
+ ),
1543
+ null,
1544
+ " "
1289
1545
  )
1290
1546
  );
1291
1547
  }
@@ -1333,7 +1589,7 @@ export async function main(argv: string[]): Promise<void> {
1333
1589
  );
1334
1590
  }
1335
1591
 
1336
- const config = args.config as Config;
1592
+ const config = await readConfig(args.config as ConfigPath);
1337
1593
  if (!config.name) {
1338
1594
  console.warn(
1339
1595
  "No configured name present, using `worker` as a prefix for the title"
@@ -1398,7 +1654,7 @@ export async function main(argv: string[]): Promise<void> {
1398
1654
  "Outputs a list of all KV namespaces associated with your account id.",
1399
1655
  {},
1400
1656
  async (args) => {
1401
- const config = args.config as Config;
1657
+ const config = await readConfig(args.config as ConfigPath);
1402
1658
 
1403
1659
  if (args.local) {
1404
1660
  throw new NotImplementedError(
@@ -1457,7 +1713,7 @@ export async function main(argv: string[]): Promise<void> {
1457
1713
  });
1458
1714
  },
1459
1715
  async (args) => {
1460
- const config = args.config as Config;
1716
+ const config = await readConfig(args.config as ConfigPath);
1461
1717
 
1462
1718
  if (args.local) {
1463
1719
  throw new NotImplementedError(
@@ -1466,10 +1722,11 @@ export async function main(argv: string[]): Promise<void> {
1466
1722
  } else {
1467
1723
  let id;
1468
1724
  try {
1469
- id = getNamespaceId(args);
1725
+ id = getNamespaceId(args, config);
1470
1726
  } catch (e) {
1471
1727
  throw new CommandLineArgsError(
1472
- "Not able to delete namespace.\n" + e.message
1728
+ "Not able to delete namespace.\n" +
1729
+ ((e as Error).message ?? e)
1473
1730
  );
1474
1731
  }
1475
1732
 
@@ -1570,13 +1827,13 @@ export async function main(argv: string[]): Promise<void> {
1570
1827
  .check(demandOneOfOption("value", "path"));
1571
1828
  },
1572
1829
  async ({ key, ttl, expiration, ...args }) => {
1573
- const namespaceId = getNamespaceId(args);
1830
+ const config = await readConfig(args.config as ConfigPath);
1831
+ const namespaceId = getNamespaceId(args, config);
1574
1832
  // One of `args.path` and `args.value` must be defined
1575
1833
  const value = args.path
1576
1834
  ? await readFile(args.path, "utf-8")
1577
1835
  : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1578
1836
  args.value!;
1579
- const config = args.config as Config;
1580
1837
 
1581
1838
  if (args.path) {
1582
1839
  console.log(
@@ -1652,9 +1909,8 @@ export async function main(argv: string[]): Promise<void> {
1652
1909
  },
1653
1910
  async ({ prefix, ...args }) => {
1654
1911
  // TODO: support for limit+cursor (pagination)
1655
-
1656
- const namespaceId = getNamespaceId(args);
1657
- const config = args.config as Config;
1912
+ const config = await readConfig(args.config as ConfigPath);
1913
+ const namespaceId = getNamespaceId(args, config);
1658
1914
 
1659
1915
  if (args.local) {
1660
1916
  const { Miniflare } = await import("miniflare");
@@ -1727,8 +1983,8 @@ export async function main(argv: string[]): Promise<void> {
1727
1983
  });
1728
1984
  },
1729
1985
  async ({ key, ...args }) => {
1730
- const namespaceId = getNamespaceId(args);
1731
- const config = args.config as Config;
1986
+ const config = await readConfig(args.config as ConfigPath);
1987
+ const namespaceId = getNamespaceId(args, config);
1732
1988
 
1733
1989
  if (args.local) {
1734
1990
  const { Miniflare } = await import("miniflare");
@@ -1758,13 +2014,11 @@ export async function main(argv: string[]): Promise<void> {
1758
2014
  }
1759
2015
  }
1760
2016
  // -- snip, end --
1761
- }
1762
2017
 
1763
- console.log(
1764
- await fetchRaw(
1765
- `/accounts/${config.account_id}/storage/kv/namespaces/${namespaceId}/values/${key}`
1766
- )
1767
- );
2018
+ console.log(
2019
+ await getKeyValue(config.account_id, namespaceId, key)
2020
+ );
2021
+ }
1768
2022
  }
1769
2023
  )
1770
2024
  .command(
@@ -1796,7 +2050,8 @@ export async function main(argv: string[]): Promise<void> {
1796
2050
  });
1797
2051
  },
1798
2052
  async ({ key, ...args }) => {
1799
- const namespaceId = getNamespaceId(args);
2053
+ const config = await readConfig(args.config as ConfigPath);
2054
+ const namespaceId = getNamespaceId(args, config);
1800
2055
 
1801
2056
  console.log(
1802
2057
  `deleting the key "${key}" on namespace ${namespaceId}`
@@ -1814,8 +2069,6 @@ export async function main(argv: string[]): Promise<void> {
1814
2069
  return;
1815
2070
  }
1816
2071
 
1817
- const config = args.config as Config;
1818
-
1819
2072
  if (!args.local) {
1820
2073
  // -- snip, extract --
1821
2074
  const loggedIn = await loginOrRefreshIfRequired();
@@ -1881,16 +2134,17 @@ export async function main(argv: string[]): Promise<void> {
1881
2134
  // The simplest implementation I could think of.
1882
2135
  // This could be made more efficient with a streaming parser/uploader
1883
2136
  // but we'll do that in the future if needed.
1884
-
1885
- const namespaceId = getNamespaceId(args);
1886
- const config = args.config as Config;
2137
+ const config = await readConfig(args.config as ConfigPath);
2138
+ const namespaceId = getNamespaceId(args, config);
1887
2139
  const content = await readFile(filename, "utf-8");
1888
2140
  let parsedContent;
1889
2141
  try {
1890
2142
  parsedContent = JSON.parse(content);
1891
2143
  } catch (err) {
1892
2144
  throw new Error(
1893
- `Could not parse json from ${filename}.\n${err.message ?? err}`
2145
+ `Could not parse json from ${filename}.\n${
2146
+ (err as Error).message ?? err
2147
+ }`
1894
2148
  );
1895
2149
  }
1896
2150
 
@@ -1965,15 +2219,18 @@ export async function main(argv: string[]): Promise<void> {
1965
2219
  });
1966
2220
  },
1967
2221
  async ({ filename, ...args }) => {
1968
- const namespaceId = getNamespaceId(args);
1969
- const config = args.config as Config;
2222
+ const config = await readConfig(args.config as ConfigPath);
2223
+ const namespaceId = getNamespaceId(args, config);
2224
+
1970
2225
  const content = await readFile(filename, "utf-8");
1971
2226
  let parsedContent;
1972
2227
  try {
1973
2228
  parsedContent = JSON.parse(content);
1974
2229
  } catch (err) {
1975
2230
  throw new Error(
1976
- `Could not parse json from ${filename}.\n${err.message ?? err}`
2231
+ `Could not parse json from ${filename}.\n${
2232
+ (err as Error).message ?? err
2233
+ }`
1977
2234
  );
1978
2235
  }
1979
2236
 
@@ -2020,14 +2277,144 @@ export async function main(argv: string[]): Promise<void> {
2020
2277
 
2021
2278
  wrangler.command("pages", "⚡️ Configure Cloudflare Pages", pages);
2022
2279
 
2280
+ wrangler.command("r2", "📦 Interact with an R2 store", (r2Yargs) => {
2281
+ return r2Yargs.command("bucket", "Manage R2 buckets", (r2BucketYargs) => {
2282
+ r2BucketYargs.command(
2283
+ "create <name>",
2284
+ "Create a new R2 bucket",
2285
+ (yargs) => {
2286
+ return yargs.positional("name", {
2287
+ describe: "The name of the new bucket",
2288
+ type: "string",
2289
+ demandOption: true,
2290
+ });
2291
+ },
2292
+ async (args) => {
2293
+ // We expect three values in `_`: `r2`, `bucket`, `create`.
2294
+ if (args._.length > 3) {
2295
+ const extraArgs = args._.slice(3).join(" ");
2296
+ throw new CommandLineArgsError(
2297
+ `Unexpected additional positional arguments "${extraArgs}".`
2298
+ );
2299
+ }
2300
+
2301
+ const config = await readConfig(args.config as ConfigPath);
2302
+
2303
+ if (args.local) {
2304
+ throw new NotImplementedError(
2305
+ `local mode is not yet supported for this command`
2306
+ );
2307
+ } else {
2308
+ // -- snip, extract --
2309
+ const loggedIn = await loginOrRefreshIfRequired();
2310
+ if (!loggedIn) {
2311
+ // didn't login, let's just quit
2312
+ console.log("Did not login, quitting...");
2313
+ return;
2314
+ }
2315
+
2316
+ if (!config.account_id) {
2317
+ config.account_id = await getAccountId();
2318
+ if (!config.account_id) {
2319
+ throw new Error("No account id found, quitting...");
2320
+ }
2321
+ }
2322
+ // -- snip, end --
2323
+
2324
+ console.log(`Creating bucket ${args.name}.`);
2325
+ await createR2Bucket(config.account_id, args.name);
2326
+ console.log(`Created bucket ${args.name}.`);
2327
+ }
2328
+ }
2329
+ );
2330
+
2331
+ r2BucketYargs.command("list", "List R2 buckets", {}, async (args) => {
2332
+ const config = await readConfig(args.config as ConfigPath);
2333
+
2334
+ if (args.local) {
2335
+ throw new NotImplementedError(
2336
+ `local mode is not yet supported for this command`
2337
+ );
2338
+ } else {
2339
+ // -- snip, extract --
2340
+ const loggedIn = await loginOrRefreshIfRequired();
2341
+ if (!loggedIn) {
2342
+ // didn't login, let's just quit
2343
+ console.log("Did not login, quitting...");
2344
+ return;
2345
+ }
2346
+
2347
+ if (!config.account_id) {
2348
+ config.account_id = await getAccountId();
2349
+ if (!config.account_id) {
2350
+ throw new Error("No account id found, quitting...");
2351
+ }
2352
+ }
2353
+ // -- snip, end --
2354
+
2355
+ console.log(
2356
+ JSON.stringify(await listR2Buckets(config.account_id), null, 2)
2357
+ );
2358
+ }
2359
+ });
2360
+
2361
+ r2BucketYargs.command(
2362
+ "delete <name>",
2363
+ "Delete an R2 bucket",
2364
+ (yargs) => {
2365
+ return yargs.positional("name", {
2366
+ describe: "The name of the bucket to delete",
2367
+ type: "string",
2368
+ demandOption: true,
2369
+ });
2370
+ },
2371
+ async (args) => {
2372
+ // We expect three values in `_`: `r2`, `bucket`, `delete`.
2373
+ if (args._.length > 3) {
2374
+ const extraArgs = args._.slice(3).join(" ");
2375
+ throw new CommandLineArgsError(
2376
+ `Unexpected additional positional arguments "${extraArgs}".`
2377
+ );
2378
+ }
2379
+
2380
+ const config = await readConfig(args.config as ConfigPath);
2381
+
2382
+ if (args.local) {
2383
+ throw new NotImplementedError(
2384
+ `local mode is not yet supported for this command`
2385
+ );
2386
+ } else {
2387
+ // -- snip, extract --
2388
+ const loggedIn = await loginOrRefreshIfRequired();
2389
+ if (!loggedIn) {
2390
+ // didn't login, let's just quit
2391
+ console.log("Did not login, quitting...");
2392
+ return;
2393
+ }
2394
+
2395
+ if (!config.account_id) {
2396
+ config.account_id = await getAccountId();
2397
+ if (!config.account_id) {
2398
+ throw new Error("No account id found, quitting...");
2399
+ }
2400
+ }
2401
+ // -- snip, end --
2402
+
2403
+ console.log(`Deleting bucket ${args.name}.`);
2404
+ await deleteR2Bucket(config.account_id, args.name);
2405
+ console.log(`Deleted bucket ${args.name}.`);
2406
+ }
2407
+ }
2408
+ );
2409
+ return r2BucketYargs;
2410
+ });
2411
+ });
2412
+
2023
2413
  wrangler
2024
2414
  .option("config", {
2025
2415
  alias: "c",
2026
2416
  describe: "Path to .toml configuration file",
2027
2417
  type: "string",
2028
- async coerce(arg) {
2029
- return await readConfig(arg);
2030
- },
2031
2418
  })
2032
2419
  .option("local", {
2033
2420
  alias: "l",
@@ -2041,9 +2428,8 @@ export async function main(argv: string[]): Promise<void> {
2041
2428
  wrangler.version(wranglerVersion).alias("v", "version");
2042
2429
  wrangler.exitProcess(false);
2043
2430
 
2044
- await initialiseUserConfig();
2045
-
2046
2431
  try {
2432
+ await initialiseUserConfig();
2047
2433
  await wrangler.parse();
2048
2434
  } catch (e) {
2049
2435
  if (e instanceof CommandLineArgsError) {
@@ -2051,7 +2437,7 @@ export async function main(argv: string[]): Promise<void> {
2051
2437
  console.error(""); // Just adds a bit of space
2052
2438
  console.error(e.message);
2053
2439
  } else {
2054
- console.error(e.message);
2440
+ console.error(e instanceof Error ? e.message : e);
2055
2441
  console.error(""); // Just adds a bit of space
2056
2442
  console.error(
2057
2443
  `${fgGreenColor}%s${resetColor}`,