wrangler 0.0.0-e88a54e → 0.0.0-e95e5a0

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 (88) hide show
  1. package/README.md +25 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4828 -21
  4. package/package.json +29 -24
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +11 -0
  8. package/pages/functions/filepath-routing.ts +8 -0
  9. package/pages/functions/routes.ts +6 -1
  10. package/pages/functions/template-plugin.ts +146 -0
  11. package/pages/functions/template-worker.ts +21 -8
  12. package/src/__tests__/config-cache.test.ts +36 -0
  13. package/src/__tests__/configuration.test.ts +530 -201
  14. package/src/__tests__/dev.test.tsx +284 -18
  15. package/src/__tests__/guess-worker-format.test.ts +28 -1
  16. package/src/__tests__/helpers/mock-cfetch.ts +5 -1
  17. package/src/__tests__/helpers/mock-console.ts +20 -3
  18. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  19. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  20. package/src/__tests__/helpers/mock-istty.ts +27 -0
  21. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  22. package/src/__tests__/helpers/run-in-tmp.ts +7 -0
  23. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  24. package/src/__tests__/https-options.test.ts +5 -2
  25. package/src/__tests__/index.test.ts +27 -769
  26. package/src/__tests__/init.test.ts +1516 -0
  27. package/src/__tests__/jest.setup.ts +4 -4
  28. package/src/__tests__/kv.test.ts +133 -61
  29. package/src/__tests__/logger.test.ts +115 -0
  30. package/src/__tests__/pages.test.ts +311 -26
  31. package/src/__tests__/publish.test.ts +2789 -618
  32. package/src/__tests__/r2.test.ts +26 -10
  33. package/src/__tests__/route.test.ts +4 -4
  34. package/src/__tests__/secret.test.ts +55 -34
  35. package/src/__tests__/tail.test.ts +146 -20
  36. package/src/__tests__/user.test.ts +131 -0
  37. package/src/__tests__/whoami.test.tsx +14 -9
  38. package/src/bundle.ts +30 -3
  39. package/src/cfetch/internal.ts +0 -2
  40. package/src/cli.ts +5 -11
  41. package/src/config/README.md +1 -1
  42. package/src/config/config.ts +20 -4
  43. package/src/config/environment.ts +32 -4
  44. package/src/config/index.ts +4 -3
  45. package/src/config/validation-helpers.ts +96 -7
  46. package/src/config/validation.ts +388 -54
  47. package/src/config-cache.ts +45 -0
  48. package/src/create-worker-preview.ts +22 -13
  49. package/src/create-worker-upload-form.ts +32 -9
  50. package/src/dev/dev-vars.ts +36 -0
  51. package/src/dev/dev.tsx +132 -78
  52. package/src/dev/local.tsx +101 -21
  53. package/src/dev/remote.tsx +23 -13
  54. package/src/dev/use-esbuild.ts +23 -5
  55. package/src/dialogs.tsx +16 -6
  56. package/src/entry.ts +83 -24
  57. package/src/environment-variables.ts +3 -1
  58. package/src/errors.ts +11 -0
  59. package/src/https-options.ts +3 -2
  60. package/src/index.tsx +689 -394
  61. package/src/inspect.ts +17 -11
  62. package/src/intl-polyfill.d.ts +139 -0
  63. package/src/logger.ts +66 -0
  64. package/src/miniflare-cli/index.ts +4 -0
  65. package/src/miniflare-cli/request-context.ts +40 -0
  66. package/src/module-collection.ts +7 -7
  67. package/src/open-in-browser.ts +5 -4
  68. package/src/package-manager.ts +9 -8
  69. package/src/pages.tsx +875 -56
  70. package/src/parse.ts +1 -1
  71. package/src/proxy.ts +28 -19
  72. package/src/publish.ts +263 -141
  73. package/src/sites.tsx +27 -14
  74. package/src/tail/index.ts +2 -2
  75. package/src/tail/printing.ts +13 -8
  76. package/src/update-check.ts +19 -0
  77. package/src/user.tsx +116 -41
  78. package/src/whoami.tsx +2 -1
  79. package/src/worker.ts +15 -6
  80. package/templates/gitignore +171 -0
  81. package/templates/static-asset-facade.js +1 -1
  82. package/wrangler-dist/cli.js +21162 -50227
  83. package/miniflare-dist/index.mjs.map +0 -7
  84. package/src/__tests__/dev2.test.tsx +0 -154
  85. package/src/__tests__/logout.test.ts +0 -52
  86. package/src/__tests__/sentry.test.ts +0 -146
  87. package/src/reporting.ts +0 -131
  88. package/wrangler-dist/cli.js.map +0 -7
package/src/index.tsx CHANGED
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { setTimeout } from "node:timers/promises";
5
5
  import TOML from "@iarna/toml";
6
6
  import chalk from "chalk";
7
+ import { execa } from "execa";
7
8
  import { findUp } from "find-up";
8
9
  import getPort from "get-port";
9
10
  import { render } from "ink";
@@ -16,8 +17,10 @@ import { fetchResult } from "./cfetch";
16
17
  import { findWranglerToml, readConfig } from "./config";
17
18
  import { createWorkerUploadForm } from "./create-worker-upload-form";
18
19
  import Dev from "./dev/dev";
20
+ import { getVarsForDev } from "./dev/dev-vars";
19
21
  import { confirm, prompt } from "./dialogs";
20
22
  import { getEntry } from "./entry";
23
+ import { DeprecationError } from "./errors";
21
24
  import {
22
25
  getNamespaceId,
23
26
  listNamespaces,
@@ -31,9 +34,16 @@ import {
31
34
  isKeyValue,
32
35
  unexpectedKeyValueProps,
33
36
  } from "./kv";
37
+ import { logger } from "./logger";
34
38
  import { getPackageManager } from "./package-manager";
35
- import { pages } from "./pages";
36
- import { formatMessage, ParseError, parseJSON, readFileSync } from "./parse";
39
+ import { pages, pagesBetaWarning } from "./pages";
40
+ import {
41
+ formatMessage,
42
+ ParseError,
43
+ parseJSON,
44
+ parseTOML,
45
+ readFileSync,
46
+ } from "./parse";
37
47
  import publish from "./publish";
38
48
  import { createR2Bucket, deleteR2Bucket, listR2Buckets } from "./r2";
39
49
  import { getAssetPaths } from "./sites";
@@ -43,13 +53,13 @@ import {
43
53
  prettyPrintLogs,
44
54
  translateCLICommandToFilterMessage,
45
55
  } from "./tail";
56
+ import { updateCheck } from "./update-check";
46
57
  import {
47
58
  login,
48
59
  logout,
49
60
  listScopes,
50
- loginOrRefreshIfRequired,
51
- getAccountId,
52
61
  validateScopeKeys,
62
+ requireAuth,
53
63
  } from "./user";
54
64
  import { whoami } from "./whoami";
55
65
 
@@ -74,8 +84,8 @@ function getRules(config: Config): Config["rules"] {
74
84
  }
75
85
 
76
86
  if (config.build?.upload?.rules) {
77
- console.warn(
78
- `Deprecation notice: The \`build.upload.rules\` config field is no longer used, the rules should be specified via the \`rules\` config field. Delete the \`build.upload\` field from the configuration file, and add this:
87
+ logger.warn(
88
+ `Deprecation: The \`build.upload.rules\` config field is no longer used, the rules should be specified via the \`rules\` config field. Delete the \`build.upload\` field from the configuration file, and add this:
79
89
 
80
90
  ${TOML.stringify({ rules: config.build.upload.rules })}`
81
91
  );
@@ -83,14 +93,15 @@ ${TOML.stringify({ rules: config.build.upload.rules })}`
83
93
  return rules;
84
94
  }
85
95
 
86
- function printWranglerBanner() {
96
+ async function printWranglerBanner() {
87
97
  // Let's not print this in tests
88
98
  if (typeof jest !== "undefined") {
89
99
  return;
90
100
  }
91
- const text = ` ⛅️ wrangler ${wranglerVersion} `;
92
101
 
93
- console.log(
102
+ const text = ` ⛅️ wrangler ${wranglerVersion} ${await updateCheck()}`;
103
+
104
+ logger.log(
94
105
  text +
95
106
  "\n" +
96
107
  (supportsColor.stdout
@@ -99,45 +110,39 @@ function printWranglerBanner() {
99
110
  );
100
111
  }
101
112
 
102
- function isLegacyEnv(args: unknown, config: Config): boolean {
103
- return (
104
- (args as { "legacy-env": boolean | undefined })["legacy-env"] ??
105
- config.legacy_env
106
- );
113
+ function isLegacyEnv(config: Config): boolean {
114
+ // We only read from config here, because we've already accounted for
115
+ // args["legacy-env"] in https://github.com/cloudflare/wrangler2/blob/b24aeb5722370c2e04bce97a84a1fa1e55725d79/packages/wrangler/src/config/validation.ts#L94-L98
116
+ return config.legacy_env;
107
117
  }
108
118
 
109
119
  function getScriptName(
110
120
  args: { name: string | undefined; env: string | undefined },
111
121
  config: Config
112
122
  ): string | undefined {
113
- const shortScriptName = args.name ?? config.name;
114
- if (!shortScriptName) {
115
- return;
123
+ if (args.name && isLegacyEnv(config) && args.env) {
124
+ throw new CommandLineArgsError(
125
+ "In legacy environment mode you cannot use --name and --env together. If you want to specify a Worker name for a specific environment you can add the following to your wrangler.toml config:" +
126
+ `
127
+ [env.${args.env}]
128
+ name = "${args.name}"
129
+ `
130
+ );
116
131
  }
117
132
 
118
- return isLegacyEnv(args, config)
119
- ? `${shortScriptName}${args.env ? `-${args.env}` : ""}`
120
- : shortScriptName;
133
+ return args.name ?? config.name;
121
134
  }
122
135
 
123
136
  /**
124
- * Ensure that a user is logged in, and a valid account_id is available.
137
+ * Alternative to the getScriptName() because special Legacy cases allowed "name", and "env" together in Wrangler1
125
138
  */
126
- async function requireAuth(
127
- config: Config,
128
- isInteractive = true
129
- ): Promise<string> {
130
- const loggedIn = await loginOrRefreshIfRequired(isInteractive);
131
- if (!loggedIn) {
132
- // didn't login, let's just quit
133
- throw new Error("Did not login, quitting...");
134
- }
135
- const accountId = config.account_id || (await getAccountId(isInteractive));
136
- if (!accountId) {
137
- throw new Error("No account id found, quitting...");
138
- }
139
-
140
- return accountId;
139
+ function getLegacyScriptName(
140
+ args: { name: string | undefined; env: string | undefined },
141
+ config: Config
142
+ ) {
143
+ return args.name && args.env && isLegacyEnv(config)
144
+ ? `${args.name}-${args.env}`
145
+ : args.name ?? config.name;
141
146
  }
142
147
 
143
148
  /**
@@ -199,21 +204,18 @@ function demandOneOfOption(...options: string[]) {
199
204
  }
200
205
 
201
206
  class CommandLineArgsError extends Error {}
202
- class DeprecationError extends Error {
203
- constructor(message: string) {
204
- super(`DEPRECATION WARNING:\n${message}`);
205
- }
206
- }
207
207
 
208
208
  export async function main(argv: string[]): Promise<void> {
209
209
  const wrangler = makeCLI(argv)
210
+ .strict()
210
211
  // We handle errors ourselves in a try-catch around `yargs.parse`.
211
212
  // If you want the "help info" to be displayed then throw an instance of `CommandLineArgsError`.
212
213
  // Otherwise we just log the error that was thrown without any "help info".
213
214
  .showHelpOnFail(false)
214
215
  .fail((msg, error) => {
215
- if (!error) {
216
- // If there is only a `msg` then this came from yargs own validation, so wrap in a `CommandLineArgsError`.
216
+ if (!error || error.name === "YError") {
217
+ // If there is no error or the error is a "YError", then this came from yargs own validation
218
+ // Wrap it in a `CommandLineArgsError` so that we can handle it appropriately further up.
217
219
  error = new CommandLineArgsError(msg);
218
220
  }
219
221
  throw error;
@@ -263,14 +265,19 @@ export async function main(argv: string[]): Promise<void> {
263
265
  default: "worker",
264
266
  })
265
267
  .positional("template", {
266
- describe: "a link to a GitHub template",
268
+ describe: "The URL of a GitHub template",
267
269
  default: "https://github.com/cloudflare/worker-template",
268
270
  });
269
271
  },
270
- () => {
272
+ (generateArgs) => {
271
273
  // "👯 [DEPRECATED]. Scaffold a Cloudflare Workers project from a public GitHub repository.",
272
274
  throw new DeprecationError(
273
- "`wrangler generate` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#generate for alternatives"
275
+ "`wrangler generate` has been deprecated.\n" +
276
+ "Try running `wrangler init` to generate a basic Worker, or cloning the template repository instead:\n\n" +
277
+ "```\n" +
278
+ `git clone ${generateArgs.template}\n` +
279
+ "```\n\n" +
280
+ "Please refer to https://developers.cloudflare.com/workers/wrangler/deprecations/#generate for more information."
274
281
  );
275
282
  }
