wrangler 0.0.0-e95bea2 → 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 (93) hide show
  1. package/README.md +25 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4886 -0
  4. package/package.json +33 -25
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +136 -25
  8. package/pages/functions/filepath-routing.ts +14 -3
  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 +1179 -271
  14. package/src/__tests__/dev.test.tsx +382 -25
  15. package/src/__tests__/guess-worker-format.test.ts +28 -1
  16. package/src/__tests__/helpers/mock-account-id.ts +4 -0
  17. package/src/__tests__/helpers/mock-cfetch.ts +5 -1
  18. package/src/__tests__/helpers/mock-console.ts +20 -3
  19. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  20. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  21. package/src/__tests__/helpers/mock-istty.ts +27 -0
  22. package/src/__tests__/helpers/mock-kv.ts +9 -16
  23. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  24. package/src/__tests__/helpers/run-in-tmp.ts +10 -0
  25. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  26. package/src/__tests__/https-options.test.ts +5 -2
  27. package/src/__tests__/index.test.ts +27 -769
  28. package/src/__tests__/init.test.ts +1516 -0
  29. package/src/__tests__/jest.setup.ts +4 -4
  30. package/src/__tests__/kv.test.ts +172 -126
  31. package/src/__tests__/logger.test.ts +115 -0
  32. package/src/__tests__/pages.test.ts +311 -26
  33. package/src/__tests__/publish.test.ts +2946 -524
  34. package/src/__tests__/r2.test.ts +26 -10
  35. package/src/__tests__/route.test.ts +4 -4
  36. package/src/__tests__/secret.test.ts +55 -34
  37. package/src/__tests__/tail.test.ts +146 -20
  38. package/src/__tests__/user.test.ts +131 -0
  39. package/src/__tests__/whoami.test.tsx +26 -7
  40. package/src/bundle.ts +30 -3
  41. package/src/cfetch/internal.ts +9 -7
  42. package/src/cli.ts +5 -11
  43. package/src/config/README.md +7 -7
  44. package/src/config/config.ts +33 -78
  45. package/src/config/environment.ts +93 -4
  46. package/src/config/index.ts +15 -4
  47. package/src/config/validation-helpers.ts +202 -15
  48. package/src/config/validation.ts +530 -133
  49. package/src/config-cache.ts +45 -0
  50. package/src/create-worker-preview.ts +22 -13
  51. package/src/create-worker-upload-form.ts +32 -9
  52. package/src/dev/dev-vars.ts +36 -0
  53. package/src/dev/dev.tsx +136 -75
  54. package/src/dev/local.tsx +196 -125
  55. package/src/dev/remote.tsx +25 -13
  56. package/src/dev/use-esbuild.ts +23 -5
  57. package/src/dialogs.tsx +16 -6
  58. package/src/entry.ts +119 -11
  59. package/src/environment-variables.ts +3 -1
  60. package/src/errors.ts +11 -0
  61. package/src/https-options.ts +3 -2
  62. package/src/index.tsx +739 -400
  63. package/src/inspect.ts +17 -11
  64. package/src/intl-polyfill.d.ts +139 -0
  65. package/src/{kv/index.tsx → kv.ts} +8 -28
  66. package/src/logger.ts +66 -0
  67. package/src/miniflare-cli/README.md +30 -0
  68. package/src/miniflare-cli/enum-keys.ts +17 -0
  69. package/src/miniflare-cli/index.ts +41 -0
  70. package/src/miniflare-cli/request-context.ts +40 -0
  71. package/src/module-collection.ts +63 -58
  72. package/src/open-in-browser.ts +5 -4
  73. package/src/package-manager.ts +9 -8
  74. package/src/pages.tsx +883 -62
  75. package/src/parse.ts +1 -1
  76. package/src/proxy.ts +32 -21
  77. package/src/publish.ts +276 -161
  78. package/src/sites.tsx +88 -33
  79. package/src/tail/index.ts +2 -2
  80. package/src/tail/printing.ts +13 -8
  81. package/src/update-check.ts +19 -0
  82. package/src/user.tsx +231 -103
  83. package/src/whoami.tsx +2 -1
  84. package/src/worker.ts +15 -6
  85. package/templates/gitignore +171 -0
  86. package/templates/static-asset-facade.js +1 -1
  87. package/wrangler-dist/cli.js +22087 -49340
  88. package/src/__tests__/dev2.test.tsx +0 -152
  89. package/src/__tests__/helpers/mock-user.ts +0 -27
  90. package/src/__tests__/logout.test.ts +0 -50
  91. package/src/__tests__/sentry.test.ts +0 -146
  92. package/src/reporting.ts +0 -131
  93. package/wrangler-dist/cli.js.map +0 -7
package/src/index.tsx CHANGED
@@ -3,19 +3,24 @@ import { writeFile, mkdir } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { setTimeout } from "node:timers/promises";
5
5
  import TOML from "@iarna/toml";
6
+ import chalk from "chalk";
7
+ import { execa } from "execa";
6
8
  import { findUp } from "find-up";
7
9
  import getPort from "get-port";
8
10
  import { render } from "ink";
9
11
  import React from "react";
10
12
  import onExit from "signal-exit";
13
+ import supportsColor from "supports-color";
11
14
  import makeCLI from "yargs";
12
15
  import { version as wranglerVersion } from "../package.json";
13
16
  import { fetchResult } from "./cfetch";
14
17
  import { findWranglerToml, readConfig } from "./config";
15
18
  import { createWorkerUploadForm } from "./create-worker-upload-form";
16
19
  import Dev from "./dev/dev";
20
+ import { getVarsForDev } from "./dev/dev-vars";
17
21
  import { confirm, prompt } from "./dialogs";
18
22
  import { getEntry } from "./entry";
23
+ import { DeprecationError } from "./errors";
19
24
  import {
20
25
  getNamespaceId,
21
26
  listNamespaces,
@@ -29,9 +34,16 @@ import {
29
34
  isKeyValue,
30
35
  unexpectedKeyValueProps,
31
36
  } from "./kv";
37
+ import { logger } from "./logger";
32
38
  import { getPackageManager } from "./package-manager";
33
- import { pages } from "./pages";
34
- 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";
35
47
  import publish from "./publish";
36
48
  import { createR2Bucket, deleteR2Bucket, listR2Buckets } from "./r2";
37
49
  import { getAssetPaths } from "./sites";
@@ -41,14 +53,13 @@ import {
41
53
  prettyPrintLogs,
42
54
  translateCLICommandToFilterMessage,
43
55
  } from "./tail";
56
+ import { updateCheck } from "./update-check";
44
57
  import {
45
58
  login,
46
59
  logout,
47
60
  listScopes,
48
- initialise as initialiseUserConfig,
49
- loginOrRefreshIfRequired,
50
- getAccountId,
51
61
  validateScopeKeys,
62
+ requireAuth,
52
63
  } from "./user";
53
64
  import { whoami } from "./whoami";
54
65
 
@@ -73,8 +84,8 @@ function getRules(config: Config): Config["rules"] {
73
84
  }
74
85
 
75
86
  if (config.build?.upload?.rules) {
76
- console.warn(
77
- `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:
78
89
 
79
90
  ${TOML.stringify({ rules: config.build.upload.rules })}`
80
91
  );
@@ -82,45 +93,56 @@ ${TOML.stringify({ rules: config.build.upload.rules })}`
82
93
  return rules;
83
94
  }
84
95
 
85
- function isLegacyEnv(args: unknown, config: Config): boolean {
86
- return (
87
- (args as { "legacy-env": boolean | undefined })["legacy-env"] ??
88
- config.legacy_env
96
+ async function printWranglerBanner() {
97
+ // Let's not print this in tests
98
+ if (typeof jest !== "undefined") {
99
+ return;
100
+ }
101
+
102
+ const text = ` ⛅️ wrangler ${wranglerVersion} ${await updateCheck()}`;
103
+
104
+ logger.log(
105
+ text +
106
+ "\n" +
107
+ (supportsColor.stdout
108
+ ? chalk.hex("#FF8800")("-".repeat(text.length))
109
+ : "-".repeat(text.length))
89
110
  );
90
111
  }
91
112
 
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;
117
+ }
118
+
92
119
  function getScriptName(
93
120
  args: { name: string | undefined; env: string | undefined },
94
121
  config: Config
95
122
  ): string | undefined {
96
- const shortScriptName = args.name ?? config.name;
97
- if (!shortScriptName) {
98
- 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
+ );
99
131
  }
100
132
 
101
- return isLegacyEnv(args, config)
102
- ? `${shortScriptName}${args.env ? `-${args.env}` : ""}`
103
- : shortScriptName;
133
+ return args.name ?? config.name;
104
134
  }
105
135
 
106
136
  /**
107
- * 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
108
138
  */
109
- async function requireAuth(
110
- config: Config,
111
- isInteractive = true
112
- ): Promise<string> {
113
- const loggedIn = await loginOrRefreshIfRequired(isInteractive);
114
- if (!loggedIn) {
115
- // didn't login, let's just quit
116
- throw new Error("Did not login, quitting...");
117
- }
118
- const accountId = config.account_id || (await getAccountId(isInteractive));
119
- if (!accountId) {
120
- throw new Error("No account id found, quitting...");
121
- }
122
-
123
- 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;
124
146
  }
125
147
 
126
148
  /**
@@ -182,21 +204,18 @@ function demandOneOfOption(...options: string[]) {
182
204
  }
183
205
 
184
206
  class CommandLineArgsError extends Error {}
185
- class DeprecationError extends Error {
186
- constructor(message: string) {
187
- super(`DEPRECATION WARNING:\n${message}`);
188
- }
189
- }
190
207
 
191
208
  export async function main(argv: string[]): Promise<void> {
192
209
  const wrangler = makeCLI(argv)
210
+ .strict()
193
211
  // We handle errors ourselves in a try-catch around `yargs.parse`.
194
212
  // If you want the "help info" to be displayed then throw an instance of `CommandLineArgsError`.
195
213
  // Otherwise we just log the error that was thrown without any "help info".
196
214
  .showHelpOnFail(false)
197
215
  .fail((msg, error) => {
198
- if (!error) {
199
- // 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.
200
219
  error = new CommandLineArgsError(msg);
201
220
  }
202
221
  throw error;
@@ -246,14 +265,19 @@ export async function main(argv: string[]): Promise<void> {
246
265
  default: "worker",
247
266
  })
248
267
  .positional("template", {
249
- describe: "a link to a GitHub template",
268
+ describe: "The URL of a GitHub template",
250
269
  default: "https://github.com/cloudflare/worker-template",
251
270
  });
252
271
  },
253
- () => {
272
+ (generateArgs) => {
254
273
  // "👯 [DEPRECATED]. Scaffold a Cloudflare Workers project from a public GitHub repository.",
255
274
  throw new DeprecationError(
256
- "`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."
257
281
  );
258
282
  }
259
283
  );
@@ -268,6 +292,18 @@ export async function main(argv: string[]): Promise<void> {
268
292
  describe: "The name of your worker",
269
293
  type: "string",
270
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
+ })
271
307
  .option("yes", {
272
308
  describe: 'Answer "yes" to any prompts for new projects',
273
309
  type: "boolean",
@@ -275,7 +311,8 @@ export async function main(argv: string[]): Promise<void> {
275
311
  });
276
312
  },
277
313
  async (args) => {
278
- if ("type" in args) {
314
+ await printWranglerBanner();
315
+ if (args.type) {
279
316
  let message = "The --type option is no longer supported.";
280
317
  if (args.type === "webpack") {
281
318
  message +=
@@ -285,9 +322,30 @@ export async function main(argv: string[]): Promise<void> {
285
322
  throw new CommandLineArgsError(message);
286
323
  }
287
324
 
288
- // 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
+ }
289
343
 
290
- 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, "-");
291
349
 
292
350
  const packageManager = await getPackageManager(creationDirectory);
293
351
 
@@ -299,10 +357,14 @@ export async function main(argv: string[]): Promise<void> {
299
357
  "./wrangler.toml"
300
358
  );
301
359
  let justCreatedWranglerToml = false;
302
- const workerName =
303
- args.name || path.basename(path.resolve(process.cwd()));
360
+
304
361
  if (fs.existsSync(wranglerTomlDestination)) {
305
- console.warn(`${wranglerTomlDestination} file already exists!`);
362
+ logger.warn(
363
+ `${path.relative(
364
+ process.cwd(),
365
+ wranglerTomlDestination
366
+ )} already exists!`
367
+ );
306
368
  const shouldContinue = await confirm(
307
369
  "Do you want to continue initializing this project?"
308
370
  );
@@ -312,6 +374,7 @@ export async function main(argv: string[]): Promise<void> {
312
374
  } else {
313
375
  await mkdir(creationDirectory, { recursive: true });
314
376
  const compatibilityDate = new Date().toISOString().substring(0, 10);
377
+
315
378
  try {
316
379
  await writeFile(
317
380
  wranglerTomlDestination,
@@ -320,18 +383,56 @@ export async function main(argv: string[]): Promise<void> {
320
383
  compatibility_date: compatibilityDate,
321
384
  }) + "\n"
322
385
  );
323
- console.log(`✨ Successfully created wrangler.toml`);
386
+
387
+ logger.log(
388
+ `✨ Created ${path.relative(
389
+ process.cwd(),
390
+ wranglerTomlDestination
391
+ )}`
392
+ );
324
393
  justCreatedWranglerToml = true;
325
394
  } catch (err) {
326
395
  throw new Error(
327
- `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}`
328
400
  );
329
401
  }
330
402
  }
331
403
 
332
- let pathToPackageJson = await findUp("package.json");
333
- let shouldCreatePackageJson = false;
334
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
+
335
436
  if (!pathToPackageJson) {
336
437
  // If no package.json exists, ask to create one
337
438
  shouldCreatePackageJson =
@@ -356,9 +457,12 @@ export async function main(argv: string[]): Promise<void> {
356
457
  " "
357
458
  ) + "\n"
358
459
  );
460
+
359
461
  await packageManager.install();
360
- console.log(`✨ Created package.json`);
361
462
  pathToPackageJson = path.join(creationDirectory, "package.json");
463
+ logger.log(
464
+ `✨ Created ${path.relative(process.cwd(), pathToPackageJson)}`
465
+ );
362
466
  } else {
363
467
  return;
364
468
  }
@@ -378,17 +482,22 @@ export async function main(argv: string[]): Promise<void> {
378
482
  const shouldInstall =
379
483
  yesFlag ||
380
484
  (await confirm(
381
- "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
+ )}?`
382
489
  ));
383
490
  if (shouldInstall) {
384
491
  await packageManager.addDevDeps(`wrangler@${wranglerVersion}`);
385
- console.log(`✨ Installed wrangler`);
492
+ logger.log(`✨ Installed wrangler`);
386
493
  }
387
494
  }
388
495
  }
389
496
 
390
497
  let isTypescriptProject = false;
391
- let pathToTSConfig = await findUp("tsconfig.json");
498
+ let pathToTSConfig = await findUp("tsconfig.json", {
499
+ cwd: creationDirectory,
500
+ });
392
501
  if (!pathToTSConfig) {
393
502
  // If there's no tsconfig, offer to create one
394
503
  // and install @cloudflare/workers-types
@@ -402,11 +511,13 @@ export async function main(argv: string[]): Promise<void> {
402
511
  "@cloudflare/workers-types",
403
512
  "typescript"
404
513
  );
405
-
406
- console.log(
407
- `✨ Created tsconfig.json, installed @cloudflare/workers-types into devDependencies`
408
- );
409
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
+ );
410
521
  }
411
522
  } else {
412
523
  isTypescriptProject = true;
@@ -431,8 +542,11 @@ export async function main(argv: string[]): Promise<void> {
431
542
  // it could be complicated in existing projects
432
543
  // and we don't want to break them. Instead, we simply
433
544
  // tell the user that they need to update their tsconfig.json
434
- console.log(
435
- `✨ 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
+ )}`
436
550
  );
437
551
  }
438
552
  }
@@ -455,7 +569,7 @@ export async function main(argv: string[]): Promise<void> {
455
569
  ) {
456
570
  if (isCreatingWranglerToml) {
457
571
  // rewrite wrangler.toml with main = "path/to/script"
458
- const parsedWranglerToml = TOML.parse(
572
+ const parsedWranglerToml = parseTOML(
459
573
  readFileSync(wranglerTomlDestination)
460
574
  );
461
575
  fs.writeFileSync(
@@ -490,21 +604,21 @@ export async function main(argv: string[]): Promise<void> {
490
604
  2
491
605
  ) + "\n"
492
606
  );
493
- console.log(
607
+ logger.log(
494
608
  `\nTo start developing your Worker, run \`${
495
609
  isNamedWorker ? `cd ${args.name} && ` : ""
496
610
  }npm start\``
497
611
  );