276
283
  );
@@ -285,6 +292,18 @@ export async function main(argv: string[]): Promise<void> {
285
292
  describe: "The name of your worker",
286
293
  type: "string",
287
294
  })
295
+ .option("type", {
296
+ describe: "The type of worker to create",
297
+ type: "string",
298
+ choices: ["rust", "javascript", "webpack"],
299
+ hidden: true,
300
+ deprecated: true,
301
+ })
302
+ .option("site", {
303
+ hidden: true,
304
+ type: "boolean",
305
+ deprecated: true,
306
+ })
288
307
  .option("yes", {
289
308
  describe: 'Answer "yes" to any prompts for new projects',
290
309
  type: "boolean",
@@ -292,8 +311,8 @@ export async function main(argv: string[]): Promise<void> {
292
311
  });
293
312
  },
294
313
  async (args) => {
295
- printWranglerBanner();
296
- if ("type" in args) {
314
+ await printWranglerBanner();
315
+ if (args.type) {
297
316
  let message = "The --type option is no longer supported.";
298
317
  if (args.type === "webpack") {
299
318
  message +=
@@ -303,9 +322,30 @@ export async function main(argv: string[]): Promise<void> {
303
322
  throw new CommandLineArgsError(message);
304
323
  }
305
324
 
306
- // TODO: make sure args.name is a valid identifier for a worker name
325
+ const creationDirectory = path.resolve(process.cwd(), args.name ?? "");
326
+
327
+ if (args.site) {
328
+ const gitDirectory =
329
+ creationDirectory !== process.cwd()
330
+ ? path.basename(creationDirectory)
331
+ : "my-site";
332
+ const message =
333
+ "The --site option is no longer supported.\n" +
334
+ "If you wish to create a brand new Worker Sites project then clone the `worker-sites-template` starter repository:\n\n" +
335
+ "```\n" +
336
+ `git clone --depth=1 --branch=wrangler2 https://github.com/cloudflare/worker-sites-template ${gitDirectory}\n` +
337
+ `cd ${gitDirectory}\n` +
338
+ "```\n\n" +
339
+ "Find out more about how to create and maintain Sites projects at https://developers.cloudflare.com/workers/platform/sites.\n" +
340
+ "Have you considered using Cloudflare Pages instead? See https://pages.cloudflare.com/.";
341
+ throw new CommandLineArgsError(message);
342
+ }
307
343
 
308
- const creationDirectory = path.join(process.cwd(), args.name ?? "");
344
+ // TODO: make sure args.name is a valid identifier for a worker name
345
+ const workerName = path
346
+ .basename(creationDirectory)
347
+ .toLowerCase()
348
+ .replaceAll(/[^a-z0-9\-_]/gm, "-");
309
349
 
310
350
  const packageManager = await getPackageManager(creationDirectory);
311
351
 
@@ -317,10 +357,14 @@ export async function main(argv: string[]): Promise<void> {
317
357
  "./wrangler.toml"
318
358
  );
319
359
  let justCreatedWranglerToml = false;
320
- const workerName =
321
- args.name || path.basename(path.resolve(process.cwd()));
360
+
322
361
  if (fs.existsSync(wranglerTomlDestination)) {
323
- console.warn(`${wranglerTomlDestination} file already exists!`);
362
+ logger.warn(
363
+ `${path.relative(
364
+ process.cwd(),
365
+ wranglerTomlDestination
366
+ )} already exists!`
367
+ );
324
368
  const shouldContinue = await confirm(
325
369
  "Do you want to continue initializing this project?"
326
370
  );
@@ -330,6 +374,7 @@ export async function main(argv: string[]): Promise<void> {
330
374
  } else {
331
375
  await mkdir(creationDirectory, { recursive: true });
332
376
  const compatibilityDate = new Date().toISOString().substring(0, 10);
377
+
333
378
  try {
334
379
  await writeFile(
335
380
  wranglerTomlDestination,
@@ -338,18 +383,56 @@ export async function main(argv: string[]): Promise<void> {
338
383
  compatibility_date: compatibilityDate,
339
384
  }) + "\n"
340
385
  );
341
- console.log(`✨ Successfully created wrangler.toml`);
386
+
387
+ logger.log(
388
+ `✨ Created ${path.relative(
389
+ process.cwd(),
390
+ wranglerTomlDestination
391
+ )}`
392
+ );
342
393
  justCreatedWranglerToml = true;
343
394
  } catch (err) {
344
395
  throw new Error(
345
- `Failed to create wrangler.toml.\n${(err as Error).message ?? err}`
396
+ `Failed to create ${path.relative(
397
+ process.cwd(),
398
+ wranglerTomlDestination
399
+ )}.\n${(err as Error).message ?? err}`
346
400
  );
347
401
  }
348
402
  }
349
403
 
350
- let pathToPackageJson = await findUp("package.json");
351
- let shouldCreatePackageJson = false;
352
404
  const yesFlag = args.yes ?? false;
405
+
406
+ const isInsideGitProject = Boolean(
407
+ await findUp(".git", { cwd: creationDirectory, type: "directory" })
408
+ );
409
+ const isGitInstalled = (await execa("git", ["--version"])).exitCode === 0;
410
+ if (!isInsideGitProject && isGitInstalled) {
411
+ const shouldInitGit =
412
+ yesFlag ||
413
+ (await confirm("Would you like to use git to manage this Worker?"));
414
+ if (shouldInitGit) {
415
+ await execa("git", ["init"], { cwd: creationDirectory });
416
+ await writeFile(
417
+ path.join(creationDirectory, ".gitignore"),
418
+ readFileSync(path.join(__dirname, "../templates/gitignore"))
419
+ );
420
+ logger.log(
421
+ args.name && args.name !== "."
422
+ ? `✨ Initialized git repository at ${path.relative(
423
+ process.cwd(),
424
+ creationDirectory
425
+ )}`
426
+ : `✨ Initialized git repository`
427
+ );
428
+ }
429
+ }
430
+
431
+ let pathToPackageJson = await findUp("package.json", {
432
+ cwd: creationDirectory,
433
+ });
434
+ let shouldCreatePackageJson = false;
435
+
353
436
  if (!pathToPackageJson) {
354
437
  // If no package.json exists, ask to create one
355
438
  shouldCreatePackageJson =
@@ -374,9 +457,12 @@ export async function main(argv: string[]): Promise<void> {
374
457
  " "
375
458
  ) + "\n"
376
459
  );
460
+
377
461
  await packageManager.install();
378
- console.log(`✨ Created package.json`);
379
462
  pathToPackageJson = path.join(creationDirectory, "package.json");
463
+ logger.log(
464
+ `✨ Created ${path.relative(process.cwd(), pathToPackageJson)}`
465
+ );
380
466
  } else {
381
467
  return;
382
468
  }
@@ -396,17 +482,22 @@ export async function main(argv: string[]): Promise<void> {
396
482
  const shouldInstall =
397
483
  yesFlag ||
398
484
  (await confirm(
399
- "Would you like to install wrangler into your package.json?"
485
+ `Would you like to install wrangler into ${path.relative(
486
+ process.cwd(),
487
+ pathToPackageJson
488
+ )}?`
400
489
  ));
401
490
  if (shouldInstall) {
402
491
  await packageManager.addDevDeps(`wrangler@${wranglerVersion}`);
403
- console.log(`✨ Installed wrangler`);
492
+ logger.log(`✨ Installed wrangler`);
404
493
  }
405
494
  }
406
495
  }
407
496
 
408
497
  let isTypescriptProject = false;
409
- let pathToTSConfig = await findUp("tsconfig.json");
498
+ let pathToTSConfig = await findUp("tsconfig.json", {
499
+ cwd: creationDirectory,
500
+ });
410
501
  if (!pathToTSConfig) {
411
502
  // If there's no tsconfig, offer to create one
412
503
  // and install @cloudflare/workers-types
@@ -420,11 +511,13 @@ export async function main(argv: string[]): Promise<void> {
420
511
  "@cloudflare/workers-types",
421
512
  "typescript"
422
513
  );
423
-
424
- console.log(
425
- `✨ Created tsconfig.json, installed @cloudflare/workers-types into devDependencies`
426
- );
427
514
  pathToTSConfig = path.join(creationDirectory, "tsconfig.json");
515
+ logger.log(
516
+ `✨ Created ${path.relative(
517
+ process.cwd(),
518
+ pathToTSConfig
519
+ )}, installed @cloudflare/workers-types into devDependencies`
520
+ );
428
521
  }
429
522
  } else {
430
523
  isTypescriptProject = true;
@@ -449,8 +542,11 @@ export async function main(argv: string[]): Promise<void> {
449
542
  // it could be complicated in existing projects
450
543
  // and we don't want to break them. Instead, we simply
451
544
  // tell the user that they need to update their tsconfig.json
452
- console.log(
453
- `✨ Installed @cloudflare/workers-types.\nPlease add "@cloudflare/workers-types" to compilerOptions.types in your tsconfig.json`
545
+ logger.log(
546
+ `✨ Installed @cloudflare/workers-types.\nPlease add "@cloudflare/workers-types" to compilerOptions.types in ${path.relative(
547
+ process.cwd(),
548
+ pathToTSConfig
549
+ )}`
454
550
  );
455
551
  }
456
552
  }
@@ -473,7 +569,7 @@ export async function main(argv: string[]): Promise<void> {
473
569
  ) {
474
570
  if (isCreatingWranglerToml) {
475
571
  // rewrite wrangler.toml with main = "path/to/script"
476
- const parsedWranglerToml = TOML.parse(
572
+ const parsedWranglerToml = parseTOML(
477
573
  readFileSync(wranglerTomlDestination)
478
574
  );
479
575
  fs.writeFileSync(
@@ -508,21 +604,21 @@ export async function main(argv: string[]): Promise<void> {
508
604
  2
509
605
  ) + "\n"
510
606
  );
511
- console.log(
607
+ logger.log(
512
608
  `\nTo start developing your Worker, run \`${
513
609
  isNamedWorker ? `cd ${args.name} && ` : ""
514
610
  }npm start\``
515
611
  );
516
- console.log(
612
+ logger.log(
517
613
  `To publish your Worker to the Internet, run \`npm run publish\``
518
614
  );
519
615
  } else {
520
- console.log(
616
+ logger.log(
521
617
  `\nTo start developing your Worker, run \`npx wrangler dev\`${
522
618
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
523
619
  }`
524
620
  );
525
- console.log(
621
+ logger.log(
526
622
  `To publish your Worker to the Internet, run \`npx wrangler publish\`${
527
623
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
528
624
  }`
@@ -532,12 +628,13 @@ export async function main(argv: string[]): Promise<void> {
532
628
 
533
629
  if (isTypescriptProject) {
534
630
  if (!fs.existsSync(path.join(creationDirectory, "./src/index.ts"))) {
535
- let shouldCreateSource = false;
536
-
537
- shouldCreateSource =
631
+ const shouldCreateSource =
538
632
  yesFlag ||
539
633
  (await confirm(
540
- `Would you like to create a Worker at src/index.ts?`
634
+ `Would you like to create a Worker at ${path.relative(
635
+ process.cwd(),
636
+ path.join(creationDirectory, "./src/index.ts")
637
+ )}?`
541
638
  ));
542
639
 
543
640
  if (shouldCreateSource) {
@@ -549,7 +646,12 @@ export async function main(argv: string[]): Promise<void> {
549
646
  readFileSync(path.join(__dirname, "../templates/new-worker.ts"))
550
647
  );
551
648
 
552
- console.log(`✨ Created src/index.ts`);
649
+ logger.log(
650
+ `✨ Created ${path.relative(
651
+ process.cwd(),
652
+ path.join(creationDirectory, "./src/index.ts")
653
+ )}`
654
+ );
553
655
 
554
656
  await writePackageJsonScriptsAndUpdateWranglerToml(
555
657
  shouldWritePackageJsonScripts,
@@ -560,20 +662,31 @@ export async function main(argv: string[]): Promise<void> {
560
662
  }
561
663
  }
562
664
  } else {
563
- if (!fs.existsSync("./src/index.js")) {
564
- const shouldCreateSource = await confirm(
565
- `Would you like to create a Worker at src/index.js?`
566
- );
665
+ if (!fs.existsSync(path.join(creationDirectory, "./src/index.js"))) {
666
+ const shouldCreateSource =
667
+ yesFlag ||
668
+ (await confirm(
669
+ `Would you like to create a Worker at ${path.relative(
670
+ process.cwd(),
671
+ path.join(creationDirectory, "./src/index.js")
672
+ )}?`
673
+ ));
674
+
567
675
  if (shouldCreateSource) {
568
676
  await mkdir(path.join(creationDirectory, "./src"), {
569
677
  recursive: true,
570
678
  });
571
679
  await writeFile(
572
- path.join(path.join(creationDirectory, "./src/index.js")),
680
+ path.join(creationDirectory, "./src/index.js"),
573
681
  readFileSync(path.join(__dirname, "../templates/new-worker.js"))
574
682
  );
575
683
 
576
- console.log(`✨ Created src/index.js`);
684
+ logger.log(
685
+ `✨ Created ${path.relative(
686
+ process.cwd(),
687
+ path.join(creationDirectory, "./src/index.js")
688
+ )}`
689
+ );
577
690
 
578
691
  await writePackageJsonScriptsAndUpdateWranglerToml(
579
692
  shouldWritePackageJsonScripts,
@@ -604,79 +717,6 @@ export async function main(argv: string[]): Promise<void> {
604
717
  }
605
718
  );
606
719
 
607
- // login
608
- wrangler.command(
609
- // this needs scopes as an option?
610
- "login",
611
- false, // we don't need to show this in the menu
612
- // "🔓 Login to Cloudflare",
613
- (yargs) => {
614
- // TODO: This needs some copy editing
615
- // I mean, this entire app does, but this too.
616
- return yargs
617
- .option("scopes-list", {
618
- describe: "List all the available OAuth scopes with descriptions",
619
- })
620
- .option("scopes", {
621
- describe: "Pick the set of applicable OAuth scopes when logging in",
622
- array: true,
623
- type: "string",
624
- });
625
-
626
- // TODO: scopes
627
- },
628
- async (args) => {
629
- printWranglerBanner();
630
- if (args["scopes-list"]) {
631
- listScopes();
632
- return;
633
- }
634
- if (args.scopes) {
635
- if (args.scopes.length === 0) {
636
- // don't allow no scopes to be passed, that would be weird
637
- listScopes();
638
- return;
639
- }
640
- if (!validateScopeKeys(args.scopes)) {
641
- throw new CommandLineArgsError(
642
- `One of ${args.scopes} is not a valid authentication scope. Run "wrangler login --list-scopes" to see the valid scopes.`
643
- );
644
- }
645
- await login({ scopes: args.scopes });
646
- return;
647
- }
648
- await login();
649
-
650
- // TODO: would be nice if it optionally saved login
651
- // credentials inside node_modules/.cache or something
652
- // this way you could have multiple users on a single machine
653
- }
654
- );
655
-
656
- // logout
657
- wrangler.command(
658
- // this needs scopes as an option?
659
- "logout",
660
- false, // we don't need to show this in the menu
661
- // "🚪 Logout from Cloudflare",
662
- () => {},
663
- async () => {
664
- printWranglerBanner();
665
- await logout();
666
- }
667
- );
668
-
669
- // whoami
670
- wrangler.command(
671
- "whoami",
672
- "🕵️ Retrieve your user info and test your auth config",
673
- () => {},
674
- async () => {
675
- printWranglerBanner();
676
- await whoami();
677
- }
678
- );
679
-
680
720
  // config
681
721
  wrangler.command(
682
722
  "config",
@@ -703,24 +743,30 @@ export async function main(argv: string[]): Promise<void> {
703
743
  .option("name", {
704
744
  describe: "Name of the worker",
705
745
  type: "string",
746
+ requiresArg: true,
706
747
  })
707
748
  .option("format", {
708
749
  choices: ["modules", "service-worker"] as const,
709
750
  describe: "Choose an entry type",
751
+ deprecated: true,
710
752
  })
711
753
  .option("env", {
712
754
  describe: "Perform on a specific environment",
713
755
  type: "string",
756
+ requiresArg: true,
714
757
  alias: "e",
715
758
  })
716
759
  .option("compatibility-date", {
717
760
  describe: "Date to use for compatibility checks",
718
761
  type: "string",
762
+ requiresArg: true,
719
763
  })
720
764
  .option("compatibility-flags", {
721
765
  describe: "Flags to use for compatibility checks",
722
- type: "array",
723
766
  alias: "compatibility-flag",
767
+ type: "string",
768
+ requiresArg: true,
769
+ array: true,
724
770
  })
725
771
  .option("latest", {
726
772
  describe: "Use the latest version of the worker runtime",
@@ -730,6 +776,7 @@ export async function main(argv: string[]): Promise<void> {
730
776
  .option("ip", {
731
777
  describe: "IP address to listen on, defaults to `localhost`",
732
778
  type: "string",
779
+ requiresArg: true,
733
780
  })
734
781
  .option("port", {
735
782
  describe: "Port to listen on",
@@ -742,10 +789,13 @@ export async function main(argv: string[]): Promise<void> {
742
789
  .option("routes", {
743
790
  describe: "Routes to upload",
744
791
  alias: "route",
745
- type: "array",
792
+ type: "string",
793
+ requiresArg: true,
794
+ array: true,
746
795
  })
747
796
  .option("host", {
748
797
  type: "string",
798
+ requiresArg: true,
749
799
  describe:
750
800
  "Host to forward requests to, defaults to the zone of project",
751
801
  })
@@ -756,21 +806,25 @@ export async function main(argv: string[]): Promise<void> {
756
806
  .option("experimental-public", {
757
807
  describe: "Static assets to be served",
758
808
  type: "string",
809
+ requiresArg: true,
759
810
  })
760
811
  .option("site", {
761
812
  describe: "Root folder of static assets for Workers Sites",
762
813
  type: "string",
814
+ requiresArg: true,
763
815
  })
764
816
  .option("site-include", {
765
817
  describe:
766
818
  "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.",
767
819
  type: "string",
820
+ requiresArg: true,
768
821
  array: true,
769
822
  })
770
823
  .option("site-exclude", {
771
824
  describe:
772
825
  "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.",
773
826
  type: "string",
827
+ requiresArg: true,
774
828
  array: true,
775
829
  })
776
830
  .option("upstream-protocol", {
@@ -781,10 +835,17 @@ export async function main(argv: string[]): Promise<void> {
781
835
  .option("jsx-factory", {
782
836
  describe: "The function that is called for each JSX element",
783
837
  type: "string",
838
+ requiresArg: true,
784
839
  })
785
840
  .option("jsx-fragment", {
786
841
  describe: "The function that is called for each JSX fragment",
787
842
  type: "string",
843
+ requiresArg: true,
844
+ })
845
+ .option("tsconfig", {
846
+ describe: "Path to a custom tsconfig.json file",
847
+ type: "string",
848
+ requiresArg: true,
788
849
  })
789
850
  .option("local", {
790
851
  alias: "l",
@@ -792,36 +853,54 @@ export async function main(argv: string[]): Promise<void> {
792
853
  type: "boolean",
793
854
  default: false, // I bet this will a point of contention. We'll revisit it.
794
855
  })
856
+ .option("minify", {
857
+ describe: "Minify the script",
858
+ type: "boolean",
859
+ })
860
+ .option("node-compat", {
861
+ describe: "Enable node.js compatibility",
862
+ type: "boolean",
863
+ })
795
864
  .option("experimental-enable-local-persistence", {
796
865
  describe: "Enable persistence for this session (only for local mode)",
797
866
  type: "boolean",
867
+ })
868
+ .option("inspect", {
869
+ describe: "Enable dev tools",
870
+ type: "boolean",
871
+ deprecated: true,
798
872
  });
799
873
  },
800
874
  async (args) => {
801
- printWranglerBanner();
875
+ await printWranglerBanner();
802
876
  const configPath =
803
877
  (args.config as ConfigPath) ||
804
878
  (args.script && findWranglerToml(path.dirname(args.script)));
805
- const config = readConfig(configPath, args.env);
879
+ const config = readConfig(configPath, args);
806
880
  const entry = await getEntry(args, config, "dev");
807
881
 
882
+ if (args.inspect) {
883
+ logger.warn(
884
+ "Passing --inspect is unnecessary, now you can always connect to devtools."
885
+ );
886
+ }
887
+
808
888
  if (args["experimental-public"]) {
809
- console.warn(
810
- "🚨 The --experimental-public field is experimental and will change in the future."
889
+ logger.warn(
890
+ "The --experimental-public field is experimental and will change in the future."
811
891
  );
812
892
  }
813
893
 
814
894
  if (args.public) {
815
895
  throw new Error(
816
- "🚨 The --public field has been renamed to --experimental-public, and will change behaviour in the future."
896
+ "The --public field has been renamed to --experimental-public, and will change behaviour in the future."
817
897
  );
818
898
  }
819
899
 
820
900
  const upstreamProtocol =
821
- (args["upstream-protocol"] as "http" | "https") ||
822
- config.dev.upstream_protocol;
901
+ args["upstream-protocol"] || config.dev.upstream_protocol;
823
902
  if (upstreamProtocol === "http") {
824
- console.warn(
903
+ logger.warn(
825
904
  "Setting upstream-protocol to http is not currently implemented.\n" +
826
905
  "If this is required in your project, please add your use case to the following issue:\n" +
827
906
  "https://github.com/cloudflare/wrangler2/issues/583."
@@ -858,13 +937,6 @@ export async function main(argv: string[]): Promise<void> {
858
937
  return zones[0]?.id;
859
938
  }
860
939
 
861
- const hostLike =
862
- args.host ||
863
- config.dev.host ||
864
- (args.routes && args.routes[0]) ||
865
- config.route ||
866
- (config.routes && config.routes[0]);
867
-
868
940
  // When we're given a host (in one of the above ways), we do 2 things:
869
941
  // - We try to extract a host from it
870
942
  // - We try to get a zone id from the host
@@ -875,27 +947,57 @@ export async function main(argv: string[]): Promise<void> {
875
947
  // get a zone id for it, by lopping off subdomains until we get a hit
876
948
  // from the API. That's it!
877
949
 
878
- const host = typeof hostLike === "string" ? getHost(hostLike) : undefined;
950
+ let zone: { host: string; id: string } | undefined;
951
+
952
+ if (!args.local) {
953
+ const hostLike =
954
+ args.host ||
955
+ config.dev.host ||
956
+ (args.routes && args.routes[0]) ||
957
+ config.route ||
958
+ (config.routes && config.routes[0]);
959
+
960
+ let zoneId: string | undefined =
961
+ typeof hostLike === "object" && "zone_id" in hostLike
962
+ ? hostLike.zone_id
963
+ : undefined;
964
+
965
+ const host =
966
+ typeof hostLike === "string"
967
+ ? getHost(hostLike)
968
+ : typeof hostLike === "object"
969
+ ? "zone_name" in hostLike
970
+ ? getHost(hostLike.zone_name)
971
+ : getHost(hostLike.pattern)
972
+ : undefined;
973
+
974
+ const hostPieces =
975
+ typeof host === "string" ? host.split(".") : undefined;
976
+
977
+ while (!zoneId && hostPieces && hostPieces.length > 1) {
978
+ zoneId = await getZoneId(hostPieces.join("."));
979
+ hostPieces.shift();
980
+ }
879
981
 
880
- let zoneId: string | undefined;
881
- const hostPieces = typeof host === "string" ? host.split(".") : undefined;
982
+ if (host && !zoneId) {
983
+ throw new Error(`Could not find zone for ${hostLike}`);
984
+ }
882
985
 
883
- while (hostPieces && hostPieces.length > 1) {
884
- zoneId = await getZoneId(hostPieces.join("."));
885
- if (zoneId) break;
886
- hostPieces.shift();
887
- }
888
- if (host && !zoneId) {
889
- throw new Error(`Could not find zone for ${hostLike}`);
986
+ zone =
987
+ typeof zoneId === "string" && typeof host === "string"
988
+ ? {
989
+ host,
990
+ id: zoneId,
991
+ }
992
+ : undefined;
890
993
  }
891
994
 
892
- const zone =
893
- typeof zoneId === "string" && typeof host === "string"
894
- ? {
895
- host,
896
- id: zoneId,
897
- }
898
- : undefined;
995
+ const nodeCompat = args.nodeCompat ?? config.node_compat;
996
+ if (nodeCompat) {
997
+ logger.warn(
998
+ "Enabling node.js compatibility mode for builtins and globals. This is experimental and has serious tradeoffs. Please see https://github.com/ionic-team/rollup-plugin-node-polyfills/ for more details."
999
+ );
1000
+ }
899
1001
 
900
1002
  const { waitUntilExit } = render(
901
1003
  <Dev
@@ -904,18 +1006,16 @@ export async function main(argv: string[]): Promise<void> {
904
1006
  env={args.env}
905
1007
  zone={zone}
906
1008
  rules={getRules(config)}
907
- legacyEnv={isLegacyEnv(args, config)}
908
- buildCommand={config.build || {}}
1009
+ legacyEnv={isLegacyEnv(config)}
1010
+ minify={args.minify ?? config.minify}
1011
+ nodeCompat={nodeCompat}
1012
+ build={config.build || {}}
909
1013
  initialMode={args.local ? "local" : "remote"}
910
1014
  jsxFactory={args["jsx-factory"] || config.jsx_factory}
911
1015
  jsxFragment={args["jsx-fragment"] || config.jsx_fragment}
1016
+ tsconfig={args.tsconfig ?? config.tsconfig}
912
1017
  upstreamProtocol={upstreamProtocol}
913
- localProtocol={
914
- // The typings are not quite clever enough to handle element accesses, only property accesses,
915
- // so we need to cast here.
916
- (args["local-protocol"] as "http" | "https") ||
917
- config.dev.local_protocol
918
- }
1018
+ localProtocol={args["local-protocol"] || config.dev.local_protocol}
919
1019
  enableLocalPersistence={
920
1020
  args["experimental-enable-local-persistence"] || false
921
1021
  }
@@ -934,14 +1034,12 @@ export async function main(argv: string[]): Promise<void> {
934
1034
  args["inspector-port"] ?? (await getPort({ port: 9229 }))
935
1035
  }
936
1036
  public={args["experimental-public"]}
937
- compatibilityDate={
938
- args["compatibility-date"] ||
939
- config.compatibility_date ||
940
- new Date().toISOString().substring(0, 10)
941
- }
1037
+ compatibilityDate={getDevCompatibilityDate(
1038
+ config,
1039
+ args["compatibility-date"]
1040
+ )}
942
1041
  compatibilityFlags={
943
- (args["compatibility-flags"] as string[]) ||
944
- config.compatibility_flags
1042
+ args["compatibility-flags"] || config.compatibility_flags
945
1043
  }
946
1044
  usageModel={config.usage_model}
947
1045
  bindings={{
@@ -966,13 +1064,29 @@ export async function main(argv: string[]): Promise<void> {
966
1064
  };
967
1065
  }
968
1066
  ),
969
- vars: config.vars,
1067
+ vars: getVarsForDev(config),
970
1068
  wasm_modules: config.wasm_modules,
971
1069
  text_blobs: config.text_blobs,
1070
+ data_blobs: config.data_blobs,
972
1071
  durable_objects: config.durable_objects,
973
- r2_buckets: config.r2_buckets,
1072
+ r2_buckets: config.r2_buckets?.map(
1073
+ ({ binding, preview_bucket_name, bucket_name: _bucket_name }) => {
1074
+ // same idea as kv namespace preview id,
1075
+ // same copy-on-write TODO
1076
+ if (!preview_bucket_name) {
1077
+ throw new Error(
1078
+ `In development, you should use a separate r2 bucket than the one you'd use in production. Please create a new r2 bucket with "wrangler r2 bucket create <name>" and add its name as preview_bucket_name to the r2_buckets "${binding}" in your wrangler.toml`
1079
+ );
1080
+ }
1081
+ return {
1082
+ binding,
1083
+ bucket_name: preview_bucket_name,
1084
+ };
1085
+ }
1086
+ ),
974
1087
  unsafe: config.unsafe?.bindings,
975
1088
  }}
1089
+ crons={config.triggers.crons}
976
1090
  />
977
1091
  );
978
1092
  await waitUntilExit();
@@ -987,29 +1101,41 @@ export async function main(argv: string[]): Promise<void> {
987
1101
  return yargs
988
1102
  .option("env", {
989
1103
  type: "string",
1104
+ requiresArg: true,
990
1105
  describe: "Perform on a specific environment",
991
1106
  alias: "e",
992
1107
  })
993
1108
  .positional("script", {
994
1109
  describe: "The path to an entry point for your worker",
995
1110
  type: "string",
1111
+ requiresArg: true,
996
1112
  })
997
1113
  .option("name", {
998
1114
  describe: "Name of the worker",
999
1115
  type: "string",
1116
+ requiresArg: true,
1117
+ })
1118
+ .option("outdir", {
1119
+ describe: "Output directory for the bundled worker",
1120
+ type: "string",
1121
+ requiresArg: true,
1000
1122
  })
1001
1123
  .option("format", {
1002
1124
  choices: ["modules", "service-worker"] as const,
1003
1125
  describe: "Choose an entry type",
1126
+ deprecated: true,
1004
1127
  })
1005
1128
  .option("compatibility-date", {
1006
1129
  describe: "Date to use for compatibility checks",
1007
1130
  type: "string",
1131
+ requiresArg: true,
1008
1132
  })
1009
1133
  .option("compatibility-flags", {
1010
1134
  describe: "Flags to use for compatibility checks",
1011
- type: "array",
1012
1135
  alias: "compatibility-flag",
1136
+ type: "string",
1137
+ requiresArg: true,
1138
+ array: true,
1013
1139
  })
1014
1140
  .option("latest", {
1015
1141
  describe: "Use the latest version of the worker runtime",
@@ -1019,64 +1145,91 @@ export async function main(argv: string[]): Promise<void> {
1019
1145
  .option("experimental-public", {
1020
1146
  describe: "Static assets to be served",
1021
1147
  type: "string",
1148
+ requiresArg: true,
1022
1149
  })
1023
1150
  .option("site", {
1024
1151
  describe: "Root folder of static assets for Workers Sites",
1025
1152
  type: "string",
1153
+ requiresArg: true,
1026
1154
  })
1027
1155
  .option("site-include", {
1028
1156
  describe:
1029
1157
  "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.",
1030
1158
  type: "string",
1159
+ requiresArg: true,
1031
1160
  array: true,
1032
1161
  })
1033
1162
  .option("site-exclude", {
1034
1163
  describe:
1035
1164
  "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.",
1036
1165
  type: "string",
1166
+ requiresArg: true,
1037
1167
  array: true,
1038
1168
  })
1039
1169
  .option("triggers", {
1040
1170
  describe: "cron schedules to attach",
1041
1171
  alias: ["schedule", "schedules"],
1042
- type: "array",
1172
+ type: "string",
1173
+ requiresArg: true,
1174
+ array: true,
1043
1175
  })
1044
1176
  .option("routes", {
1045
1177
  describe: "Routes to upload",
1046
1178
  alias: "route",
1047
- type: "array",
1179
+ type: "string",
1180
+ requiresArg: true,
1181
+ array: true,
1048
1182
  })
1049
1183
  .option("jsx-factory", {
1050
1184
  describe: "The function that is called for each JSX element",
1051
1185
  type: "string",
1186
+ requiresArg: true,
1052
1187
  })
1053
1188
  .option("jsx-fragment", {
1054
1189
  describe: "The function that is called for each JSX fragment",
1055
1190
  type: "string",
1191
+ requiresArg: true,
1192
+ })
1193
+ .option("tsconfig", {
1194
+ describe: "Path to a custom tsconfig.json file",
1195
+ type: "string",
1196
+ requiresArg: true,
1197
+ })
1198
+ .option("minify", {
1199
+ describe: "Minify the script",
1200
+ type: "boolean",
1201
+ })
1202
+ .option("node-compat", {
1203
+ describe: "Enable node.js compatibility",
1204
+ type: "boolean",
1205
+ })
1206
+ .option("dry-run", {
1207
+ describe: "Don't actually publish",
1208
+ type: "boolean",
1056
1209
  });
1057
1210
  },
1058
1211
  async (args) => {
1059
- printWranglerBanner();
1212
+ await printWranglerBanner();
1060
1213
  if (args["experimental-public"]) {
1061
- console.warn(
1062
- "🚨 The --experimental-public field is experimental and will change in the future."
1214
+ logger.warn(
1215
+ "The --experimental-public field is experimental and will change in the future."
1063
1216
  );
1064
1217
  }
1065
1218
  if (args.public) {
1066
1219
  throw new Error(
1067
- "🚨 The --public field has been renamed to --experimental-public, and will change behaviour in the future."
1220
+ "The --public field has been renamed to --experimental-public, and will change behaviour in the future."
1068
1221
  );
1069
1222
  }
1070
1223
 
1071
1224
  const configPath =
1072
1225
  (args.config as ConfigPath) ||
1073
1226
  (args.script && findWranglerToml(path.dirname(args.script)));
1074
- const config = readConfig(configPath, args.env);
1227
+ const config = readConfig(configPath, args);
1075
1228
  const entry = await getEntry(args, config, "publish");
1076
1229
 
1077
1230
  if (args.latest) {
1078
- console.warn(
1079
- "⚠️ 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"
1231
+ logger.warn(
1232
+ "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"
1080
1233
  );
1081
1234
  }
1082
1235
 
@@ -1098,14 +1251,19 @@ export async function main(argv: string[]): Promise<void> {
1098
1251
  compatibilityDate: args.latest
1099
1252
  ? new Date().toISOString().substring(0, 10)
1100
1253
  : args["compatibility-date"],
1101
- compatibilityFlags: args["compatibility-flags"] as string[],
1254
+ compatibilityFlags: args["compatibility-flags"],
1102
1255
  triggers: args.triggers,
1103
1256
  jsxFactory: args["jsx-factory"],
1104
1257
  jsxFragment: args["jsx-fragment"],
1258
+ tsconfig: args.tsconfig,
1105
1259
  routes: args.routes,
1106
1260
  assetPaths,
1107
- legacyEnv: isLegacyEnv(args, config),
1261
+ legacyEnv: isLegacyEnv(config),
1262
+ minify: args.minify,
1263
+ nodeCompat: args.nodeCompat,
1108
1264
  experimentalPublic: args["experimental-public"] !== undefined,
1265
+ outDir: args.outdir,
1266
+ dryRun: args.dryRun,
1109
1267
  });
1110
1268
  }
1111
1269
  );
@@ -1115,67 +1273,69 @@ export async function main(argv: string[]): Promise<void> {
1115
1273
  "tail [name]",
1116
1274
  "🦚 Starts a log tailing session for a published Worker.",
1117
1275
  (yargs) => {
1118
- return (
1119
- yargs
1120
- .positional("name", {
1121
- describe: "Name of the worker",
1122
- type: "string",
1123
- })
1124
- // TODO: auto-detect if this should be json or pretty based on atty
1125
- .option("format", {
1126
- default: "json",
1127
- choices: ["json", "pretty"],
1128
- describe: "The format of log entries",
1129
- })
1130
- .option("status", {
1131
- choices: ["ok", "error", "canceled"],
1132
- describe: "Filter by invocation status",
1133
- array: true,
1134
- })
1135
- .option("header", {
1136
- type: "string",
1137
- describe: "Filter by HTTP header",
1138
- })
1139
- .option("method", {
1140
- type: "string",
1141
- describe: "Filter by HTTP method",
1142
- array: true,
1143
- })
1144
- .option("sampling-rate", {
1145
- type: "number",
1146
- describe: "Adds a percentage of requests to log sampling rate",
1147
- })
1148
- .option("search", {
1149
- type: "string",
1150
- describe: "Filter by a text match in console.log messages",
1151
- })
1152
- .option("ip", {
1153
- type: "string",
1154
- describe:
1155
- 'Filter by the IP address the request originates from. Use "self" to filter for your own IP',
1156
- array: true,
1157
- })
1158
- .option("env", {
1159
- type: "string",
1160
- describe: "Perform on a specific environment",
1161
- alias: "e",
1162
- })
1163
- .option("debug", {
1164
- type: "boolean",
1165
- hidden: true,
1166
- default: false,
1167
- describe:
1168
- "If a log would have been filtered out, send it through anyway alongside the filter which would have blocked it.",
1169
- })
1170
- );
1276
+ return yargs
1277
+ .positional("name", {
1278
+ describe: "Name of the worker",
1279
+ type: "string",
1280
+ })
1281
+ .option("format", {
1282
+ default: process.stdout.isTTY ? "pretty" : "json",
1283
+ choices: ["json", "pretty"],
1284
+ describe: "The format of log entries",
1285
+ })
1286
+ .option("status", {
1287
+ choices: ["ok", "error", "canceled"],
1288
+ describe: "Filter by invocation status",
1289
+ array: true,
1290
+ })
1291
+ .option("header", {
1292
+ type: "string",
1293
+ requiresArg: true,
1294
+ describe: "Filter by HTTP header",
1295
+ })
1296
+ .option("method", {
1297
+ type: "string",
1298
+ requiresArg: true,
1299
+ describe: "Filter by HTTP method",
1300
+ array: true,
1301
+ })
1302
+ .option("sampling-rate", {
1303
+ type: "number",
1304
+ describe: "Adds a percentage of requests to log sampling rate",
1305
+ })
1306
+ .option("search", {
1307
+ type: "string",
1308
+ requiresArg: true,
1309
+ describe: "Filter by a text match in console.log messages",
1310
+ })
1311
+ .option("ip", {
1312
+ type: "string",
1313
+ requiresArg: true,
1314
+ describe:
1315
+ 'Filter by the IP address the request originates from. Use "self" to filter for your own IP',
1316
+ array: true,
1317
+ })
1318
+ .option("env", {
1319
+ type: "string",
1320
+ requiresArg: true,
1321
+ describe: "Perform on a specific environment",
1322
+ alias: "e",
1323
+ })
1324
+ .option("debug", {
1325
+ type: "boolean",
1326
+ hidden: true,
1327
+ default: false,
1328
+ describe:
1329
+ "If a log would have been filtered out, send it through anyway alongside the filter which would have blocked it.",
1330
+ });
1171
1331
  },
1172
1332
  async (args) => {
1173
1333
  if (args.format === "pretty") {
1174
- printWranglerBanner();
1334
+ await printWranglerBanner();
1175
1335
  }
1176
- const config = readConfig(args.config as ConfigPath, args.env);
1336
+ const config = readConfig(args.config as ConfigPath, args);
1177
1337
 
1178
- const scriptName = getScriptName(args, config);
1338
+ const scriptName = getLegacyScriptName(args, config);
1179
1339
 
1180
1340
  if (!scriptName) {
1181
1341
  throw new Error("Missing script name");
@@ -1184,7 +1344,7 @@ export async function main(argv: string[]): Promise<void> {
1184
1344
  const accountId = await requireAuth(config);
1185
1345
 
1186
1346
  const cliFilters: TailCLIFilters = {
1187
- status: args.status as ("ok" | "error" | "canceled")[],
1347
+ status: args.status as ("ok" | "error" | "canceled")[] | undefined,
1188
1348
  header: args.header,
1189
1349
  method: args.method,
1190
1350
  samplingRate: args["sampling-rate"],
@@ -1201,16 +1361,18 @@ export async function main(argv: string[]): Promise<void> {
1201
1361
  accountId,
1202
1362
  scriptName,
1203
1363
  filters,
1204
- !isLegacyEnv(args, config) ? args.env : undefined
1364
+ !isLegacyEnv(config) ? args.env : undefined
1205
1365
  );
1206
1366
 
1207
1367
  const scriptDisplayName = `${scriptName}${
1208
- args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1368
+ args.env && !isLegacyEnv(config) ? ` (${args.env})` : ""
1209
1369
  }`;
1210
1370
 
1211
- console.log(
1212
- `successfully created tail, expires at ${expiration.toLocaleString()}`
1213
- );
1371
+ if (args.format === "pretty") {
1372
+ logger.log(
1373
+ `Successfully created tail, expires at ${expiration.toLocaleString()}`
1374
+ );
1375
+ }
1214
1376
 
1215
1377
  onExit(async () => {
1216
1378
  tail.terminate();
@@ -1237,7 +1399,9 @@ export async function main(argv: string[]): Promise<void> {
1237
1399
  }
1238
1400
  }
1239
1401
 
1240
- console.log(`Connected to ${scriptDisplayName}, waiting for logs...`);
1402
+ if (args.format === "pretty") {
1403
+ logger.log(`Connected to ${scriptDisplayName}, waiting for logs...`);
1404
+ }
1241
1405
 
1242
1406
  tail.on("close", async () => {
1243
1407
  tail.terminate();
@@ -1262,6 +1426,7 @@ export async function main(argv: string[]): Promise<void> {
1262
1426
  })
1263
1427
  .option("env", {
1264
1428
  type: "string",
1429
+ requiresArg: true,
1265
1430
  describe: "Perform on a specific environment",
1266
1431
  })
1267
1432
  .option("watch", {
@@ -1279,14 +1444,14 @@ export async function main(argv: string[]): Promise<void> {
1279
1444
  }
1280
1445
 
1281
1446
  // Delegate to `wrangler dev`
1282
- console.warn(
1447
+ logger.warn(
1283
1448
  "***************************************************\n" +
1284
1449
  "The `wrangler preview` command has been deprecated.\n" +
1285
1450
  "Attempting to run `wrangler dev` instead.\n" +
1286
1451
  "***************************************************\n"
1287
1452
  );
1288
1453
 
1289
- const config = readConfig(args.config as ConfigPath, args.env);
1454
+ const config = readConfig(args.config as ConfigPath, args);
1290
1455
  const entry = await getEntry({}, config, "dev");
1291
1456
 
1292
1457
  const accountId = await requireAuth(config);
@@ -1298,11 +1463,14 @@ export async function main(argv: string[]): Promise<void> {
1298
1463
  rules={getRules(config)}
1299
1464
  env={args.env}
1300
1465
  zone={undefined}
1301
- legacyEnv={isLegacyEnv(args, config)}
1302
- buildCommand={config.build || {}}
1466
+ legacyEnv={isLegacyEnv(config)}
1467
+ build={config.build || {}}
1468
+ minify={undefined}
1469
+ nodeCompat={config.node_compat}
1303
1470
  initialMode={args.local ? "local" : "remote"}
1304
1471
  jsxFactory={config.jsx_factory}
1305
1472
  jsxFragment={config.jsx_fragment}
1473
+ tsconfig={config.tsconfig}
1306
1474
  upstreamProtocol={config.dev.upstream_protocol}
1307
1475
  localProtocol={config.dev.local_protocol}
1308
1476
  enableLocalPersistence={false}
@@ -1311,14 +1479,8 @@ export async function main(argv: string[]): Promise<void> {
1311
1479
  port={config.dev?.port}
1312
1480
  ip={config.dev.ip}
1313
1481
  public={undefined}
1314
- compatibilityDate={
1315
- config.compatibility_date ||
1316
- new Date().toISOString().substring(0, 10)
1317
- }
1318
- compatibilityFlags={
1319
- (args["compatibility-flags"] as string[]) ||
1320
- config.compatibility_flags
1321
- }
1482
+ compatibilityDate={getDevCompatibilityDate(config)}
1483
+ compatibilityFlags={config.compatibility_flags}
1322
1484
  usageModel={config.usage_model}
1323
1485
  bindings={{
1324
1486
  kv_namespaces: config.kv_namespaces?.map(
@@ -1345,10 +1507,26 @@ export async function main(argv: string[]): Promise<void> {
1345
1507
  vars: config.vars,
1346
1508
  wasm_modules: config.wasm_modules,
1347
1509
  text_blobs: config.text_blobs,
1510
+ data_blobs: config.data_blobs,
1348
1511
  durable_objects: config.durable_objects,
1349
- r2_buckets: config.r2_buckets,
1512
+ r2_buckets: config.r2_buckets?.map(
1513
+ ({ binding, preview_bucket_name, bucket_name: _bucket_name }) => {
1514
+ // same idea as kv namespace preview id,
1515
+ // same copy-on-write TODO
1516
+ if (!preview_bucket_name) {
1517
+ throw new Error(
1518
+ `In development, you should use a separate r2 bucket than the one you'd use in production. Please create a new r2 bucket with "wrangler r2 bucket create <name>" and add its name as preview_bucket_name to the r2_buckets "${binding}" in your wrangler.toml`
1519
+ );
1520
+ }
1521
+ return {
1522
+ binding,
1523
+ bucket_name: preview_bucket_name,
1524
+ };
1525
+ }
1526
+ ),
1350
1527
  unsafe: config.unsafe?.bindings,
1351
1528
  }}
1529
+ crons={config.triggers.crons}
1352
1530
  inspectorPort={await getPort({ port: 9229 })}
1353
1531
  />
1354
1532
  );
@@ -1370,10 +1548,12 @@ export async function main(argv: string[]): Promise<void> {
1370
1548
  return yargs
1371
1549
  .option("env", {
1372
1550
  type: "string",
1551
+ requiresArg: true,
1373
1552
  describe: "Perform on a specific environment",
1374
1553
  })
1375
1554
  .option("zone", {
1376
1555
  type: "string",
1556
+ requiresArg: true,
1377
1557
  describe: "Zone id",
1378
1558
  })
1379
1559
  .positional("zone", {
@@ -1395,7 +1575,7 @@ export async function main(argv: string[]): Promise<void> {
1395
1575
  }
1396
1576
  )
1397
1577
  .command(
1398
- "delete",
1578
+ "delete [id]",
1399
1579
  "Delete a route associated with a zone",
1400
1580
  (yargs) => {
1401
1581
  return yargs
@@ -1405,10 +1585,12 @@ export async function main(argv: string[]): Promise<void> {
1405
1585
  })
1406
1586
  .option("zone", {
1407
1587
  type: "string",
1588
+ requiresArg: true,
1408
1589
  describe: "zone id",
1409
1590
  })
1410
1591
  .option("env", {
1411
1592
  type: "string",
1593
+ requiresArg: true,
1412
1594
  describe: "Perform on a specific environment",
1413
1595
  });
1414
1596
  },
@@ -1465,39 +1647,41 @@ export async function main(argv: string[]): Promise<void> {
1465
1647
  .option("name", {
1466
1648
  describe: "Name of the worker",
1467
1649
  type: "string",
1650
+ requiresArg: true,
1468
1651
  })
1469
1652
  .option("env", {
1470
1653
  type: "string",
1654
+ requiresArg: true,
1471
1655
  describe:
1472
1656
  "Binds the secret to the Worker of the specific environment",
1473
1657
  alias: "e",
1474
1658
  });
1475
1659
  },
1476
1660
  async (args) => {
1477
- printWranglerBanner();
1478
- const config = readConfig(args.config as ConfigPath, args.env);
1661
+ await printWranglerBanner();
1662
+ const config = readConfig(args.config as ConfigPath, args);
1479
1663
 
1480
- const scriptName = getScriptName(args, config);
1664
+ const scriptName = getLegacyScriptName(args, config);
1481
1665
  if (!scriptName) {
1482
1666
  throw new Error("Missing script name");
1483
1667
  }
1484
1668
 
1485
- const isInteractive = process.stdin.isTTY;
1486
- const accountId = await requireAuth(config, isInteractive);
1669
+ const accountId = await requireAuth(config);
1487
1670
 
1671
+ const isInteractive = process.stdin.isTTY;
1488
1672
  const secretValue = isInteractive
1489
1673
  ? await prompt("Enter a secret value:", "password")
1490
1674
  : await readFromStdin();
1491
1675
 
1492
- console.log(
1676
+ logger.log(
1493
1677
  `🌀 Creating the secret for script ${scriptName} ${
1494
- args.env && !isLegacyEnv(args, config) ? `(${args.env})` : ""
1678
+ args.env && !isLegacyEnv(config) ? `(${args.env})` : ""
1495
1679
  }`
1496
1680
  );
1497
1681
 
1498
1682
  async function submitSecret() {
1499
1683
  const url =
1500
- !args.env || isLegacyEnv(args, config)
1684
+ !args.env || isLegacyEnv(config)
1501
1685
  ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
1502
1686
  : `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1503
1687
 
@@ -1515,7 +1699,7 @@ export async function main(argv: string[]): Promise<void> {
1515
1699
  const createDraftWorker = async () => {
1516
1700
  // TODO: log a warning
1517
1701
  await fetchResult(
1518
- !args["legacy-env"] && args.env
1702
+ !isLegacyEnv(config) && args.env
1519
1703
  ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}`
1520
1704
  : `/accounts/${accountId}/workers/scripts/${scriptName}`,
1521
1705
  {
@@ -1534,6 +1718,7 @@ export async function main(argv: string[]): Promise<void> {
1534
1718
  r2_buckets: [],
1535
1719
  wasm_modules: {},
1536
1720
  text_blobs: {},
1721
+ data_blobs: {},
1537
1722
  unsafe: [],
1538
1723
  },
1539
1724
  modules: [],
@@ -1550,7 +1735,7 @@ export async function main(argv: string[]): Promise<void> {
1550
1735
  return (
1551
1736
  typeof e === "object" &&
1552
1737
  e !== null &&
1553
- (e as { code: 10007 }).code === 10007
1738
+ (e as { code: number }).code === 10007
1554
1739
  );
1555
1740
  }
1556
1741
 
@@ -1567,14 +1752,14 @@ export async function main(argv: string[]): Promise<void> {
1567
1752
  }
1568
1753
  }
1569
1754
 
1570
- console.log(`✨ Success! Uploaded secret ${args.key}`);
1755
+ logger.log(`✨ Success! Uploaded secret ${args.key}`);
1571
1756
  }
1572
1757
  )
1573
1758
  .command(
1574
1759
  "delete <key>",
1575
1760
  "Delete a secret variable from a script",
1576
- (yargs) => {
1577
- printWranglerBanner();
1761
+ async (yargs) => {
1762
+ await printWranglerBanner();
1578
1763
  return yargs
1579
1764
  .positional("key", {
1580
1765
  describe: "The variable name to be accessible in the script",
@@ -1583,18 +1768,20 @@ export async function main(argv: string[]): Promise<void> {
1583
1768
  .option("name", {
1584
1769
  describe: "Name of the worker",
1585
1770
  type: "string",
1771
+ requiresArg: true,
1586
1772
  })
1587
1773
  .option("env", {
1588
1774
  type: "string",
1775
+ requiresArg: true,
1589
1776
  describe:
1590
1777
  "Binds the secret to the Worker of the specific environment",
1591
1778
  alias: "e",
1592
1779
  });
1593
1780
  },
1594
1781
  async (args) => {
1595
- const config = readConfig(args.config as ConfigPath, args.env);
1782
+ const config = readConfig(args.config as ConfigPath, args);
1596
1783
 
1597
- const scriptName = getScriptName(args, config);
1784
+ const scriptName = getLegacyScriptName(args, config);
1598
1785
  if (!scriptName) {
1599
1786
  throw new Error("Missing script name");
1600
1787
  }
@@ -1606,23 +1793,23 @@ export async function main(argv: string[]): Promise<void> {
1606
1793
  `Are you sure you want to permanently delete the variable ${
1607
1794
  args.key
1608
1795
  } on the script ${scriptName}${
1609
- args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1796
+ args.env && !isLegacyEnv(config) ? ` (${args.env})` : ""
1610
1797
  }?`
1611
1798
  )
1612
1799
  ) {
1613
- console.log(
1800
+ logger.log(
1614
1801
  `🌀 Deleting the secret ${args.key} on script ${scriptName}${
1615
- args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1802
+ args.env && !isLegacyEnv(config) ? ` (${args.env})` : ""
1616
1803
  }`
1617
1804
  );
1618
1805
 
1619
1806
  const url =
1620
- !args.env || isLegacyEnv(args, config)
1807
+ !args.env || isLegacyEnv(config)
1621
1808
  ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
1622
1809
  : `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1623
1810
 
1624
1811
  await fetchResult(`${url}/${args.key}`, { method: "DELETE" });
1625
- console.log(`✨ Success! Deleted secret ${args.key}`);
1812
+ logger.log(`✨ Success! Deleted secret ${args.key}`);
1626
1813
  }
1627
1814
  }
1628
1815
  )
@@ -1634,18 +1821,20 @@ export async function main(argv: string[]): Promise<void> {
1634
1821
  .option("name", {
1635
1822
  describe: "Name of the worker",
1636
1823
  type: "string",
1824
+ requiresArg: true,
1637
1825
  })
1638
1826
  .option("env", {
1639
1827
  type: "string",
1828
+ requiresArg: true,
1640
1829
  describe:
1641
1830
  "Binds the secret to the Worker of the specific environment.",
1642
1831
  alias: "e",
1643
1832
  });
1644
1833
  },
1645
1834
  async (args) => {
1646
- const config = readConfig(args.config as ConfigPath, args.env);
1835
+ const config = readConfig(args.config as ConfigPath, args);
1647
1836
 
1648
- const scriptName = getScriptName(args, config);
1837
+ const scriptName = getLegacyScriptName(args, config);
1649
1838
  if (!scriptName) {
1650
1839
  throw new Error("Missing script name");
1651
1840
  }
@@ -1653,11 +1842,11 @@ export async function main(argv: string[]): Promise<void> {
1653
1842
  const accountId = await requireAuth(config);
1654
1843
 
1655
1844
  const url =
1656
- !args.env || isLegacyEnv(args, config)
1845
+ !args.env || isLegacyEnv(config)
1657
1846
  ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
1658
1847
  : `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1659
1848
 
1660
- console.log(JSON.stringify(await fetchResult(url), null, " "));
1849
+ logger.log(JSON.stringify(await fetchResult(url), null, " "));
1661
1850
  }
1662
1851
  );
1663
1852
  }
@@ -1683,6 +1872,7 @@ export async function main(argv: string[]): Promise<void> {
1683
1872
  })
1684
1873
  .option("env", {
1685
1874
  type: "string",
1875
+ requiresArg: true,
1686
1876
  describe: "Perform on a specific environment",
1687
1877
  alias: "e",
1688
1878
  })
@@ -1692,13 +1882,7 @@ export async function main(argv: string[]): Promise<void> {
1692
1882
  });
1693
1883
  },
1694
1884
  async (args) => {
1695
- printWranglerBanner();
1696
- if (args._.length > 2) {
1697
- const extraArgs = args._.slice(2).join(" ");
1698
- throw new CommandLineArgsError(
1699
- `Unexpected additional positional arguments "${extraArgs}".`
1700
- );
1701
- }
1885
+ await printWranglerBanner();
1702
1886
 
1703
1887
  if (!isValidNamespaceBinding(args.namespace)) {
1704
1888
  throw new CommandLineArgsError(
@@ -1706,9 +1890,9 @@ export async function main(argv: string[]): Promise<void> {
1706
1890
  );
1707
1891
  }
1708
1892
 
1709
- const config = readConfig(args.config as ConfigPath, args.env);
1893
+ const config = readConfig(args.config as ConfigPath, args);
1710
1894
  if (!config.name) {
1711
- console.warn(
1895
+ logger.warn(
1712
1896
  "No configured name present, using `worker` as a prefix for the title"
1713
1897
  );
1714
1898
  }
@@ -1722,16 +1906,16 @@ export async function main(argv: string[]): Promise<void> {
1722
1906
 
1723
1907
  // TODO: generate a binding name stripping non alphanumeric chars
1724
1908
 
1725
- console.log(`🌀 Creating namespace with title "${title}"`);
1909
+ logger.log(`🌀 Creating namespace with title "${title}"`);
1726
1910
  const namespaceId = await createNamespace(accountId, title);
1727
1911
 
1728
- console.log("✨ Success!");
1912
+ logger.log("✨ Success!");
1729
1913
  const envString = args.env ? ` under [env.${args.env}]` : "";
1730
1914
  const previewString = args.preview ? "preview_" : "";
1731
- console.log(
1915
+ logger.log(
1732
1916
  `Add the following to your configuration file in your kv_namespaces array${envString}:`
1733
1917
  );
1734
- console.log(
1918
+ logger.log(
1735
1919
  `{ binding = "${args.namespace}", ${previewString}id = "${namespaceId}" }`
1736
1920
  );
1737
1921
 
@@ -1743,13 +1927,13 @@ export async function main(argv: string[]): Promise<void> {
1743
1927
  "Outputs a list of all KV namespaces associated with your account id.",
1744
1928
  {},
1745
1929
  async (args) => {
1746
- const config = readConfig(args.config as ConfigPath, undefined);
1930
+ const config = readConfig(args.config as ConfigPath, args);
1747
1931
 
1748
1932
  const accountId = await requireAuth(config);
1749
1933
 
1750
1934
  // TODO: we should show bindings if they exist for given ids
1751
1935
 
1752
- console.log(
1936
+ logger.log(
1753
1937
  JSON.stringify(await listNamespaces(accountId), null, " ")
1754
1938
  );
1755
1939
  }
@@ -1761,15 +1945,18 @@ export async function main(argv: string[]): Promise<void> {
1761
1945
  return yargs
1762
1946
  .option("binding", {
1763
1947
  type: "string",
1948
+ requiresArg: true,
1764
1949
  describe: "The name of the namespace to delete",
1765
1950
  })
1766
1951
  .option("namespace-id", {
1767
1952
  type: "string",
1953
+ requiresArg: true,
1768
1954
  describe: "The id of the namespace to delete",
1769
1955
  })
1770
1956
  .check(demandOneOfOption("binding", "namespace-id"))
1771
1957
  .option("env", {
1772
1958
  type: "string",
1959
+ requiresArg: true,
1773
1960
  describe: "Perform on a specific environment",
1774
1961
  alias: "e",
1775
1962
  })
@@ -1779,8 +1966,8 @@ export async function main(argv: string[]): Promise<void> {
1779
1966
  });
1780
1967
  },
1781
1968
  async (args) => {
1782
- printWranglerBanner();
1783
- const config = readConfig(args.config as ConfigPath, args.env);
1969
+ await printWranglerBanner();
1970
+ const config = readConfig(args.config as ConfigPath, args);
1784
1971
 
1785
1972
  let id;
1786
1973
  try {
@@ -1843,15 +2030,18 @@ export async function main(argv: string[]): Promise<void> {
1843
2030
  })
1844
2031
  .option("binding", {
1845
2032
  type: "string",
2033
+ requiresArg: true,
1846
2034
  describe: "The binding of the namespace to write to",
1847
2035
  })
1848
2036
  .option("namespace-id", {
1849
2037
  type: "string",
2038
+ requiresArg: true,
1850
2039
  describe: "The id of the namespace to write to",
1851
2040
  })
1852
2041
  .check(demandOneOfOption("binding", "namespace-id"))
1853
2042
  .option("env", {
1854
2043
  type: "string",
2044
+ requiresArg: true,
1855
2045
  describe: "Perform on a specific environment",
1856
2046
  alias: "e",
1857
2047
  })
@@ -1870,13 +2060,14 @@ export async function main(argv: string[]): Promise<void> {
1870
2060
  })
1871
2061
  .option("path", {
1872
2062
  type: "string",
2063
+ requiresArg: true,
1873
2064
  describe: "Read value from the file at a given path",
1874
2065
  })
1875
2066
  .check(demandOneOfOption("value", "path"));
1876
2067
  },
1877
2068
  async ({ key, ttl, expiration, ...args }) => {
1878
- printWranglerBanner();
1879
- const config = readConfig(args.config as ConfigPath, args.env);
2069
+ await printWranglerBanner();
2070
+ const config = readConfig(args.config as ConfigPath, args);
1880
2071
  const namespaceId = getNamespaceId(args, config);
1881
2072
  // One of `args.path` and `args.value` must be defined
1882
2073
  const value = args.path
@@ -1885,12 +2076,12 @@ export async function main(argv: string[]): Promise<void> {
1885
2076
  args.value!;
1886
2077
 
1887
2078
  if (args.path) {
1888
- console.log(
1889
- `writing the contents of ${args.path} to the key "${key}" on namespace ${namespaceId}`
2079
+ logger.log(
2080
+ `Writing the contents of ${args.path} to the key "${key}" on namespace ${namespaceId}.`
1890
2081
  );
1891
2082
  } else {
1892
- console.log(
1893
- `writing the value "${value}" to key "${key}" on namespace ${namespaceId}`
2083
+ logger.log(
2084
+ `Writing the value "${value}" to key "${key}" on namespace ${namespaceId}.`
1894
2085
  );
1895
2086
  }
1896
2087
 
@@ -1911,15 +2102,18 @@ export async function main(argv: string[]): Promise<void> {
1911
2102
  return yargs
1912
2103
  .option("binding", {
1913
2104
  type: "string",
2105
+ requiresArg: true,
1914
2106
  describe: "The name of the namespace to list",
1915
2107
  })
1916
2108
  .option("namespace-id", {
1917
2109
  type: "string",
2110
+ requiresArg: true,
1918
2111
  describe: "The id of the namespace to list",
1919
2112
  })
1920
2113
  .check(demandOneOfOption("binding", "namespace-id"))
1921
2114
  .option("env", {
1922
2115
  type: "string",
2116
+ requiresArg: true,
1923
2117
  describe: "Perform on a specific environment",
1924
2118
  alias: "e",
1925
2119
  })
@@ -1931,12 +2125,13 @@ export async function main(argv: string[]): Promise<void> {
1931
2125
  })
1932
2126
  .option("prefix", {
1933
2127
  type: "string",
2128
+ requiresArg: true,
1934
2129
  describe: "A prefix to filter listed keys",
1935
2130
  });
1936
2131
  },
1937
2132
  async ({ prefix, ...args }) => {
1938
2133
  // TODO: support for limit+cursor (pagination)
1939
- const config = readConfig(args.config as ConfigPath, args.env);
2134
+ const config = readConfig(args.config as ConfigPath, args);
1940
2135
  const namespaceId = getNamespaceId(args, config);
1941
2136
 
1942
2137
  const accountId = await requireAuth(config);
@@ -1946,7 +2141,7 @@ export async function main(argv: string[]): Promise<void> {
1946
2141
  namespaceId,
1947
2142
  prefix
1948
2143
  );
1949
- console.log(JSON.stringify(results, undefined, 2));
2144
+ logger.log(JSON.stringify(results, undefined, 2));
1950
2145
  }
1951
2146
  )
1952
2147
  .command(
@@ -1961,15 +2156,18 @@ export async function main(argv: string[]): Promise<void> {
1961
2156
  })
1962
2157
  .option("binding", {
1963
2158
  type: "string",
2159
+ requiresArg: true,
1964
2160
  describe: "The name of the namespace to get from",
1965
2161
  })
1966
2162
  .option("namespace-id", {
1967
2163
  type: "string",
2164
+ requiresArg: true,
1968
2165
  describe: "The id of the namespace to get from",
1969
2166
  })
1970
2167
  .check(demandOneOfOption("binding", "namespace-id"))
1971
2168
  .option("env", {
1972
2169
  type: "string",
2170
+ requiresArg: true,
1973
2171
  describe: "Perform on a specific environment",
1974
2172
  alias: "e",
1975
2173
  })
@@ -1985,12 +2183,12 @@ export async function main(argv: string[]): Promise<void> {
1985
2183
  });
1986
2184
  },
1987
2185
  async ({ key, ...args }) => {
1988
- const config = readConfig(args.config as ConfigPath, args.env);
2186
+ const config = readConfig(args.config as ConfigPath, args);
1989
2187
  const namespaceId = getNamespaceId(args, config);
1990
2188
 
1991
2189
  const accountId = await requireAuth(config);
1992
2190
 
1993
- console.log(await getKeyValue(accountId, namespaceId, key));
2191
+ logger.log(await getKeyValue(accountId, namespaceId, key));
1994
2192
  }
1995
2193
  )
1996
2194
  .command(
@@ -2005,15 +2203,18 @@ export async function main(argv: string[]): Promise<void> {
2005
2203
  })
2006
2204
  .option("binding", {
2007
2205
  type: "string",
2206
+ requiresArg: true,
2008
2207
  describe: "The name of the namespace to delete from",
2009
2208
  })
2010
2209
  .option("namespace-id", {
2011
2210
  type: "string",
2211
+ requiresArg: true,
2012
2212
  describe: "The id of the namespace to delete from",
2013
2213
  })
2014
2214
  .check(demandOneOfOption("binding", "namespace-id"))
2015
2215
  .option("env", {
2016
2216
  type: "string",
2217
+ requiresArg: true,
2017
2218
  describe: "Perform on a specific environment",
2018
2219
  alias: "e",
2019
2220
  })
@@ -2023,12 +2224,12 @@ export async function main(argv: string[]): Promise<void> {
2023
2224
  });
2024
2225
  },
2025
2226
  async ({ key, ...args }) => {
2026
- printWranglerBanner();
2027
- const config = readConfig(args.config as ConfigPath, args.env);
2227
+ await printWranglerBanner();
2228
+ const config = readConfig(args.config as ConfigPath, args);
2028
2229
  const namespaceId = getNamespaceId(args, config);
2029
2230
 
2030
- console.log(
2031
- `deleting the key "${key}" on namespace ${namespaceId}`
2231
+ logger.log(
2232
+ `Deleting the key "${key}" on namespace ${namespaceId}.`
2032
2233
  );
2033
2234
 
2034
2235
  const accountId = await requireAuth(config);
@@ -2061,15 +2262,18 @@ export async function main(argv: string[]): Promise<void> {
2061
2262
  })
2062
2263
  .option("binding", {
2063
2264
  type: "string",
2265
+ requiresArg: true,
2064
2266
  describe: "The name of the namespace to insert values into",
2065
2267
  })
2066
2268
  .option("namespace-id", {
2067
2269
  type: "string",
2270
+ requiresArg: true,
2068
2271
  describe: "The id of the namespace to insert values into",
2069
2272
  })
2070
2273
  .check(demandOneOfOption("binding", "namespace-id"))
2071
2274
  .option("env", {
2072
2275
  type: "string",
2276
+ requiresArg: true,
2073
2277
  describe: "Perform on a specific environment",
2074
2278
  alias: "e",
2075
2279
  })
@@ -2079,12 +2283,12 @@ export async function main(argv: string[]): Promise<void> {
2079
2283
  });
2080
2284
  },
2081
2285
  async ({ filename, ...args }) => {
2082
- printWranglerBanner();
2286
+ await printWranglerBanner();
2083
2287
  // The simplest implementation I could think of.
2084
2288
  // This could be made more efficient with a streaming parser/uploader
2085
2289
  // but we'll do that in the future if needed.
2086
2290
 
2087
- const config = readConfig(args.config as ConfigPath, args.env);
2291
+ const config = readConfig(args.config as ConfigPath, args);
2088
2292
  const namespaceId = getNamespaceId(args, config);
2089
2293
  const content = parseJSON(readFileSync(filename), filename);
2090
2294
 
@@ -2121,7 +2325,7 @@ export async function main(argv: string[]): Promise<void> {
2121
2325
  }
2122
2326
  }
2123
2327
  if (warnings.length > 0) {
2124
- console.warn(
2328
+ logger.warn(
2125
2329
  `Unexpected key-value properties in "${filename}".\n` +
2126
2330
  warnings.join("\n")
2127
2331
  );
@@ -2148,11 +2352,11 @@ export async function main(argv: string[]): Promise<void> {
2148
2352
  namespaceId,
2149
2353
  content,
2150
2354
  (index, total) => {
2151
- console.log(`Uploaded ${index} of ${total}.`);
2355
+ logger.log(`Uploaded ${index} of ${total}.`);
2152
2356
  }
2153
2357
  );
2154
2358
 
2155
- console.log("Success!");
2359
+ logger.log("Success!");
2156
2360
  }
2157
2361
  )
2158
2362
  .command(
@@ -2167,15 +2371,18 @@ export async function main(argv: string[]): Promise<void> {
2167
2371
  })
2168
2372
  .option("binding", {
2169
2373
  type: "string",
2374
+ requiresArg: true,
2170
2375
  describe: "The name of the namespace to delete from",
2171
2376
  })
2172
2377
  .option("namespace-id", {
2173
2378
  type: "string",
2379
+ requiresArg: true,
2174
2380
  describe: "The id of the namespace to delete from",
2175
2381
  })
2176
2382
  .check(demandOneOfOption("binding", "namespace-id"))
2177
2383
  .option("env", {
2178
2384
  type: "string",
2385
+ requiresArg: true,
2179
2386
  describe: "Perform on a specific environment",
2180
2387
  alias: "e",
2181
2388
  })
@@ -2190,8 +2397,8 @@ export async function main(argv: string[]): Promise<void> {
2190
2397
  });
2191
2398
  },
2192
2399
  async ({ filename, ...args }) => {
2193
- printWranglerBanner();
2194
- const config = readConfig(args.config as ConfigPath, args.env);
2400
+ await printWranglerBanner();
2401
+ const config = readConfig(args.config as ConfigPath, args);
2195
2402
  const namespaceId = getNamespaceId(args, config);
2196
2403
 
2197
2404
  if (!args.force) {
@@ -2199,7 +2406,7 @@ export async function main(argv: string[]): Promise<void> {
2199
2406
  `Are you sure you want to delete all the keys read from "${filename}" from kv-namespace with id "${namespaceId}"?`
2200
2407
  );
2201
2408
  if (!result) {
2202
- console.log(`Not deleting keys read from "${filename}".`);
2409
+ logger.log(`Not deleting keys read from "${filename}".`);
2203
2410
  return;
2204
2411
  }
2205
2412
  }
@@ -2242,18 +2449,22 @@ export async function main(argv: string[]): Promise<void> {
2242
2449
  namespaceId,
2243
2450
  content,
2244
2451
  (index, total) => {
2245
- console.log(`Deleted ${index} of ${total}.`);
2452
+ logger.log(`Deleted ${index} of ${total}.`);
2246
2453
  }
2247
2454
  );
2248
2455
 
2249
- console.log("Success!");
2456
+ logger.log("Success!");
2250
2457
  }
2251
2458
  );
2252
2459
  }
2253
2460
  );
2254
2461
 
2255
- wrangler.command("pages", "⚡️ Configure Cloudflare Pages", (pagesYargs) =>
2256
- pages(pagesYargs.command(subHelp))
2462
+ wrangler.command(
2463
+ "pages",
2464
+ "⚡️ Configure Cloudflare Pages",
2465
+ async (pagesYargs) => {
2466
+ await pages(pagesYargs.command(subHelp).epilogue(pagesBetaWarning));
2467
+ }
2257
2468
  );
2258
2469
 
2259
2470
  wrangler.command("r2", "📦 Interact with an R2 store", (r2Yargs) => {
@@ -2271,31 +2482,24 @@ export async function main(argv: string[]): Promise<void> {
2271
2482
  });
2272
2483
  },
2273
2484
  async (args) => {
2274
- printWranglerBanner();
2275
- // We expect three values in `_`: `r2`, `bucket`, `create`.
2276
- if (args._.length > 3) {
2277
- const extraArgs = args._.slice(3).join(" ");
2278
- throw new CommandLineArgsError(
2279
- `Unexpected additional positional arguments "${extraArgs}".`
2280
- );
2281
- }
2485
+ await printWranglerBanner();
2282
2486
 
2283
- const config = readConfig(args.config as ConfigPath, undefined);
2487
+ const config = readConfig(args.config as ConfigPath, args);
2284
2488
 
2285
2489
  const accountId = await requireAuth(config);
2286
2490
 
2287
- console.log(`Creating bucket ${args.name}.`);
2491
+ logger.log(`Creating bucket ${args.name}.`);
2288
2492
  await createR2Bucket(accountId, args.name);
2289
- console.log(`Created bucket ${args.name}.`);
2493
+ logger.log(`Created bucket ${args.name}.`);
2290
2494
  }
2291
2495
  );
2292
2496
 
2293
2497
  r2BucketYargs.command("list", "List R2 buckets", {}, async (args) => {
2294
- const config = readConfig(args.config as ConfigPath, undefined);
2498
+ const config = readConfig(args.config as ConfigPath, args);
2295
2499
 
2296
2500
  const accountId = await requireAuth(config);
2297
2501
 
2298
- console.log(JSON.stringify(await listR2Buckets(accountId), null, 2));
2502
+ logger.log(JSON.stringify(await listR2Buckets(accountId), null, 2));
2299
2503
  });
2300
2504
 
2301
2505
  r2BucketYargs.command(
@@ -2309,28 +2513,97 @@ export async function main(argv: string[]): Promise<void> {
2309
2513
  });
2310
2514
  },
2311
2515
  async (args) => {
2312
- printWranglerBanner();
2313
- // We expect three values in `_`: `r2`, `bucket`, `delete`.
2314
- if (args._.length > 3) {
2315
- const extraArgs = args._.slice(3).join(" ");
2316
- throw new CommandLineArgsError(
2317
- `Unexpected additional positional arguments "${extraArgs}".`
2318
- );
2319
- }
2516
+ await printWranglerBanner();
2320
2517
 
2321
- const config = readConfig(args.config as ConfigPath, undefined);
2518
+ const config = readConfig(args.config as ConfigPath, args);
2322
2519
 
2323
2520
  const accountId = await requireAuth(config);
2324
2521
 
2325
- console.log(`Deleting bucket ${args.name}.`);
2522
+ logger.log(`Deleting bucket ${args.name}.`);
2326
2523
  await deleteR2Bucket(accountId, args.name);
2327
- console.log(`Deleted bucket ${args.name}.`);
2524
+ logger.log(`Deleted bucket ${args.name}.`);
2328
2525
  }
2329
2526
  );
2330
2527
  return r2BucketYargs;
2331
2528
  });
2332
2529
  });
2333
2530
 
2531
+ /**
2532
+ * User Group: login, logout, and whoami
2533
+ * TODO: group commands into User group similar to .group() for flags in yargs
2534
+ */
2535
+ // login
2536
+ wrangler.command(
2537
+ // this needs scopes as an option?
2538
+ "login",
2539
+ "🔓 Login to Cloudflare",
2540
+ (yargs) => {
2541
+ // TODO: This needs some copy editing
2542
+ // I mean, this entire app does, but this too.
2543
+ return yargs
2544
+ .option("scopes-list", {
2545
+ describe: "List all the available OAuth scopes with descriptions",
2546
+ })
2547
+ .option("scopes", {
2548
+ describe: "Pick the set of applicable OAuth scopes when logging in",
2549
+ array: true,
2550
+ type: "string",
2551
+ requiresArg: true,
2552
+ });
2553
+
2554
+ // TODO: scopes
2555
+ },
2556
+ async (args) => {
2557
+ await printWranglerBanner();
2558
+ if (args["scopes-list"]) {
2559
+ listScopes();
2560
+ return;
2561
+ }
2562
+ if (args.scopes) {
2563
+ if (args.scopes.length === 0) {
2564
+ // don't allow no scopes to be passed, that would be weird
2565
+ listScopes();
2566
+ return;
2567
+ }
2568
+ if (!validateScopeKeys(args.scopes)) {
2569
+ throw new CommandLineArgsError(
2570
+ `One of ${args.scopes} is not a valid authentication scope. Run "wrangler login --list-scopes" to see the valid scopes.`
2571
+ );
2572
+ }
2573
+ await login({ scopes: args.scopes });
2574
+ return;
2575
+ }
2576
+ await login();
2577
+
2578
+ // TODO: would be nice if it optionally saved login
2579
+ // credentials inside node_modules/.cache or something
2580
+ // this way you could have multiple users on a single machine
2581
+ }
2582
+ );
2583
+
2584
+ // logout
2585
+ wrangler.command(
2586
+ // this needs scopes as an option?
2587
+ "logout",
2588
+ "🚪 Logout from Cloudflare",
2589
+ () => {},
2590
+ async () => {
2591
+ await printWranglerBanner();
2592
+ await logout();
2593
+ }
2594
+ );
2595
+
2596
+ // whoami
2597
+ wrangler.command(
2598
+ "whoami",
2599
+ "🕵️ Retrieve your user info and test your auth config",
2600
+ () => {},
2601
+ async () => {
2602
+ await printWranglerBanner();
2603
+ await whoami();
2604
+ }
2605
+ );
2606
+
2334
2607
  wrangler
2335
2608
  .option("legacy-env", {
2336
2609
  type: "boolean",
@@ -2340,6 +2613,7 @@ export async function main(argv: string[]): Promise<void> {
2340
2613
  alias: "c",
2341
2614
  describe: "Path to .toml configuration file",
2342
2615
  type: "string",
2616
+ requiresArg: true,
2343
2617
  });
2344
2618
 
2345
2619
  wrangler.group(["config", "help", "version", "legacy-env"], "Flags:");
@@ -2350,20 +2624,19 @@ export async function main(argv: string[]): Promise<void> {
2350
2624
  try {
2351
2625
  await wrangler.parse();
2352
2626
  } catch (e) {
2627
+ logger.log(""); // Just adds a bit of space
2353
2628
  if (e instanceof CommandLineArgsError) {
2354
2629
  wrangler.showHelp("error");
2355
- console.error(""); // Just adds a bit of space
2356
- console.error(e.message);
2630
+ logger.log(""); // Add a bit of space.
2631
+ logger.error(e.message);
2357
2632
  } else if (e instanceof ParseError) {
2358
- console.error("");
2359
2633
  e.notes.push({
2360
2634
  text: "\nIf you think this is a bug, please open an issue at: https://github.com/cloudflare/wrangler2/issues/new",
2361
2635
  });
2362
- console.error(formatMessage(e));
2636
+ logger.error(formatMessage(e));
2363
2637
  } else {
2364
- console.error(e instanceof Error ? e.message : e);
2365
- console.error(""); // Just adds a bit of space
2366
- console.error(
2638
+ logger.error(e instanceof Error ? e.message : e);
2639
+ logger.log(
2367
2640
  `${fgGreenColor}%s${resetColor}`,
2368
2641
  "If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
2369
2642
  );
@@ -2371,3 +2644,25 @@ export async function main(argv: string[]): Promise<void> {
2371
2644
  throw e;
2372
2645
  }
2373
2646
  }
2647
+
2648
+ function getDevCompatibilityDate(
2649
+ config: Config,
2650
+ compatibilityDate = config.compatibility_date
2651
+ ) {
2652
+ const currentDate = new Date().toISOString().substring(0, 10);
2653
+ if (config.configPath !== undefined && compatibilityDate === undefined) {
2654
+ logger.warn(
2655
+ `No compatibility_date was specified. Using today's date: ${currentDate}.\n` +
2656
+ "Add one to your wrangler.toml file:\n" +
2657
+ "```\n" +
2658
+ `compatibility_date = "${currentDate}"\n` +
2659
+ "```\n" +
2660
+ "or pass it in your terminal:\n" +
2661
+ "```\n" +
2662
+ `--compatibility-date=${currentDate}\n` +
2663
+ "```\n" +
2664
+ "See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information."
2665
+ );
2666
+ }
2667
+ return compatibilityDate ?? currentDate;
2668
+ }