498
- console.log(
612
+ logger.log(
499
613
  `To publish your Worker to the Internet, run \`npm run publish\``
500
614
  );
501
615
  } else {
502
- console.log(
616
+ logger.log(
503
617
  `\nTo start developing your Worker, run \`npx wrangler dev\`${
504
618
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
505
619
  }`
506
620
  );
507
- console.log(
621
+ logger.log(
508
622
  `To publish your Worker to the Internet, run \`npx wrangler publish\`${
509
623
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
510
624
  }`
@@ -514,12 +628,13 @@ export async function main(argv: string[]): Promise<void> {
514
628
 
515
629
  if (isTypescriptProject) {
516
630
  if (!fs.existsSync(path.join(creationDirectory, "./src/index.ts"))) {
517
- let shouldCreateSource = false;
518
-
519
- shouldCreateSource =
631
+ const shouldCreateSource =
520
632
  yesFlag ||
521
633
  (await confirm(
522
- `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
+ )}?`
523
638
  ));
524
639
 
525
640
  if (shouldCreateSource) {
@@ -531,7 +646,12 @@ export async function main(argv: string[]): Promise<void> {
531
646
  readFileSync(path.join(__dirname, "../templates/new-worker.ts"))
532
647
  );
533
648
 
534
- 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
+ );
535
655
 
536
656
  await writePackageJsonScriptsAndUpdateWranglerToml(
537
657
  shouldWritePackageJsonScripts,
@@ -542,20 +662,31 @@ export async function main(argv: string[]): Promise<void> {
542
662
  }
543
663
  }
544
664
  } else {
545
- if (!fs.existsSync("./src/index.js")) {
546
- const shouldCreateSource = await confirm(
547
- `Would you like to create a Worker at src/index.js?`
548
- );
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
+
549
675
  if (shouldCreateSource) {
550
676
  await mkdir(path.join(creationDirectory, "./src"), {
551
677
  recursive: true,
552
678
  });
553
679
  await writeFile(
554
- path.join(path.join(creationDirectory, "./src/index.js")),
680
+ path.join(creationDirectory, "./src/index.js"),
555
681
  readFileSync(path.join(__dirname, "../templates/new-worker.js"))
556
682
  );
557
683
 
558
- 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
+ );
559
690
 
560
691
  await writePackageJsonScriptsAndUpdateWranglerToml(
561
692
  shouldWritePackageJsonScripts,
@@ -586,76 +717,6 @@ export async function main(argv: string[]): Promise<void> {
586
717
  }
587
718
  );
588
719
 
589
- // login
590
- wrangler.command(
591
- // this needs scopes as an option?
592
- "login",
593
- false, // we don't need to show this in the menu
594
- // "🔓 Login to Cloudflare",
595
- (yargs) => {
596
- // TODO: This needs some copy editing
597
- // I mean, this entire app does, but this too.
598
- return yargs
599
- .option("scopes-list", {
600
- describe: "List all the available OAuth scopes with descriptions",
601
- })
602
- .option("scopes", {
603
- describe: "Pick the set of applicable OAuth scopes when logging in",
604
- array: true,
605
- type: "string",
606
- });
607
-
608
- // TODO: scopes
609
- },
610
- async (args) => {
611
- if (args["scopes-list"]) {
612
- listScopes();
613
- return;
614
- }
615
- if (args.scopes) {
616
- if (args.scopes.length === 0) {
617
- // don't allow no scopes to be passed, that would be weird
618
- listScopes();
619
- return;
620
- }
621
- if (!validateScopeKeys(args.scopes)) {
622
- throw new CommandLineArgsError(
623
- `One of ${args.scopes} is not a valid authentication scope. Run "wrangler login --list-scopes" to see the valid scopes.`
624
- );
625
- }
626
- await login({ scopes: args.scopes });
627
- return;
628
- }
629
- await login();
630
-
631
- // TODO: would be nice if it optionally saved login
632
- // credentials inside node_modules/.cache or something
633
- // this way you could have multiple users on a single machine
634
- }
635
- );
636
-
637
- // logout
638
- wrangler.command(
639
- // this needs scopes as an option?
640
- "logout",
641
- false, // we don't need to show this in the menu
642
- // "🚪 Logout from Cloudflare",
643
- () => {},
644
- async () => {
645
- await logout();
646
- }
647
- );
648
-
649
- // whoami
650
- wrangler.command(
651
- "whoami",
652
- "🕵️ Retrieve your user info and test your auth config",
653
- () => {},
654
- async () => {
655
- await whoami();
656
- }
657
- );
658
-
659
720
  // config
660
721
  wrangler.command(
661
722
  "config",
@@ -664,7 +725,7 @@ export async function main(argv: string[]): Promise<void> {
664
725
  () => {
665
726
  // "🕵️ Authenticate Wrangler with a Cloudflare API Token",
666
727
  throw new DeprecationError(
667
- "`wrangler config` has been deprecated, please refer to TODO://some/path for alternatives"
728
+ "`wrangler config` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#config for alternatives"
668
729
  );
669
730
  }
670
731
  );
@@ -682,23 +743,30 @@ export async function main(argv: string[]): Promise<void> {
682
743
  .option("name", {
683
744
  describe: "Name of the worker",
684
745
  type: "string",
746
+ requiresArg: true,
685
747
  })
686
748
  .option("format", {
687
749
  choices: ["modules", "service-worker"] as const,
688
750
  describe: "Choose an entry type",
751
+ deprecated: true,
689
752
  })
690
753
  .option("env", {
691
754
  describe: "Perform on a specific environment",
692
755
  type: "string",
756
+ requiresArg: true,
757
+ alias: "e",
693
758
  })
694
759
  .option("compatibility-date", {
695
760
  describe: "Date to use for compatibility checks",
696
761
  type: "string",
762
+ requiresArg: true,
697
763
  })
698
764
  .option("compatibility-flags", {
699
765
  describe: "Flags to use for compatibility checks",
700
- type: "array",
701
766
  alias: "compatibility-flag",
767
+ type: "string",
768
+ requiresArg: true,
769
+ array: true,
702
770
  })
703
771
  .option("latest", {
704
772
  describe: "Use the latest version of the worker runtime",
@@ -706,9 +774,9 @@ export async function main(argv: string[]): Promise<void> {
706
774
  default: true,
707
775
  })
708
776
  .option("ip", {
709
- describe: "IP address to listen on",
777
+ describe: "IP address to listen on, defaults to `localhost`",
710
778
  type: "string",
711
- default: "127.0.0.1",
779
+ requiresArg: true,
712
780
  })
713
781
  .option("port", {
714
782
  describe: "Port to listen on",
@@ -721,10 +789,13 @@ export async function main(argv: string[]): Promise<void> {
721
789
  .option("routes", {
722
790
  describe: "Routes to upload",
723
791
  alias: "route",
724
- type: "array",
792
+ type: "string",
793
+ requiresArg: true,
794
+ array: true,
725
795
  })
726
796
  .option("host", {
727
797
  type: "string",
798
+ requiresArg: true,
728
799
  describe:
729
800
  "Host to forward requests to, defaults to the zone of project",
730
801
  })
@@ -735,21 +806,25 @@ export async function main(argv: string[]): Promise<void> {
735
806
  .option("experimental-public", {
736
807
  describe: "Static assets to be served",
737
808
  type: "string",
809
+ requiresArg: true,
738
810
  })
739
811
  .option("site", {
740
812
  describe: "Root folder of static assets for Workers Sites",
741
813
  type: "string",
814
+ requiresArg: true,
742
815
  })
743
816
  .option("site-include", {
744
817
  describe:
745
818
  "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.",
746
819
  type: "string",
820
+ requiresArg: true,
747
821
  array: true,
748
822
  })
749
823
  .option("site-exclude", {
750
824
  describe:
751
825
  "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.",
752
826
  type: "string",
827
+ requiresArg: true,
753
828
  array: true,
754
829
  })
755
830
  .option("upstream-protocol", {
@@ -760,10 +835,17 @@ export async function main(argv: string[]): Promise<void> {
760
835
  .option("jsx-factory", {
761
836
  describe: "The function that is called for each JSX element",
762
837
  type: "string",
838
+ requiresArg: true,
763
839
  })
764
840
  .option("jsx-fragment", {
765
841
  describe: "The function that is called for each JSX fragment",
766
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,
767
849
  })
768
850
  .option("local", {
769
851
  alias: "l",
@@ -771,35 +853,54 @@ export async function main(argv: string[]): Promise<void> {
771
853
  type: "boolean",
772
854
  default: false, // I bet this will a point of contention. We'll revisit it.
773
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
+ })
774
864
  .option("experimental-enable-local-persistence", {
775
865
  describe: "Enable persistence for this session (only for local mode)",
776
866
  type: "boolean",
867
+ })
868
+ .option("inspect", {
869
+ describe: "Enable dev tools",
870
+ type: "boolean",
871
+ deprecated: true,
777
872
  });
778
873
  },
779
874
  async (args) => {
780
- const config = readConfig(
875
+ await printWranglerBanner();
876
+ const configPath =
781
877
  (args.config as ConfigPath) ||
782
- (args.script && findWranglerToml(path.dirname(args.script)))
783
- );
878
+ (args.script && findWranglerToml(path.dirname(args.script)));
879
+ const config = readConfig(configPath, args);
784
880
  const entry = await getEntry(args, config, "dev");
785
881
 
882
+ if (args.inspect) {
883
+ logger.warn(
884
+ "Passing --inspect is unnecessary, now you can always connect to devtools."
885
+ );
886
+ }
887
+
786
888
  if (args["experimental-public"]) {
787
- console.warn(
788
- "🚨 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."
789
891
  );
790
892
  }
791
893
 
792
894
  if (args.public) {
793
895
  throw new Error(
794
- "🚨 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."
795
897
  );
796
898
  }
797
899
 
798
900
  const upstreamProtocol =
799
- (args["upstream-protocol"] as "http" | "https") ||
800
- config.dev.upstream_protocol;
901
+ args["upstream-protocol"] || config.dev.upstream_protocol;
801
902
  if (upstreamProtocol === "http") {
802
- console.warn(
903
+ logger.warn(
803
904
  "Setting upstream-protocol to http is not currently implemented.\n" +
804
905
  "If this is required in your project, please add your use case to the following issue:\n" +
805
906
  "https://github.com/cloudflare/wrangler2/issues/583."
@@ -836,16 +937,6 @@ export async function main(argv: string[]): Promise<void> {
836
937
  return zones[0]?.id;
837
938
  }
838
939
 
839
- const environments = config.env ?? {};
840
- const envRootObj = args.env ? environments[args.env] || {} : config;
841
-
842
- const hostLike =
843
- args.host ||
844
- config.dev.host ||
845
- (args.routes && args.routes[0]) ||
846
- envRootObj.route ||
847
- (envRootObj.routes && envRootObj.routes[0]);
848
-
849
940
  // When we're given a host (in one of the above ways), we do 2 things:
850
941
  // - We try to extract a host from it
851
942
  // - We try to get a zone id from the host
@@ -856,27 +947,57 @@ export async function main(argv: string[]): Promise<void> {
856
947
  // get a zone id for it, by lopping off subdomains until we get a hit
857
948
  // from the API. That's it!
858
949
 
859
- 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
+ }
860
981
 
861
- let zoneId: string | undefined;
862
- const hostPieces = typeof host === "string" ? host.split(".") : undefined;
982
+ if (host && !zoneId) {
983
+ throw new Error(`Could not find zone for ${hostLike}`);
984
+ }
863
985
 
864
- while (hostPieces && hostPieces.length > 1) {
865
- zoneId = await getZoneId(hostPieces.join("."));
866
- if (zoneId) break;
867
- hostPieces.shift();
868
- }
869
- if (host && !zoneId) {
870
- 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;
871
993
  }
872
994
 
873
- const zone =
874
- typeof zoneId === "string" && typeof host === "string"
875
- ? {
876
- host,
877
- id: zoneId,
878
- }
879
- : 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
+ }
880
1001
 
881
1002
  const { waitUntilExit } = render(
882
1003
  <Dev
@@ -885,18 +1006,16 @@ export async function main(argv: string[]): Promise<void> {
885
1006
  env={args.env}
886
1007
  zone={zone}
887
1008
  rules={getRules(config)}
888
- legacyEnv={isLegacyEnv(args, config)}
889
- buildCommand={config.build || {}}
1009
+ legacyEnv={isLegacyEnv(config)}
1010
+ minify={args.minify ?? config.minify}
1011
+ nodeCompat={nodeCompat}
1012
+ build={config.build || {}}
890
1013
  initialMode={args.local ? "local" : "remote"}
891
- jsxFactory={args["jsx-factory"] || envRootObj?.jsx_factory}
892
- jsxFragment={args["jsx-fragment"] || envRootObj?.jsx_fragment}
1014
+ jsxFactory={args["jsx-factory"] || config.jsx_factory}
1015
+ jsxFragment={args["jsx-fragment"] || config.jsx_fragment}
1016
+ tsconfig={args.tsconfig ?? config.tsconfig}
893
1017
  upstreamProtocol={upstreamProtocol}
894
- localProtocol={
895
- // The typings are not quite clever enough to handle element accesses, only property accesses,
896
- // so we need to cast here.
897
- (args["local-protocol"] as "http" | "https") ||
898
- config.dev.local_protocol
899
- }
1018
+ localProtocol={args["local-protocol"] || config.dev.local_protocol}
900
1019
  enableLocalPersistence={
901
1020
  args["experimental-enable-local-persistence"] || false
902
1021
  }
@@ -910,22 +1029,21 @@ export async function main(argv: string[]): Promise<void> {
910
1029
  port={
911
1030
  args.port || config.dev?.port || (await getPort({ port: 8787 }))
912
1031
  }
1032
+ ip={args.ip || config.dev.ip}
913
1033
  inspectorPort={
914
1034
  args["inspector-port"] ?? (await getPort({ port: 9229 }))
915
1035
  }
916
1036
  public={args["experimental-public"]}
917
- compatibilityDate={
918
- args["compatibility-date"] ||
919
- config.compatibility_date ||
920
- new Date().toISOString().substring(0, 10)
921
- }
1037
+ compatibilityDate={getDevCompatibilityDate(
1038
+ config,
1039
+ args["compatibility-date"]
1040
+ )}
922
1041
  compatibilityFlags={
923
- (args["compatibility-flags"] as string[]) ||
924
- config.compatibility_flags
1042
+ args["compatibility-flags"] || config.compatibility_flags
925
1043
  }
926
- usageModel={envRootObj.usage_model}
1044
+ usageModel={config.usage_model}
927
1045
  bindings={{
928
- kv_namespaces: envRootObj.kv_namespaces?.map(
1046
+ kv_namespaces: config.kv_namespaces?.map(
929
1047
  ({ binding, preview_id, id: _id }) => {
930
1048
  // In `dev`, we make folks use a separate kv namespace called
931
1049
  // `preview_id` instead of `id` so that they don't
@@ -946,13 +1064,29 @@ export async function main(argv: string[]): Promise<void> {
946
1064
  };
947
1065
  }
948
1066
  ),
949
- vars: envRootObj.vars,
1067
+ vars: getVarsForDev(config),
950
1068
  wasm_modules: config.wasm_modules,
951
1069
  text_blobs: config.text_blobs,
952
- durable_objects: envRootObj.durable_objects,
953
- r2_buckets: envRootObj.r2_buckets,
954
- unsafe: envRootObj.unsafe?.bindings,
1070
+ data_blobs: config.data_blobs,
1071
+ durable_objects: config.durable_objects,
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
+ ),
1087
+ unsafe: config.unsafe?.bindings,
955
1088
  }}
1089
+ crons={config.triggers.crons}
956
1090
  />
957
1091
  );
958
1092
  await waitUntilExit();
@@ -967,28 +1101,41 @@ export async function main(argv: string[]): Promise<void> {
967
1101
  return yargs
968
1102
  .option("env", {
969
1103
  type: "string",
1104
+ requiresArg: true,
970
1105
  describe: "Perform on a specific environment",
1106
+ alias: "e",
971
1107
  })
972
1108
  .positional("script", {
973
1109
  describe: "The path to an entry point for your worker",
974
1110
  type: "string",
1111
+ requiresArg: true,
975
1112
  })
976
1113
  .option("name", {
977
1114
  describe: "Name of the worker",
978
1115
  type: "string",
1116
+ requiresArg: true,
1117
+ })
1118
+ .option("outdir", {
1119
+ describe: "Output directory for the bundled worker",
1120
+ type: "string",
1121
+ requiresArg: true,
979
1122
  })
980
1123
  .option("format", {
981
1124
  choices: ["modules", "service-worker"] as const,
982
1125
  describe: "Choose an entry type",
1126
+ deprecated: true,
983
1127
  })
984
1128
  .option("compatibility-date", {
985
1129
  describe: "Date to use for compatibility checks",
986
1130
  type: "string",
1131
+ requiresArg: true,
987
1132
  })
988
1133
  .option("compatibility-flags", {
989
1134
  describe: "Flags to use for compatibility checks",
990
- type: "array",
991
1135
  alias: "compatibility-flag",
1136
+ type: "string",
1137
+ requiresArg: true,
1138
+ array: true,
992
1139
  })
993
1140
  .option("latest", {
994
1141
  describe: "Use the latest version of the worker runtime",
@@ -998,63 +1145,91 @@ export async function main(argv: string[]): Promise<void> {
998
1145
  .option("experimental-public", {
999
1146
  describe: "Static assets to be served",
1000
1147
  type: "string",
1148
+ requiresArg: true,
1001
1149
  })
1002
1150
  .option("site", {
1003
1151
  describe: "Root folder of static assets for Workers Sites",
1004
1152
  type: "string",
1153
+ requiresArg: true,
1005
1154
  })
1006
1155
  .option("site-include", {
1007
1156
  describe:
1008
1157
  "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.",
1009
1158
  type: "string",
1159
+ requiresArg: true,
1010
1160
  array: true,
1011
1161
  })
1012
1162
  .option("site-exclude", {
1013
1163
  describe:
1014
1164
  "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.",
1015
1165
  type: "string",
1166
+ requiresArg: true,
1016
1167
  array: true,
1017
1168
  })
1018
1169
  .option("triggers", {
1019
1170
  describe: "cron schedules to attach",
1020
1171
  alias: ["schedule", "schedules"],
1021
- type: "array",
1172
+ type: "string",
1173
+ requiresArg: true,
1174
+ array: true,
1022
1175
  })
1023
1176
  .option("routes", {
1024
1177
  describe: "Routes to upload",
1025
1178
  alias: "route",
1026
- type: "array",
1179
+ type: "string",
1180
+ requiresArg: true,
1181
+ array: true,
1027
1182
  })
1028
1183
  .option("jsx-factory", {
1029
1184
  describe: "The function that is called for each JSX element",
1030
1185
  type: "string",
1186
+ requiresArg: true,
1031
1187
  })
1032
1188
  .option("jsx-fragment", {
1033
1189
  describe: "The function that is called for each JSX fragment",
1034
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",
1035
1209
  });
1036
1210
  },
1037
1211
  async (args) => {
1212
+ await printWranglerBanner();
1038
1213
  if (args["experimental-public"]) {
1039
- console.warn(
1040
- "🚨 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."
1041
1216
  );
1042
1217
  }
1043
1218
  if (args.public) {
1044
1219
  throw new Error(
1045
- "🚨 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."
1046
1221
  );
1047
1222
  }
1048
1223
 
1049
- const config = readConfig(
1224
+ const configPath =
1050
1225
  (args.config as ConfigPath) ||
1051
- (args.script && findWranglerToml(path.dirname(args.script)))
1052
- );
1226
+ (args.script && findWranglerToml(path.dirname(args.script)));
1227
+ const config = readConfig(configPath, args);
1053
1228
  const entry = await getEntry(args, config, "publish");
1054
1229
 
1055
1230
  if (args.latest) {
1056
- console.warn(
1057
- "⚠️ 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"
1058
1233
  );
1059
1234
  }
1060
1235
 
@@ -1076,14 +1251,19 @@ export async function main(argv: string[]): Promise<void> {
1076
1251
  compatibilityDate: args.latest
1077
1252
  ? new Date().toISOString().substring(0, 10)
1078
1253
  : args["compatibility-date"],
1079
- compatibilityFlags: args["compatibility-flags"] as string[],
1254
+ compatibilityFlags: args["compatibility-flags"],
1080
1255
  triggers: args.triggers,
1081
1256
  jsxFactory: args["jsx-factory"],
1082
1257
  jsxFragment: args["jsx-fragment"],
1258
+ tsconfig: args.tsconfig,
1083
1259
  routes: args.routes,
1084
1260
  assetPaths,
1085
- legacyEnv: isLegacyEnv(args, config),
1261
+ legacyEnv: isLegacyEnv(config),
1262
+ minify: args.minify,
1263
+ nodeCompat: args.nodeCompat,
1086
1264
  experimentalPublic: args["experimental-public"] !== undefined,
1265
+ outDir: args.outdir,
1266
+ dryRun: args.dryRun,
1087
1267
  });
1088
1268
  }
1089
1269
  );
@@ -1093,63 +1273,69 @@ export async function main(argv: string[]): Promise<void> {
1093
1273
  "tail [name]",
1094
1274
  "🦚 Starts a log tailing session for a published Worker.",
1095
1275
  (yargs) => {
1096
- return (
1097
- yargs
1098
- .positional("name", {
1099
- describe: "Name of the worker",
1100
- type: "string",
1101
- })
1102
- // TODO: auto-detect if this should be json or pretty based on atty
1103
- .option("format", {
1104
- default: "json",
1105
- choices: ["json", "pretty"],
1106
- describe: "The format of log entries",
1107
- })
1108
- .option("status", {
1109
- choices: ["ok", "error", "canceled"],
1110
- describe: "Filter by invocation status",
1111
- array: true,
1112
- })
1113
- .option("header", {
1114
- type: "string",
1115
- describe: "Filter by HTTP header",
1116
- })
1117
- .option("method", {
1118
- type: "string",
1119
- describe: "Filter by HTTP method",
1120
- array: true,
1121
- })
1122
- .option("sampling-rate", {
1123
- type: "number",
1124
- describe: "Adds a percentage of requests to log sampling rate",
1125
- })
1126
- .option("search", {
1127
- type: "string",
1128
- describe: "Filter by a text match in console.log messages",
1129
- })
1130
- .option("ip", {
1131
- type: "string",
1132
- describe:
1133
- 'Filter by the IP address the request originates from. Use "self" to filter for your own IP',
1134
- array: true,
1135
- })
1136
- .option("env", {
1137
- type: "string",
1138
- describe: "Perform on a specific environment",
1139
- })
1140
- .option("debug", {
1141
- type: "boolean",
1142
- hidden: true,
1143
- default: false,
1144
- describe:
1145
- "If a log would have been filtered out, send it through anyway alongside the filter which would have blocked it.",
1146
- })
1147
- );
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
+ });
1148
1331
  },
1149
1332
  async (args) => {
1150
- const config = readConfig(args.config as ConfigPath);
1333
+ if (args.format === "pretty") {
1334
+ await printWranglerBanner();
1335
+ }
1336
+ const config = readConfig(args.config as ConfigPath, args);
1151
1337
 
1152
- const scriptName = getScriptName(args, config);
1338
+ const scriptName = getLegacyScriptName(args, config);
1153
1339
 
1154
1340
  if (!scriptName) {
1155
1341
  throw new Error("Missing script name");
@@ -1158,7 +1344,7 @@ export async function main(argv: string[]): Promise<void> {
1158
1344
  const accountId = await requireAuth(config);
1159
1345
 
1160
1346
  const cliFilters: TailCLIFilters = {
1161
- status: args.status as ("ok" | "error" | "canceled")[],
1347
+ status: args.status as ("ok" | "error" | "canceled")[] | undefined,
1162
1348
  header: args.header,
1163
1349
  method: args.method,
1164
1350
  samplingRate: args["sampling-rate"],
@@ -1175,16 +1361,18 @@ export async function main(argv: string[]): Promise<void> {
1175
1361
  accountId,
1176
1362
  scriptName,
1177
1363
  filters,
1178
- !isLegacyEnv(args, config) ? args.env : undefined
1364
+ !isLegacyEnv(config) ? args.env : undefined
1179
1365
  );
1180
1366
 
1181
1367
  const scriptDisplayName = `${scriptName}${
1182
- args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1368
+ args.env && !isLegacyEnv(config) ? ` (${args.env})` : ""
1183
1369
  }`;
1184
1370
 
1185
- console.log(
1186
- `successfully created tail, expires at ${expiration.toLocaleString()}`
1187
- );
1371
+ if (args.format === "pretty") {
1372
+ logger.log(
1373
+ `Successfully created tail, expires at ${expiration.toLocaleString()}`
1374
+ );
1375
+ }
1188
1376
 
1189
1377
  onExit(async () => {
1190
1378
  tail.terminate();
@@ -1211,7 +1399,9 @@ export async function main(argv: string[]): Promise<void> {
1211
1399
  }
1212
1400
  }
1213
1401
 
1214
- console.log(`Connected to ${scriptDisplayName}, waiting for logs...`);
1402
+ if (args.format === "pretty") {
1403
+ logger.log(`Connected to ${scriptDisplayName}, waiting for logs...`);
1404
+ }
1215
1405
 
1216
1406
  tail.on("close", async () => {
1217
1407
  tail.terminate();
@@ -1236,6 +1426,7 @@ export async function main(argv: string[]): Promise<void> {
1236
1426
  })
1237
1427
  .option("env", {
1238
1428
  type: "string",
1429
+ requiresArg: true,
1239
1430
  describe: "Perform on a specific environment",
1240
1431
  })
1241
1432
  .option("watch", {
@@ -1253,21 +1444,18 @@ export async function main(argv: string[]): Promise<void> {
1253
1444
  }
1254
1445
 
1255
1446
  // Delegate to `wrangler dev`
1256
- console.warn(
1447
+ logger.warn(
1257
1448
  "***************************************************\n" +
1258
1449
  "The `wrangler preview` command has been deprecated.\n" +
1259
1450
  "Attempting to run `wrangler dev` instead.\n" +
1260
1451
  "***************************************************\n"
1261
1452
  );
1262
1453
 
1263
- const config = readConfig(args.config as ConfigPath);
1454
+ const config = readConfig(args.config as ConfigPath, args);
1264
1455
  const entry = await getEntry({}, config, "dev");
1265
1456
 
1266
1457
  const accountId = await requireAuth(config);
1267
1458
 
1268
- const environments = config.env ?? {};
1269
- const envRootObj = args.env ? environments[args.env] || {} : config;
1270
-
1271
1459
  const { waitUntilExit } = render(
1272
1460
  <Dev
1273
1461
  name={config.name}
@@ -1275,29 +1463,27 @@ export async function main(argv: string[]): Promise<void> {
1275
1463
  rules={getRules(config)}
1276
1464
  env={args.env}
1277
1465
  zone={undefined}
1278
- legacyEnv={isLegacyEnv(args, config)}
1279
- buildCommand={config.build || {}}
1466
+ legacyEnv={isLegacyEnv(config)}
1467
+ build={config.build || {}}
1468
+ minify={undefined}
1469
+ nodeCompat={config.node_compat}
1280
1470
  initialMode={args.local ? "local" : "remote"}
1281
- jsxFactory={envRootObj?.jsx_factory}
1282
- jsxFragment={envRootObj?.jsx_fragment}
1471
+ jsxFactory={config.jsx_factory}
1472
+ jsxFragment={config.jsx_fragment}
1473
+ tsconfig={config.tsconfig}
1283
1474
  upstreamProtocol={config.dev.upstream_protocol}
1284
1475
  localProtocol={config.dev.local_protocol}
1285
1476
  enableLocalPersistence={false}
1286
1477
  accountId={accountId}
1287
1478
  assetPaths={undefined}
1288
1479
  port={config.dev?.port}
1480
+ ip={config.dev.ip}
1289
1481
  public={undefined}
1290
- compatibilityDate={
1291
- config.compatibility_date ||
1292
- new Date().toISOString().substring(0, 10)
1293
- }
1294
- compatibilityFlags={
1295
- (args["compatibility-flags"] as string[]) ||
1296
- config.compatibility_flags
1297
- }
1482
+ compatibilityDate={getDevCompatibilityDate(config)}
1483
+ compatibilityFlags={config.compatibility_flags}
1298
1484
  usageModel={config.usage_model}
1299
1485
  bindings={{
1300
- kv_namespaces: envRootObj.kv_namespaces?.map(
1486
+ kv_namespaces: config.kv_namespaces?.map(
1301
1487
  ({ binding, preview_id, id: _id }) => {
1302
1488
  // In `dev`, we make folks use a separate kv namespace called
1303
1489
  // `preview_id` instead of `id` so that they don't
@@ -1318,13 +1504,29 @@ export async function main(argv: string[]): Promise<void> {
1318
1504
  };
1319
1505
  }
1320
1506
  ),
1321
- vars: envRootObj.vars,
1507
+ vars: config.vars,
1322
1508
  wasm_modules: config.wasm_modules,
1323
1509
  text_blobs: config.text_blobs,
1324
- durable_objects: envRootObj.durable_objects,
1325
- r2_buckets: envRootObj.r2_buckets,
1326
- unsafe: envRootObj.unsafe?.bindings,
1510
+ data_blobs: config.data_blobs,
1511
+ durable_objects: config.durable_objects,
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
+ ),
1527
+ unsafe: config.unsafe?.bindings,
1327
1528
  }}
1529
+ crons={config.triggers.crons}
1328
1530
  inspectorPort={await getPort({ port: 9229 })}
1329
1531
  />
1330
1532
  );
@@ -1346,10 +1548,12 @@ export async function main(argv: string[]): Promise<void> {
1346
1548
  return yargs
1347
1549
  .option("env", {
1348
1550
  type: "string",
1551
+ requiresArg: true,
1349
1552
  describe: "Perform on a specific environment",
1350
1553
  })
1351
1554
  .option("zone", {
1352
1555
  type: "string",
1556
+ requiresArg: true,
1353
1557
  describe: "Zone id",
1354
1558
  })
1355
1559
  .positional("zone", {
@@ -1371,7 +1575,7 @@ export async function main(argv: string[]): Promise<void> {
1371
1575
  }
1372
1576
  )
1373
1577
  .command(
1374
- "delete",
1578
+ "delete [id]",
1375
1579
  "Delete a route associated with a zone",
1376
1580
  (yargs) => {
1377
1581
  return yargs
@@ -1381,10 +1585,12 @@ export async function main(argv: string[]): Promise<void> {
1381
1585
  })
1382
1586
  .option("zone", {
1383
1587
  type: "string",
1588
+ requiresArg: true,
1384
1589
  describe: "zone id",
1385
1590
  })
1386
1591
  .option("env", {
1387
1592
  type: "string",
1593
+ requiresArg: true,
1388
1594
  describe: "Perform on a specific environment",
1389
1595
  });
1390
1596
  },
@@ -1417,7 +1623,7 @@ export async function main(argv: string[]): Promise<void> {
1417
1623
  },
1418
1624
  () => {
1419
1625
  throw new DeprecationError(
1420
- "`wrangler subdomain` has been deprecated, please refer to TODO://some/path for alternatives"
1626
+ "`wrangler subdomain` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#subdomain for alternatives"
1421
1627
  );
1422
1628
  }
1423
1629
  );
@@ -1441,37 +1647,41 @@ export async function main(argv: string[]): Promise<void> {
1441
1647
  .option("name", {
1442
1648
  describe: "Name of the worker",
1443
1649
  type: "string",
1650
+ requiresArg: true,
1444
1651
  })
1445
1652
  .option("env", {
1446
1653
  type: "string",
1654
+ requiresArg: true,
1447
1655
  describe:
1448
1656
  "Binds the secret to the Worker of the specific environment",
1657
+ alias: "e",
1449
1658
  });
1450
1659
  },
1451
1660
  async (args) => {
1452
- const config = readConfig(args.config as ConfigPath);
1661
+ await printWranglerBanner();
1662
+ const config = readConfig(args.config as ConfigPath, args);
1453
1663
 
1454
- const scriptName = getScriptName(args, config);
1664
+ const scriptName = getLegacyScriptName(args, config);
1455
1665
  if (!scriptName) {
1456
1666
  throw new Error("Missing script name");
1457
1667
  }
1458
1668
 
1459
- const isInteractive = process.stdin.isTTY;
1460
- const accountId = await requireAuth(config, isInteractive);
1669
+ const accountId = await requireAuth(config);
1461
1670
 
1671
+ const isInteractive = process.stdin.isTTY;
1462
1672
  const secretValue = isInteractive
1463
1673
  ? await prompt("Enter a secret value:", "password")
1464
1674
  : await readFromStdin();
1465
1675
 
1466
- console.log(
1676
+ logger.log(
1467
1677
  `🌀 Creating the secret for script ${scriptName} ${
1468
- args.env && !isLegacyEnv(args, config) ? `(${args.env})` : ""
1678
+ args.env && !isLegacyEnv(config) ? `(${args.env})` : ""
1469
1679
  }`
1470
1680
  );
1471
1681
 
1472
1682
  async function submitSecret() {
1473
1683
  const url =
1474
- !args.env || isLegacyEnv(args, config)
1684
+ !args.env || isLegacyEnv(config)
1475
1685
  ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
1476
1686
  : `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1477
1687
 
@@ -1489,7 +1699,7 @@ export async function main(argv: string[]): Promise<void> {
1489
1699
  const createDraftWorker = async () => {
1490
1700
  // TODO: log a warning
1491
1701
  await fetchResult(
1492
- !args["legacy-env"] && args.env
1702
+ !isLegacyEnv(config) && args.env
1493
1703
  ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}`
1494
1704
  : `/accounts/${accountId}/workers/scripts/${scriptName}`,
1495
1705
  {
@@ -1508,6 +1718,7 @@ export async function main(argv: string[]): Promise<void> {
1508
1718
  r2_buckets: [],
1509
1719
  wasm_modules: {},
1510
1720
  text_blobs: {},
1721
+ data_blobs: {},
1511
1722
  unsafe: [],
1512
1723
  },
1513
1724
  modules: [],
@@ -1524,7 +1735,7 @@ export async function main(argv: string[]): Promise<void> {
1524
1735
  return (
1525
1736
  typeof e === "object" &&
1526
1737
  e !== null &&
1527
- (e as { code: 10007 }).code === 10007
1738
+ (e as { code: number }).code === 10007
1528
1739
  );
1529
1740
  }
1530
1741
 
@@ -1541,13 +1752,14 @@ export async function main(argv: string[]): Promise<void> {
1541
1752
  }
1542
1753
  }
1543
1754
 
1544
- console.log(`✨ Success! Uploaded secret ${args.key}`);
1755
+ logger.log(`✨ Success! Uploaded secret ${args.key}`);
1545
1756
  }
1546
1757
  )
1547
1758
  .command(
1548
1759
  "delete <key>",
1549
1760
  "Delete a secret variable from a script",
1550
- (yargs) => {
1761
+ async (yargs) => {
1762
+ await printWranglerBanner();
1551
1763
  return yargs
1552
1764
  .positional("key", {
1553
1765
  describe: "The variable name to be accessible in the script",
@@ -1556,17 +1768,20 @@ export async function main(argv: string[]): Promise<void> {
1556
1768
  .option("name", {
1557
1769
  describe: "Name of the worker",
1558
1770
  type: "string",
1771
+ requiresArg: true,
1559
1772
  })
1560
1773
  .option("env", {
1561
1774
  type: "string",
1775
+ requiresArg: true,
1562
1776
  describe:
1563
1777
  "Binds the secret to the Worker of the specific environment",
1778
+ alias: "e",
1564
1779
  });
1565
1780
  },
1566
1781
  async (args) => {
1567
- const config = readConfig(args.config as ConfigPath);
1782
+ const config = readConfig(args.config as ConfigPath, args);
1568
1783
 
1569
- const scriptName = getScriptName(args, config);
1784
+ const scriptName = getLegacyScriptName(args, config);
1570
1785
  if (!scriptName) {
1571
1786
  throw new Error("Missing script name");
1572
1787
  }
@@ -1578,23 +1793,23 @@ export async function main(argv: string[]): Promise<void> {
1578
1793
  `Are you sure you want to permanently delete the variable ${
1579
1794
  args.key
1580
1795
  } on the script ${scriptName}${
1581
- args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1796
+ args.env && !isLegacyEnv(config) ? ` (${args.env})` : ""
1582
1797
  }?`
1583
1798
  )
1584
1799
  ) {
1585
- console.log(
1800
+ logger.log(
1586
1801
  `🌀 Deleting the secret ${args.key} on script ${scriptName}${
1587
- args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1802
+ args.env && !isLegacyEnv(config) ? ` (${args.env})` : ""
1588
1803
  }`
1589
1804
  );
1590
1805
 
1591
1806
  const url =
1592
- !args.env || isLegacyEnv(args, config)
1807
+ !args.env || isLegacyEnv(config)
1593
1808
  ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
1594
1809
  : `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1595
1810
 
1596
1811
  await fetchResult(`${url}/${args.key}`, { method: "DELETE" });
1597
- console.log(`✨ Success! Deleted secret ${args.key}`);
1812
+ logger.log(`✨ Success! Deleted secret ${args.key}`);
1598
1813
  }
1599
1814
  }
1600
1815
  )
@@ -1606,17 +1821,20 @@ export async function main(argv: string[]): Promise<void> {
1606
1821
  .option("name", {
1607
1822
  describe: "Name of the worker",
1608
1823
  type: "string",
1824
+ requiresArg: true,
1609
1825
  })
1610
1826
  .option("env", {
1611
1827
  type: "string",
1828
+ requiresArg: true,
1612
1829
  describe:
1613
1830
  "Binds the secret to the Worker of the specific environment.",
1831
+ alias: "e",
1614
1832
  });
1615
1833
  },
1616
1834
  async (args) => {
1617
- const config = readConfig(args.config as ConfigPath);
1835
+ const config = readConfig(args.config as ConfigPath, args);
1618
1836
 
1619
- const scriptName = getScriptName(args, config);
1837
+ const scriptName = getLegacyScriptName(args, config);
1620
1838
  if (!scriptName) {
1621
1839
  throw new Error("Missing script name");
1622
1840
  }
@@ -1624,11 +1842,11 @@ export async function main(argv: string[]): Promise<void> {
1624
1842
  const accountId = await requireAuth(config);
1625
1843
 
1626
1844
  const url =
1627
- !args.env || isLegacyEnv(args, config)
1845
+ !args.env || isLegacyEnv(config)
1628
1846
  ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
1629
1847
  : `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1630
1848
 
1631
- console.log(JSON.stringify(await fetchResult(url), null, " "));
1849
+ logger.log(JSON.stringify(await fetchResult(url), null, " "));
1632
1850
  }
1633
1851
  );
1634
1852
  }
@@ -1654,7 +1872,9 @@ export async function main(argv: string[]): Promise<void> {
1654
1872
  })
1655
1873
  .option("env", {
1656
1874
  type: "string",
1875
+ requiresArg: true,
1657
1876
  describe: "Perform on a specific environment",
1877
+ alias: "e",
1658
1878
  })
1659
1879
  .option("preview", {
1660
1880
  type: "boolean",
@@ -1662,12 +1882,7 @@ export async function main(argv: string[]): Promise<void> {
1662
1882
  });
1663
1883
  },
1664
1884
  async (args) => {
1665
- if (args._.length > 2) {
1666
- const extraArgs = args._.slice(2).join(" ");
1667
- throw new CommandLineArgsError(
1668
- `Unexpected additional positional arguments "${extraArgs}".`
1669
- );
1670
- }
1885
+ await printWranglerBanner();
1671
1886
 
1672
1887
  if (!isValidNamespaceBinding(args.namespace)) {
1673
1888
  throw new CommandLineArgsError(
@@ -1675,9 +1890,9 @@ export async function main(argv: string[]): Promise<void> {
1675
1890
  );
1676
1891
  }
1677
1892
 
1678
- const config = readConfig(args.config as ConfigPath);
1893
+ const config = readConfig(args.config as ConfigPath, args);
1679
1894
  if (!config.name) {
1680
- console.warn(
1895
+ logger.warn(
1681
1896
  "No configured name present, using `worker` as a prefix for the title"
1682
1897
  );
1683
1898
  }
@@ -1691,16 +1906,16 @@ export async function main(argv: string[]): Promise<void> {
1691
1906
 
1692
1907
  // TODO: generate a binding name stripping non alphanumeric chars
1693
1908
 
1694
- console.log(`🌀 Creating namespace with title "${title}"`);
1909
+ logger.log(`🌀 Creating namespace with title "${title}"`);
1695
1910
  const namespaceId = await createNamespace(accountId, title);
1696
1911
 
1697
- console.log("✨ Success!");
1912
+ logger.log("✨ Success!");
1698
1913
  const envString = args.env ? ` under [env.${args.env}]` : "";
1699
1914
  const previewString = args.preview ? "preview_" : "";
1700
- console.log(
1915
+ logger.log(
1701
1916
  `Add the following to your configuration file in your kv_namespaces array${envString}:`
1702
1917
  );
1703
- console.log(
1918
+ logger.log(
1704
1919
  `{ binding = "${args.namespace}", ${previewString}id = "${namespaceId}" }`
1705
1920
  );
1706
1921
 
@@ -1712,13 +1927,13 @@ export async function main(argv: string[]): Promise<void> {
1712
1927
  "Outputs a list of all KV namespaces associated with your account id.",
1713
1928
  {},
1714
1929
  async (args) => {
1715
- const config = readConfig(args.config as ConfigPath);
1930
+ const config = readConfig(args.config as ConfigPath, args);
1716
1931
 
1717
1932
  const accountId = await requireAuth(config);
1718
1933
 
1719
1934
  // TODO: we should show bindings if they exist for given ids
1720
1935
 
1721
- console.log(
1936
+ logger.log(
1722
1937
  JSON.stringify(await listNamespaces(accountId), null, " ")
1723
1938
  );
1724
1939
  }
@@ -1730,16 +1945,20 @@ export async function main(argv: string[]): Promise<void> {
1730
1945
  return yargs
1731
1946
  .option("binding", {
1732
1947
  type: "string",
1948
+ requiresArg: true,
1733
1949
  describe: "The name of the namespace to delete",
1734
1950
  })
1735
1951
  .option("namespace-id", {
1736
1952
  type: "string",
1953
+ requiresArg: true,
1737
1954
  describe: "The id of the namespace to delete",
1738
1955
  })
1739
1956
  .check(demandOneOfOption("binding", "namespace-id"))
1740
1957
  .option("env", {
1741
1958
  type: "string",
1959
+ requiresArg: true,
1742
1960
  describe: "Perform on a specific environment",
1961
+ alias: "e",
1743
1962
  })
1744
1963
  .option("preview", {
1745
1964
  type: "boolean",
@@ -1747,7 +1966,8 @@ export async function main(argv: string[]): Promise<void> {
1747
1966
  });
1748
1967
  },
1749
1968
  async (args) => {
1750
- const config = readConfig(args.config as ConfigPath);
1969
+ await printWranglerBanner();
1970
+ const config = readConfig(args.config as ConfigPath, args);
1751
1971
 
1752
1972
  let id;
1753
1973
  try {
@@ -1810,16 +2030,20 @@ export async function main(argv: string[]): Promise<void> {
1810
2030
  })
1811
2031
  .option("binding", {
1812
2032
  type: "string",
2033
+ requiresArg: true,
1813
2034
  describe: "The binding of the namespace to write to",
1814
2035
  })
1815
2036
  .option("namespace-id", {
1816
2037
  type: "string",
2038
+ requiresArg: true,
1817
2039
  describe: "The id of the namespace to write to",
1818
2040
  })
1819
2041
  .check(demandOneOfOption("binding", "namespace-id"))
1820
2042
  .option("env", {
1821
2043
  type: "string",
2044
+ requiresArg: true,
1822
2045
  describe: "Perform on a specific environment",
2046
+ alias: "e",
1823
2047
  })
1824
2048
  .option("preview", {
1825
2049
  type: "boolean",
@@ -1836,12 +2060,14 @@ export async function main(argv: string[]): Promise<void> {
1836
2060
  })
1837
2061
  .option("path", {
1838
2062
  type: "string",
2063
+ requiresArg: true,
1839
2064
  describe: "Read value from the file at a given path",
1840
2065
  })
1841
2066
  .check(demandOneOfOption("value", "path"));
1842
2067
  },
1843
2068
  async ({ key, ttl, expiration, ...args }) => {
1844
- const config = readConfig(args.config as ConfigPath);
2069
+ await printWranglerBanner();
2070
+ const config = readConfig(args.config as ConfigPath, args);
1845
2071
  const namespaceId = getNamespaceId(args, config);
1846
2072
  // One of `args.path` and `args.value` must be defined
1847
2073
  const value = args.path
@@ -1850,12 +2076,12 @@ export async function main(argv: string[]): Promise<void> {
1850
2076
  args.value!;
1851
2077
 
1852
2078
  if (args.path) {
1853
- console.log(
1854
- `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}.`
1855
2081
  );
1856
2082
  } else {
1857
- console.log(
1858
- `writing the value "${value}" to key "${key}" on namespace ${namespaceId}`
2083
+ logger.log(
2084
+ `Writing the value "${value}" to key "${key}" on namespace ${namespaceId}.`
1859
2085
  );
1860
2086
  }
1861
2087
 
@@ -1876,16 +2102,20 @@ export async function main(argv: string[]): Promise<void> {
1876
2102
  return yargs
1877
2103
  .option("binding", {
1878
2104
  type: "string",
2105
+ requiresArg: true,
1879
2106
  describe: "The name of the namespace to list",
1880
2107
  })
1881
2108
  .option("namespace-id", {
1882
2109
  type: "string",
2110
+ requiresArg: true,
1883
2111
  describe: "The id of the namespace to list",
1884
2112
  })
1885
2113
  .check(demandOneOfOption("binding", "namespace-id"))
1886
2114
  .option("env", {
1887
2115
  type: "string",
2116
+ requiresArg: true,
1888
2117
  describe: "Perform on a specific environment",
2118
+ alias: "e",
1889
2119
  })
1890
2120
  .option("preview", {
1891
2121
  type: "boolean",
@@ -1895,12 +2125,13 @@ export async function main(argv: string[]): Promise<void> {
1895
2125
  })
1896
2126
  .option("prefix", {
1897
2127
  type: "string",
2128
+ requiresArg: true,
1898
2129
  describe: "A prefix to filter listed keys",
1899
2130
  });
1900
2131
  },
1901
2132
  async ({ prefix, ...args }) => {
1902
2133
  // TODO: support for limit+cursor (pagination)
1903
- const config = readConfig(args.config as ConfigPath);
2134
+ const config = readConfig(args.config as ConfigPath, args);
1904
2135
  const namespaceId = getNamespaceId(args, config);
1905
2136
 
1906
2137
  const accountId = await requireAuth(config);
@@ -1910,7 +2141,7 @@ export async function main(argv: string[]): Promise<void> {
1910
2141
  namespaceId,
1911
2142
  prefix
1912
2143
  );
1913
- console.log(JSON.stringify(results, undefined, 2));
2144
+ logger.log(JSON.stringify(results, undefined, 2));
1914
2145
  }
1915
2146
  )
1916
2147
  .command(
@@ -1925,16 +2156,20 @@ export async function main(argv: string[]): Promise<void> {
1925
2156
  })
1926
2157
  .option("binding", {
1927
2158
  type: "string",
2159
+ requiresArg: true,
1928
2160
  describe: "The name of the namespace to get from",
1929
2161
  })
1930
2162
  .option("namespace-id", {
1931
2163
  type: "string",
2164
+ requiresArg: true,
1932
2165
  describe: "The id of the namespace to get from",
1933
2166
  })
1934
2167
  .check(demandOneOfOption("binding", "namespace-id"))
1935
2168
  .option("env", {
1936
2169
  type: "string",
2170
+ requiresArg: true,
1937
2171
  describe: "Perform on a specific environment",
2172
+ alias: "e",
1938
2173
  })
1939
2174
  .option("preview", {
1940
2175
  type: "boolean",
@@ -1948,12 +2183,12 @@ export async function main(argv: string[]): Promise<void> {
1948
2183
  });
1949
2184
  },
1950
2185
  async ({ key, ...args }) => {
1951
- const config = readConfig(args.config as ConfigPath);
2186
+ const config = readConfig(args.config as ConfigPath, args);
1952
2187
  const namespaceId = getNamespaceId(args, config);
1953
2188
 
1954
2189
  const accountId = await requireAuth(config);
1955
2190
 
1956
- console.log(await getKeyValue(accountId, namespaceId, key));
2191
+ logger.log(await getKeyValue(accountId, namespaceId, key));
1957
2192
  }
1958
2193
  )
1959
2194
  .command(
@@ -1968,16 +2203,20 @@ export async function main(argv: string[]): Promise<void> {
1968
2203
  })
1969
2204
  .option("binding", {
1970
2205
  type: "string",
2206
+ requiresArg: true,
1971
2207
  describe: "The name of the namespace to delete from",
1972
2208
  })
1973
2209
  .option("namespace-id", {
1974
2210
  type: "string",
2211
+ requiresArg: true,
1975
2212
  describe: "The id of the namespace to delete from",
1976
2213
  })
1977
2214
  .check(demandOneOfOption("binding", "namespace-id"))
1978
2215
  .option("env", {
1979
2216
  type: "string",
2217
+ requiresArg: true,
1980
2218
  describe: "Perform on a specific environment",
2219
+ alias: "e",
1981
2220
  })
1982
2221
  .option("preview", {
1983
2222
  type: "boolean",
@@ -1985,11 +2224,12 @@ export async function main(argv: string[]): Promise<void> {
1985
2224
  });
1986
2225
  },
1987
2226
  async ({ key, ...args }) => {
1988
- const config = readConfig(args.config as ConfigPath);
2227
+ await printWranglerBanner();
2228
+ const config = readConfig(args.config as ConfigPath, args);
1989
2229
  const namespaceId = getNamespaceId(args, config);
1990
2230
 
1991
- console.log(
1992
- `deleting the key "${key}" on namespace ${namespaceId}`
2231
+ logger.log(
2232
+ `Deleting the key "${key}" on namespace ${namespaceId}.`
1993
2233
  );
1994
2234
 
1995
2235
  const accountId = await requireAuth(config);
@@ -2022,16 +2262,20 @@ export async function main(argv: string[]): Promise<void> {
2022
2262
  })
2023
2263
  .option("binding", {
2024
2264
  type: "string",
2265
+ requiresArg: true,
2025
2266
  describe: "The name of the namespace to insert values into",
2026
2267
  })
2027
2268
  .option("namespace-id", {
2028
2269
  type: "string",
2270
+ requiresArg: true,
2029
2271
  describe: "The id of the namespace to insert values into",
2030
2272
  })
2031
2273
  .check(demandOneOfOption("binding", "namespace-id"))
2032
2274
  .option("env", {
2033
2275
  type: "string",
2276
+ requiresArg: true,
2034
2277
  describe: "Perform on a specific environment",
2278
+ alias: "e",
2035
2279
  })
2036
2280
  .option("preview", {
2037
2281
  type: "boolean",
@@ -2039,11 +2283,12 @@ export async function main(argv: string[]): Promise<void> {
2039
2283
  });
2040
2284
  },
2041
2285
  async ({ filename, ...args }) => {
2286
+ await printWranglerBanner();
2042
2287
  // The simplest implementation I could think of.
2043
2288
  // This could be made more efficient with a streaming parser/uploader
2044
2289
  // but we'll do that in the future if needed.
2045
2290
 
2046
- const config = readConfig(args.config as ConfigPath);
2291
+ const config = readConfig(args.config as ConfigPath, args);
2047
2292
  const namespaceId = getNamespaceId(args, config);
2048
2293
  const content = parseJSON(readFileSync(filename), filename);
2049
2294
 
@@ -2080,7 +2325,7 @@ export async function main(argv: string[]): Promise<void> {
2080
2325
  }
2081
2326
  }
2082
2327
  if (warnings.length > 0) {
2083
- console.warn(
2328
+ logger.warn(
2084
2329
  `Unexpected key-value properties in "${filename}".\n` +
2085
2330
  warnings.join("\n")
2086
2331
  );
@@ -2107,11 +2352,11 @@ export async function main(argv: string[]): Promise<void> {
2107
2352
  namespaceId,
2108
2353
  content,
2109
2354
  (index, total) => {
2110
- console.log(`Uploaded ${index} of ${total}.`);
2355
+ logger.log(`Uploaded ${index} of ${total}.`);
2111
2356
  }
2112
2357
  );
2113
2358
 
2114
- console.log("Success!");
2359
+ logger.log("Success!");
2115
2360
  }
2116
2361
  )
2117
2362
  .command(
@@ -2126,16 +2371,20 @@ export async function main(argv: string[]): Promise<void> {
2126
2371
  })
2127
2372
  .option("binding", {
2128
2373
  type: "string",
2374
+ requiresArg: true,
2129
2375
  describe: "The name of the namespace to delete from",
2130
2376
  })
2131
2377
  .option("namespace-id", {
2132
2378
  type: "string",
2379
+ requiresArg: true,
2133
2380
  describe: "The id of the namespace to delete from",
2134
2381
  })
2135
2382
  .check(demandOneOfOption("binding", "namespace-id"))
2136
2383
  .option("env", {
2137
2384
  type: "string",
2385
+ requiresArg: true,
2138
2386
  describe: "Perform on a specific environment",
2387
+ alias: "e",
2139
2388
  })
2140
2389
  .option("preview", {
2141
2390
  type: "boolean",
@@ -2148,7 +2397,8 @@ export async function main(argv: string[]): Promise<void> {
2148
2397
  });
2149
2398
  },
2150
2399
  async ({ filename, ...args }) => {
2151
- const config = readConfig(args.config as ConfigPath);
2400
+ await printWranglerBanner();
2401
+ const config = readConfig(args.config as ConfigPath, args);
2152
2402
  const namespaceId = getNamespaceId(args, config);
2153
2403
 
2154
2404
  if (!args.force) {
@@ -2156,7 +2406,7 @@ export async function main(argv: string[]): Promise<void> {
2156
2406
  `Are you sure you want to delete all the keys read from "${filename}" from kv-namespace with id "${namespaceId}"?`
2157
2407
  );
2158
2408
  if (!result) {
2159
- console.log(`Not deleting keys read from "${filename}".`);
2409
+ logger.log(`Not deleting keys read from "${filename}".`);
2160
2410
  return;
2161
2411
  }
2162
2412
  }
@@ -2199,18 +2449,22 @@ export async function main(argv: string[]): Promise<void> {
2199
2449
  namespaceId,
2200
2450
  content,
2201
2451
  (index, total) => {
2202
- console.log(`Deleted ${index} of ${total}.`);
2452
+ logger.log(`Deleted ${index} of ${total}.`);
2203
2453
  }
2204
2454
  );
2205
2455
 
2206
- console.log("Success!");
2456
+ logger.log("Success!");
2207
2457
  }
2208
2458
  );
2209
2459
  }
2210
2460
  );
2211
2461
 
2212
- wrangler.command("pages", "⚡️ Configure Cloudflare Pages", (pagesYargs) =>
2213
- 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
+ }
2214
2468
  );
2215
2469
 
2216
2470
  wrangler.command("r2", "📦 Interact with an R2 store", (r2Yargs) => {
@@ -2228,30 +2482,24 @@ export async function main(argv: string[]): Promise<void> {
2228
2482
  });
2229
2483
  },
2230
2484
  async (args) => {
2231
- // We expect three values in `_`: `r2`, `bucket`, `create`.
2232
- if (args._.length > 3) {
2233
- const extraArgs = args._.slice(3).join(" ");
2234
- throw new CommandLineArgsError(
2235
- `Unexpected additional positional arguments "${extraArgs}".`
2236
- );
2237
- }
2485
+ await printWranglerBanner();
2238
2486
 
2239
- const config = readConfig(args.config as ConfigPath);
2487
+ const config = readConfig(args.config as ConfigPath, args);
2240
2488
 
2241
2489
  const accountId = await requireAuth(config);
2242
2490
 
2243
- console.log(`Creating bucket ${args.name}.`);
2491
+ logger.log(`Creating bucket ${args.name}.`);
2244
2492
  await createR2Bucket(accountId, args.name);
2245
- console.log(`Created bucket ${args.name}.`);
2493
+ logger.log(`Created bucket ${args.name}.`);
2246
2494
  }
2247
2495
  );
2248
2496
 
2249
2497
  r2BucketYargs.command("list", "List R2 buckets", {}, async (args) => {
2250
- const config = readConfig(args.config as ConfigPath);
2498
+ const config = readConfig(args.config as ConfigPath, args);
2251
2499
 
2252
2500
  const accountId = await requireAuth(config);
2253
2501
 
2254
- console.log(JSON.stringify(await listR2Buckets(accountId), null, 2));
2502
+ logger.log(JSON.stringify(await listR2Buckets(accountId), null, 2));
2255
2503
  });
2256
2504
 
2257
2505
  r2BucketYargs.command(
@@ -2265,27 +2513,97 @@ export async function main(argv: string[]): Promise<void> {
2265
2513
  });
2266
2514
  },
2267
2515
  async (args) => {
2268
- // We expect three values in `_`: `r2`, `bucket`, `delete`.
2269
- if (args._.length > 3) {
2270
- const extraArgs = args._.slice(3).join(" ");
2271
- throw new CommandLineArgsError(
2272
- `Unexpected additional positional arguments "${extraArgs}".`
2273
- );
2274
- }
2516
+ await printWranglerBanner();
2275
2517
 
2276
- const config = readConfig(args.config as ConfigPath);
2518
+ const config = readConfig(args.config as ConfigPath, args);
2277
2519
 
2278
2520
  const accountId = await requireAuth(config);
2279
2521
 
2280
- console.log(`Deleting bucket ${args.name}.`);
2522
+ logger.log(`Deleting bucket ${args.name}.`);
2281
2523
  await deleteR2Bucket(accountId, args.name);
2282
- console.log(`Deleted bucket ${args.name}.`);
2524
+ logger.log(`Deleted bucket ${args.name}.`);
2283
2525
  }
2284
2526
  );
2285
2527
  return r2BucketYargs;
2286
2528
  });
2287
2529
  });
2288
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
+
2289
2607
  wrangler
2290
2608
  .option("legacy-env", {
2291
2609
  type: "boolean",
@@ -2295,6 +2613,7 @@ export async function main(argv: string[]): Promise<void> {
2295
2613
  alias: "c",
2296
2614
  describe: "Path to .toml configuration file",
2297
2615
  type: "string",
2616
+ requiresArg: true,
2298
2617
  });
2299
2618
 
2300
2619
  wrangler.group(["config", "help", "version", "legacy-env"], "Flags:");
@@ -2303,23 +2622,21 @@ export async function main(argv: string[]): Promise<void> {
2303
2622
  wrangler.exitProcess(false);
2304
2623
 
2305
2624
  try {
2306
- await initialiseUserConfig();
2307
2625
  await wrangler.parse();
2308
2626
  } catch (e) {
2627
+ logger.log(""); // Just adds a bit of space
2309
2628
  if (e instanceof CommandLineArgsError) {
2310
2629
  wrangler.showHelp("error");
2311
- console.error(""); // Just adds a bit of space
2312
- console.error(e.message);
2630
+ logger.log(""); // Add a bit of space.
2631
+ logger.error(e.message);
2313
2632
  } else if (e instanceof ParseError) {
2314
- console.error("");
2315
2633
  e.notes.push({
2316
2634
  text: "\nIf you think this is a bug, please open an issue at: https://github.com/cloudflare/wrangler2/issues/new",
2317
2635
  });
2318
- console.error(formatMessage(e));
2636
+ logger.error(formatMessage(e));
2319
2637
  } else {
2320
- console.error(e instanceof Error ? e.message : e);
2321
- console.error(""); // Just adds a bit of space
2322
- console.error(
2638
+ logger.error(e instanceof Error ? e.message : e);
2639
+ logger.log(
2323
2640
  `${fgGreenColor}%s${resetColor}`,
2324
2641
  "If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
2325
2642
  );
@@ -2327,3 +2644,25 @@ export async function main(argv: string[]): Promise<void> {
2327
2644
  throw e;
2328
2645
  }
2329
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
+ }