wrangler 0.0.0-e2f9bb2 → 0.0.0-e3278fa

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 (90) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +3 -1
  3. package/bin/wrangler.js +36 -4
  4. package/package.json +12 -7
  5. package/pages/functions/buildPlugin.ts +13 -0
  6. package/pages/functions/buildWorker.ts +14 -1
  7. package/pages/functions/template-plugin.ts +3 -2
  8. package/src/__tests__/configuration.test.ts +370 -87
  9. package/src/__tests__/dev.test.tsx +298 -77
  10. package/src/__tests__/helpers/mock-dialogs.ts +43 -2
  11. package/src/__tests__/index.test.ts +79 -72
  12. package/src/__tests__/init.test.ts +718 -123
  13. package/src/__tests__/jest.setup.ts +8 -0
  14. package/src/__tests__/kv.test.ts +173 -146
  15. package/src/__tests__/package-manager.test.ts +154 -7
  16. package/src/__tests__/pages.test.ts +478 -53
  17. package/src/__tests__/parse.test.ts +8 -0
  18. package/src/__tests__/publish.test.ts +1094 -215
  19. package/src/__tests__/r2.test.ts +55 -36
  20. package/src/__tests__/secret.test.ts +39 -4
  21. package/src/__tests__/whoami.test.tsx +34 -0
  22. package/src/abort.d.ts +3 -0
  23. package/src/bundle.ts +34 -6
  24. package/src/cfetch/index.ts +21 -4
  25. package/src/cfetch/internal.ts +23 -18
  26. package/src/config/config.ts +2 -2
  27. package/src/config/environment.ts +40 -14
  28. package/src/config/index.ts +162 -0
  29. package/src/config/validation-helpers.ts +10 -1
  30. package/src/config/validation.ts +180 -65
  31. package/src/create-worker-preview.ts +32 -22
  32. package/src/create-worker-upload-form.ts +22 -8
  33. package/src/dev/dev.tsx +12 -18
  34. package/src/dev/local.tsx +10 -0
  35. package/src/dev/remote.tsx +40 -16
  36. package/src/dialogs.tsx +48 -0
  37. package/src/durable.ts +102 -0
  38. package/src/entry.ts +64 -9
  39. package/src/index.tsx +548 -317
  40. package/src/inspect.ts +39 -0
  41. package/src/kv.ts +114 -25
  42. package/src/open-in-browser.ts +5 -12
  43. package/src/package-manager.ts +50 -3
  44. package/src/pages.tsx +434 -207
  45. package/src/parse.ts +23 -4
  46. package/src/proxy.ts +59 -35
  47. package/src/publish.ts +374 -127
  48. package/src/sites.tsx +61 -28
  49. package/src/user.tsx +23 -2
  50. package/src/whoami.tsx +3 -2
  51. package/src/worker.ts +10 -1
  52. package/src/zones.ts +73 -0
  53. package/templates/new-worker-scheduled.js +17 -0
  54. package/templates/new-worker-scheduled.ts +32 -0
  55. package/templates/new-worker.ts +16 -1
  56. package/templates/static-asset-facade.js +0 -4
  57. package/wrangler-dist/cli.js +96965 -83414
  58. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  59. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  60. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  61. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  62. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  63. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  64. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  65. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  66. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  67. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  68. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  69. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  70. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  71. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  72. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  73. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  74. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  75. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  76. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  77. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  78. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  79. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  80. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  81. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  82. package/vendor/wrangler-mime/LICENSE +0 -21
  83. package/vendor/wrangler-mime/Mime.js +0 -97
  84. package/vendor/wrangler-mime/README.md +0 -187
  85. package/vendor/wrangler-mime/cli.js +0 -46
  86. package/vendor/wrangler-mime/index.js +0 -4
  87. package/vendor/wrangler-mime/lite.js +0 -4
  88. package/vendor/wrangler-mime/package.json +0 -52
  89. package/vendor/wrangler-mime/types/other.js +0 -1
  90. package/vendor/wrangler-mime/types/standard.js +0 -1
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 { watch } from "chokidar";
7
8
  import { execa } from "execa";
8
9
  import { findUp } from "find-up";
9
10
  import getPort from "get-port";
@@ -11,28 +12,31 @@ import { render } from "ink";
11
12
  import React from "react";
12
13
  import onExit from "signal-exit";
13
14
  import supportsColor from "supports-color";
15
+ import { setGlobalDispatcher, ProxyAgent } from "undici";
14
16
  import makeCLI from "yargs";
15
17
  import { version as wranglerVersion } from "../package.json";
16
18
  import { fetchResult } from "./cfetch";
17
- import { findWranglerToml, readConfig } from "./config";
19
+ import { findWranglerToml, printBindings, readConfig } from "./config";
18
20
  import { createWorkerUploadForm } from "./create-worker-upload-form";
19
21
  import Dev from "./dev/dev";
20
22
  import { getVarsForDev } from "./dev/dev-vars";
21
- import { confirm, prompt } from "./dialogs";
23
+ import { confirm, prompt, select } from "./dialogs";
22
24
  import { getEntry } from "./entry";
23
25
  import { DeprecationError } from "./errors";
24
26
  import {
25
- getNamespaceId,
26
- listNamespaces,
27
- listNamespaceKeys,
28
- putKeyValue,
29
- putBulkKeyValue,
30
- deleteBulkKeyValue,
31
- createNamespace,
32
- isValidNamespaceBinding,
33
- getKeyValue,
34
- isKeyValue,
35
- unexpectedKeyValueProps,
27
+ getKVNamespaceId,
28
+ listKVNamespaces,
29
+ listKVNamespaceKeys,
30
+ putKVKeyValue,
31
+ putKVBulkKeyValue,
32
+ deleteKVBulkKeyValue,
33
+ createKVNamespace,
34
+ isValidKVNamespaceBinding,
35
+ getKVKeyValue,
36
+ isKVKeyValue,
37
+ unexpectedKVKeyValueProps,
38
+ deleteKVNamespace,
39
+ deleteKVKeyValue,
36
40
  } from "./kv";
37
41
  import { logger } from "./logger";
38
42
  import { getPackageManager } from "./package-manager";
@@ -41,6 +45,7 @@ import {
41
45
  formatMessage,
42
46
  ParseError,
43
47
  parseJSON,
48
+ parsePackageJSON,
44
49
  parseTOML,
45
50
  readFileSync,
46
51
  } from "./parse";
@@ -62,6 +67,7 @@ import {
62
67
  requireAuth,
63
68
  } from "./user";
64
69
  import { whoami } from "./whoami";
70
+ import { getZoneIdFromHost, getZoneForRoute } from "./zones";
65
71
 
66
72
  import type { Config } from "./config";
67
73
  import type { TailCLIFilters } from "./tail";
@@ -73,6 +79,18 @@ type ConfigPath = string | undefined;
73
79
 
74
80
  const resetColor = "\x1b[0m";
75
81
  const fgGreenColor = "\x1b[32m";
82
+ const DEFAULT_LOCAL_PORT = 8787;
83
+
84
+ const proxy =
85
+ process.env.https_proxy ||
86
+ process.env.HTTPS_PROXY ||
87
+ process.env.http_proxy ||
88
+ process.env.HTTP_PROXY ||
89
+ undefined;
90
+
91
+ if (proxy) {
92
+ setGlobalDispatcher(new ProxyAgent(proxy));
93
+ }
76
94
 
77
95
  function getRules(config: Config): Config["rules"] {
78
96
  const rules = config.rules ?? config.build?.upload?.rules ?? [];
@@ -203,9 +221,17 @@ function demandOneOfOption(...options: string[]) {
203
221
  };
204
222
  }
205
223
 
224
+ /**
225
+ * Remove trailing white space from inputs.
226
+ * Matching Wrangler legacy behavior with handling inputs
227
+ */
228
+ function trimTrailingWhitespace(str: string) {
229
+ return str.trimEnd();
230
+ }
231
+
206
232
  class CommandLineArgsError extends Error {}
207
233
 
208
- export async function main(argv: string[]): Promise<void> {
234
+ function createCLIParser(argv: string[]) {
209
235
  const wrangler = makeCLI(argv)
210
236
  .strict()
211
237
  // We handle errors ourselves in a try-catch around `yargs.parse`.
@@ -322,6 +348,9 @@ export async function main(argv: string[]): Promise<void> {
322
348
  throw new CommandLineArgsError(message);
323
349
  }
324
350
 
351
+ const devDepsToInstall: string[] = [];
352
+ const instructions: string[] = [];
353
+ let shouldRunPackageManagerInstall = false;
325
354
  const creationDirectory = path.resolve(process.cwd(), args.name ?? "");
326
355
 
327
356
  if (args.site) {
@@ -359,7 +388,12 @@ export async function main(argv: string[]): Promise<void> {
359
388
  let justCreatedWranglerToml = false;
360
389
 
361
390
  if (fs.existsSync(wranglerTomlDestination)) {
362
- logger.warn(`${wranglerTomlDestination} file already exists!`);
391
+ logger.warn(
392
+ `${path.relative(
393
+ process.cwd(),
394
+ wranglerTomlDestination
395
+ )} already exists!`
396
+ );
363
397
  const shouldContinue = await confirm(
364
398
  "Do you want to continue initializing this project?"
365
399
  );
@@ -379,34 +413,63 @@ export async function main(argv: string[]): Promise<void> {
379
413
  }) + "\n"
380
414
  );
381
415
 
382
- logger.log(`✨ Successfully created wrangler.toml`);
416
+ logger.log(
417
+ `✨ Created ${path.relative(
418
+ process.cwd(),
419
+ wranglerTomlDestination
420
+ )}`
421
+ );
383
422
  justCreatedWranglerToml = true;
384
423
  } catch (err) {
385
424
  throw new Error(
386
- `Failed to create wrangler.toml.\n${(err as Error).message ?? err}`
425
+ `Failed to create ${path.relative(
426
+ process.cwd(),
427
+ wranglerTomlDestination
428
+ )}.\n${(err as Error).message ?? err}`
387
429
  );
388
430
  }
389
431
  }
390
432
 
391
433
  const yesFlag = args.yes ?? false;
392
434
 
393
- const isInsideGitProject = Boolean(await findUp(".git"));
394
- const isGitInstalled = (await execa("git", ["--version"])).exitCode === 0;
435
+ const isInsideGitProject = Boolean(
436
+ await findUp(".git", { cwd: creationDirectory, type: "directory" })
437
+ );
438
+ let isGitInstalled;
439
+ try {
440
+ isGitInstalled = (await execa("git", ["--version"])).exitCode === 0;
441
+ } catch (err) {
442
+ isGitInstalled = false;
443
+ }
395
444
  if (!isInsideGitProject && isGitInstalled) {
396
445
  const shouldInitGit =
397
446
  yesFlag ||
398
447
  (await confirm("Would you like to use git to manage this Worker?"));
399
448
  if (shouldInitGit) {
400
- await execa("git", ["init"], { cwd: creationDirectory });
449
+ await execa("git", ["init"], {
450
+ cwd: creationDirectory,
451
+ });
452
+ await execa("git", ["branch", "-m", "main"], {
453
+ cwd: creationDirectory,
454
+ });
401
455
  await writeFile(
402
456
  path.join(creationDirectory, ".gitignore"),
403
457
  readFileSync(path.join(__dirname, "../templates/gitignore"))
404
458
  );
405
- logger.log(`✨ Initialized git repository`);
459
+ logger.log(
460
+ args.name && args.name !== "."
461
+ ? `✨ Initialized git repository at ${path.relative(
462
+ process.cwd(),
463
+ creationDirectory
464
+ )}`
465
+ : `✨ Initialized git repository`
466
+ );
406
467
  }
407
468
  }
408
469
 
409
- let pathToPackageJson = await findUp("package.json");
470
+ let pathToPackageJson = await findUp("package.json", {
471
+ cwd: creationDirectory,
472
+ });
410
473
  let shouldCreatePackageJson = false;
411
474
 
412
475
  if (!pathToPackageJson) {
@@ -434,16 +497,18 @@ export async function main(argv: string[]): Promise<void> {
434
497
  ) + "\n"
435
498
  );
436
499
 
437
- await packageManager.install();
438
- logger.log(`✨ Created package.json`);
500
+ shouldRunPackageManagerInstall = true;
439
501
  pathToPackageJson = path.join(creationDirectory, "package.json");
502
+ logger.log(
503
+ `✨ Created ${path.relative(process.cwd(), pathToPackageJson)}`
504
+ );
440
505
  } else {
441
506
  return;
442
507
  }
443
508
  } else {
444
509
  // If package.json exists and wrangler isn't installed,
445
510
  // then ask to add wrangler to devDependencies
446
- const packageJson = parseJSON(
511
+ const packageJson = parsePackageJSON(
447
512
  readFileSync(pathToPackageJson),
448
513
  pathToPackageJson
449
514
  );
@@ -456,17 +521,21 @@ export async function main(argv: string[]): Promise<void> {
456
521
  const shouldInstall =
457
522
  yesFlag ||
458
523
  (await confirm(
459
- "Would you like to install wrangler into your package.json?"
524
+ `Would you like to install wrangler into ${path.relative(
525
+ process.cwd(),
526
+ pathToPackageJson
527
+ )}?`
460
528
  ));
461
529
  if (shouldInstall) {
462
- await packageManager.addDevDeps(`wrangler@${wranglerVersion}`);
463
- logger.log(`✨ Installed wrangler`);
530
+ devDepsToInstall.push(`wrangler@${wranglerVersion}`);
464
531
  }
465
532
  }
466
533
  }
467
534
 
468
535
  let isTypescriptProject = false;
469
- let pathToTSConfig = await findUp("tsconfig.json");
536
+ let pathToTSConfig = await findUp("tsconfig.json", {
537
+ cwd: creationDirectory,
538
+ });
470
539
  if (!pathToTSConfig) {
471
540
  // If there's no tsconfig, offer to create one
472
541
  // and install @cloudflare/workers-types
@@ -476,21 +545,18 @@ export async function main(argv: string[]): Promise<void> {
476
545
  path.join(creationDirectory, "./tsconfig.json"),
477
546
  readFileSync(path.join(__dirname, "../templates/tsconfig.json"))
478
547
  );
479
- await packageManager.addDevDeps(
480
- "@cloudflare/workers-types",
481
- "typescript"
482
- );
483
-
548
+ devDepsToInstall.push("@cloudflare/workers-types");
549
+ devDepsToInstall.push("typescript");
550
+ pathToTSConfig = path.join(creationDirectory, "tsconfig.json");
484
551
  logger.log(
485
- `✨ Created tsconfig.json, installed @cloudflare/workers-types into devDependencies`
552
+ `✨ Created ${path.relative(process.cwd(), pathToTSConfig)}`
486
553
  );
487
- pathToTSConfig = path.join(creationDirectory, "tsconfig.json");
488
554
  }
489
555
  } else {
490
556
  isTypescriptProject = true;
491
557
  // If there's a tsconfig, check if @cloudflare/workers-types
492
558
  // is already installed, and offer to install it if not
493
- const packageJson = parseJSON(
559
+ const packageJson = parsePackageJSON(
494
560
  readFileSync(pathToPackageJson),
495
561
  pathToPackageJson
496
562
  );
@@ -504,19 +570,22 @@ export async function main(argv: string[]): Promise<void> {
504
570
  "Would you like to install the type definitions for Workers into your package.json?"
505
571
  );
506
572
  if (shouldInstall) {
507
- await packageManager.addDevDeps("@cloudflare/workers-types");
573
+ devDepsToInstall.push("@cloudflare/workers-types");
508
574
  // We don't update the tsconfig.json because
509
575
  // it could be complicated in existing projects
510
576
  // and we don't want to break them. Instead, we simply
511
577
  // tell the user that they need to update their tsconfig.json
512
- logger.log(
513
- `✨ Installed @cloudflare/workers-types.\nPlease add "@cloudflare/workers-types" to compilerOptions.types in your tsconfig.json`
578
+ instructions.push(
579
+ `🚨 Please add "@cloudflare/workers-types" to compilerOptions.types in ${path.relative(
580
+ process.cwd(),
581
+ pathToTSConfig
582
+ )}`
514
583
  );
515
584
  }
516
585
  }
517
586
  }
518
587
 
519
- const packageJsonContent = parseJSON(
588
+ const packageJsonContent = parsePackageJSON(
520
589
  readFileSync(pathToPackageJson),
521
590
  pathToPackageJson
522
591
  );
@@ -525,25 +594,60 @@ export async function main(argv: string[]): Promise<void> {
525
594
  !packageJsonContent.scripts?.publish &&
526
595
  shouldCreatePackageJson;
527
596
 
597
+ /*
598
+ * Passes the array of accumulated devDeps to install through to
599
+ * the package manager. Also generates a human-readable list
600
+ * of packages it installed.
601
+ * If there are no devDeps to install, optionally runs
602
+ * the package manager's install command.
603
+ */
604
+ async function installPackages(
605
+ shouldRunInstall: boolean,
606
+ depsToInstall: string[]
607
+ ) {
608
+ //lets install the devDeps they asked for
609
+ //and run their package manager's install command if needed
610
+ if (depsToInstall.length > 0) {
611
+ const formatter = new Intl.ListFormat("en", {
612
+ style: "long",
613
+ type: "conjunction",
614
+ });
615
+ await packageManager.addDevDeps(...depsToInstall);
616
+ const versionlessPackages = depsToInstall.map((dep) =>
617
+ dep === `wrangler@${wranglerVersion}` ? "wrangler" : dep
618
+ );
619
+
620
+ logger.log(
621
+ `✨ Installed ${formatter.format(
622
+ versionlessPackages
623
+ )} into devDependencies`
624
+ );
625
+ } else {
626
+ if (shouldRunInstall) {
627
+ await packageManager.install();
628
+ }
629
+ }
630
+ }
631
+
528
632
  async function writePackageJsonScriptsAndUpdateWranglerToml(
529
633
  isWritingScripts: boolean,
530
634
  isCreatingWranglerToml: boolean,
531
635
  packagePath: string,
532
- scriptPath: string
636
+ scriptPath: string,
637
+ extraToml: TOML.JsonMap
533
638
  ) {
534
639
  if (isCreatingWranglerToml) {
535
- // rewrite wrangler.toml with main = "path/to/script"
640
+ // rewrite wrangler.toml with main = "path/to/script" and any additional config specified in `extraToml`
536
641
  const parsedWranglerToml = parseTOML(
537
642
  readFileSync(wranglerTomlDestination)
538
643
  );
539
- fs.writeFileSync(
540
- wranglerTomlDestination,
541
- TOML.stringify({
542
- name: parsedWranglerToml.name,
543
- main: scriptPath,
544
- compatibility_date: parsedWranglerToml.compatibility_date,
545
- })
546
- );
644
+ const newToml = {
645
+ name: parsedWranglerToml.name,
646
+ main: scriptPath,
647
+ compatibility_date: parsedWranglerToml.compatibility_date,
648
+ ...extraToml,
649
+ };
650
+ fs.writeFileSync(wranglerTomlDestination, TOML.stringify(newToml));
547
651
  }
548
652
  const isNamedWorker =
549
653
  isCreatingWranglerToml && path.dirname(packagePath) !== process.cwd();
@@ -559,7 +663,7 @@ export async function main(argv: string[]): Promise<void> {
559
663
  start: isCreatingWranglerToml
560
664
  ? `wrangler dev`
561
665
  : `wrangler dev ${scriptPath}`,
562
- publish: isCreatingWranglerToml
666
+ deploy: isCreatingWranglerToml
563
667
  ? `wrangler publish`
564
668
  : `wrangler publish ${scriptPath}`,
565
669
  },
@@ -568,21 +672,21 @@ export async function main(argv: string[]): Promise<void> {
568
672
  2
569
673
  ) + "\n"
570
674
  );
571
- logger.log(
675
+ instructions.push(
572
676
  `\nTo start developing your Worker, run \`${
573
677
  isNamedWorker ? `cd ${args.name} && ` : ""
574
678
  }npm start\``
575
679
  );
576
- logger.log(
577
- `To publish your Worker to the Internet, run \`npm run publish\``
680
+ instructions.push(
681
+ `To publish your Worker to the Internet, run \`npm run deploy\``
578
682
  );
579
683
  } else {
580
- logger.log(
684
+ instructions.push(
581
685
  `\nTo start developing your Worker, run \`npx wrangler dev\`${
582
686
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
583
687
  }`
584
688
  );
585
- logger.log(
689
+ instructions.push(
586
690
  `To publish your Worker to the Internet, run \`npx wrangler publish\`${
587
691
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
588
692
  }`
@@ -590,60 +694,147 @@ export async function main(argv: string[]): Promise<void> {
590
694
  }
591
695
  }
592
696
 
697
+ async function getNewWorkerType(newWorkerFilename: string) {
698
+ return select(
699
+ `Would you like to create a Worker at ${newWorkerFilename}?`,
700
+ [
701
+ {
702
+ value: "none",
703
+ label: "None",
704
+ },
705
+ {
706
+ value: "fetch",
707
+ label: "Fetch handler",
708
+ },
709
+ {
710
+ value: "scheduled",
711
+ label: "Scheduled handler",
712
+ },
713
+ ],
714
+ 1
715
+ ) as Promise<"none" | "fetch" | "scheduled">;
716
+ }
717
+
718
+ function getNewWorkerTemplate(
719
+ lang: "js" | "ts",
720
+ workerType: "fetch" | "scheduled"
721
+ ) {
722
+ const templates = {
723
+ "js-fetch": "new-worker.js",
724
+ "js-scheduled": "new-worker-scheduled.js",
725
+ "ts-fetch": "new-worker.ts",
726
+ "ts-scheduled": "new-worker-scheduled.ts",
727
+ };
728
+
729
+ return templates[`${lang}-${workerType}`];
730
+ }
731
+
732
+ function getNewWorkerToml(
733
+ workerType: "fetch" | "scheduled"
734
+ ): TOML.JsonMap {
735
+ if (workerType === "scheduled") {
736
+ return {
737
+ triggers: {
738
+ crons: ["1 * * * *"],
739
+ },
740
+ };
741
+ }
742
+
743
+ return {};
744
+ }
745
+
593
746
  if (isTypescriptProject) {
594
747
  if (!fs.existsSync(path.join(creationDirectory, "./src/index.ts"))) {
595
- let shouldCreateSource = false;
748
+ const newWorkerFilename = path.relative(
749
+ process.cwd(),
750
+ path.join(creationDirectory, "./src/index.ts")
751
+ );
596
752
 
597
- shouldCreateSource =
598
- yesFlag ||
599
- (await confirm(
600
- `Would you like to create a Worker at src/index.ts?`
601
- ));
753
+ const newWorkerType = yesFlag
754
+ ? "fetch"
755
+ : await getNewWorkerType(newWorkerFilename);
756
+
757
+ if (newWorkerType !== "none") {
758
+ const template = getNewWorkerTemplate("ts", newWorkerType);
602
759
 
603
- if (shouldCreateSource) {
604
760
  await mkdir(path.join(creationDirectory, "./src"), {
605
761
  recursive: true,
606
762
  });
607
763
  await writeFile(
608
764
  path.join(creationDirectory, "./src/index.ts"),
609
- readFileSync(path.join(__dirname, "../templates/new-worker.ts"))
765
+ readFileSync(path.join(__dirname, `../templates/${template}`))
610
766
  );
611
767
 
612
- logger.log(`✨ Created src/index.ts`);
768
+ logger.log(
769
+ `✨ Created ${path.relative(
770
+ process.cwd(),
771
+ path.join(creationDirectory, "./src/index.ts")
772
+ )}`
773
+ );
613
774
 
614
775
  await writePackageJsonScriptsAndUpdateWranglerToml(
615
776
  shouldWritePackageJsonScripts,
616
777
  justCreatedWranglerToml,
617
778
  pathToPackageJson,
618
- "src/index.ts"
779
+ "src/index.ts",
780
+ getNewWorkerToml(newWorkerType)
619
781
  );
620
782
  }
621
783
  }
622
784
  } else {
623
- if (!fs.existsSync("./src/index.js")) {
624
- const shouldCreateSource = await confirm(
625
- `Would you like to create a Worker at src/index.js?`
785
+ if (!fs.existsSync(path.join(creationDirectory, "./src/index.js"))) {
786
+ const newWorkerFilename = path.relative(
787
+ process.cwd(),
788
+ path.join(creationDirectory, "./src/index.js")
626
789
  );
627
- if (shouldCreateSource) {
790
+
791
+ const newWorkerType = yesFlag
792
+ ? "fetch"
793
+ : await getNewWorkerType(newWorkerFilename);
794
+
795
+ if (newWorkerType !== "none") {
796
+ const template = getNewWorkerTemplate("js", newWorkerType);
797
+
628
798
  await mkdir(path.join(creationDirectory, "./src"), {
629
799
  recursive: true,
630
800
  });
631
801
  await writeFile(
632
- path.join(path.join(creationDirectory, "./src/index.js")),
633
- readFileSync(path.join(__dirname, "../templates/new-worker.js"))
802
+ path.join(creationDirectory, "./src/index.js"),
803
+ readFileSync(path.join(__dirname, `../templates/${template}`))
634
804
  );
635
805
 
636
- logger.log(`✨ Created src/index.js`);
806
+ logger.log(
807
+ `✨ Created ${path.relative(
808
+ process.cwd(),
809
+ path.join(creationDirectory, "./src/index.js")
810
+ )}`
811
+ );
637
812
 
638
813
  await writePackageJsonScriptsAndUpdateWranglerToml(
639
814
  shouldWritePackageJsonScripts,
640
815
  justCreatedWranglerToml,
641
816
  pathToPackageJson,
642
- "src/index.js"
817
+ "src/index.js",
818
+ getNewWorkerToml(newWorkerType)
643
819
  );
644
820
  }
645
821
  }
646
822
  }
823
+ // install packages as the final step of init
824
+ try {
825
+ await installPackages(shouldRunPackageManagerInstall, devDepsToInstall);
826
+ } catch (e) {
827
+ // fetching packages could fail due to loss of internet, etc
828
+ // we should let folks know we failed to fetch, but their
829
+ // workers project is still ready to go
830
+ logger.error(e instanceof Error ? e.message : e);
831
+ instructions.push(
832
+ "\n🚨 wrangler was unable to fetch your npm packages, but your project is ready to go"
833
+ );
834
+ }
835
+
836
+ // let users know what to do now
837
+ instructions.forEach((instruction) => logger.log(instruction));
647
838
  }
648
839
  );
649
840
 
@@ -654,13 +845,34 @@ export async function main(argv: string[]): Promise<void> {
654
845
  (yargs) => {
655
846
  return yargs.option("env", {
656
847
  describe: "Perform on a specific environment",
848
+ type: "string",
657
849
  });
658
850
  },
659
- () => {
851
+ async (buildArgs) => {
660
852
  // "[DEPRECATED] 🦀 Build your project (if applicable)",
661
- throw new DeprecationError(
662
- "`wrangler build` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#build for alternatives"
853
+
854
+ const envFlag = buildArgs.env ? ` --env=${buildArgs.env}` : "";
855
+ logger.log(
856
+ formatMessage({
857
+ kind: "warning",
858
+ text: "Deprecation: `wrangler build` has been deprecated.",
859
+ notes: [
860
+ {
861
+ text: "Please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#build for more information.",
862
+ },
863
+ {
864
+ text: `Attempting to run \`wrangler publish --dry-run --outdir=dist${envFlag}\` for you instead:`,
865
+ },
866
+ ],
867
+ })
663
868
  );
869
+
870
+ await createCLIParser([
871
+ "publish",
872
+ "--dry-run",
873
+ "--outdir=dist",
874
+ ...(buildArgs.env ? ["--env", buildArgs.env] : []),
875
+ ]).parse();
664
876
  }
665
877
  );
666
878
 
@@ -672,7 +884,7 @@ export async function main(argv: string[]): Promise<void> {
672
884
  () => {
673
885
  // "🕵️ Authenticate Wrangler with a Cloudflare API Token",
674
886
  throw new DeprecationError(
675
- "`wrangler config` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#config for alternatives"
887
+ "`wrangler config` has been deprecated, please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#config for alternatives"
676
888
  );
677
889
  }
678
890
  );
@@ -816,181 +1028,112 @@ export async function main(argv: string[]): Promise<void> {
816
1028
  describe: "Enable dev tools",
817
1029
  type: "boolean",
818
1030
  deprecated: true,
1031
+ })
1032
+ .option("legacy-env", {
1033
+ type: "boolean",
1034
+ describe: "Use legacy environments",
1035
+ hidden: true,
819
1036
  });
820
1037
  },
821
1038
  async (args) => {
822
- await printWranglerBanner();
823
- const configPath =
824
- (args.config as ConfigPath) ||
825
- (args.script && findWranglerToml(path.dirname(args.script)));
826
- const config = readConfig(configPath, args);
827
- const entry = await getEntry(args, config, "dev");
828
-
829
- if (args.inspect) {
830
- logger.warn(
831
- "Passing --inspect is unnecessary, now you can always connect to devtools."
832
- );
833
- }
834
-
835
- if (args["experimental-public"]) {
836
- logger.warn(
837
- "The --experimental-public field is experimental and will change in the future."
838
- );
839
- }
840
-
841
- if (args.public) {
842
- throw new Error(
843
- "The --public field has been renamed to --experimental-public, and will change behaviour in the future."
844
- );
845
- }
846
-
847
- const upstreamProtocol =
848
- args["upstream-protocol"] || config.dev.upstream_protocol;
849
- if (upstreamProtocol === "http") {
850
- logger.warn(
851
- "Setting upstream-protocol to http is not currently implemented.\n" +
852
- "If this is required in your project, please add your use case to the following issue:\n" +
853
- "https://github.com/cloudflare/wrangler2/issues/583."
854
- );
855
- }
1039
+ let watcher: ReturnType<typeof watch> | undefined;
1040
+ try {
1041
+ await printWranglerBanner();
1042
+ const configPath =
1043
+ (args.config as ConfigPath) ||
1044
+ ((args.script &&
1045
+ findWranglerToml(path.dirname(args.script))) as ConfigPath);
1046
+ let config = readConfig(configPath, args);
1047
+
1048
+ if (config.configPath) {
1049
+ watcher = watch(config.configPath, {
1050
+ persistent: true,
1051
+ }).on("change", async (_event) => {
1052
+ // TODO: Do we need to handle different `_event` types differently?
1053
+ // e.g. what if the file is deleted, or added?
1054
+ config = readConfig(configPath, args);
1055
+ if (config.configPath) {
1056
+ logger.log(`${path.basename(config.configPath)} changed...`);
1057
+ rerender(await getDevReactElement(config));
1058
+ }
1059
+ });
1060
+ }
856
1061
 
857
- const accountId = !args.local ? await requireAuth(config) : undefined;
1062
+ const entry = await getEntry(args, config, "dev");
858
1063
 
859
- // TODO: if worker_dev = false and no routes, then error (only for dev)
1064
+ if (config.services && config.services.length > 0) {
1065
+ logger.warn(
1066
+ `This worker is bound to live services: ${config.services
1067
+ .map(
1068
+ (service) =>
1069
+ `${service.binding} (${service.service}${
1070
+ service.environment ? `@${service.environment}` : ""
1071
+ })`
1072
+ )
1073
+ .join(", ")}`
1074
+ );
1075
+ }
860
1076
 
861
- /**
862
- * Given something that resembles a URL,
863
- * try to extract a host from it
864
- */
865
- function getHost(urlLike: string): string | undefined {
866
- if (
867
- !(urlLike.startsWith("http://") || urlLike.startsWith("https://"))
868
- ) {
869
- urlLike = "http://" + urlLike;
1077
+ if (args.inspect) {
1078
+ logger.warn(
1079
+ "Passing --inspect is unnecessary, now you can always connect to devtools."
1080
+ );
870
1081
  }
871
- return new URL(urlLike).host;
872
- }
873
1082
 
874
- /**
875
- * Given something that resembles a host,
876
- * try to infer a zone id from it
877
- */
878
- async function getZoneId(host: string): Promise<string | undefined> {
879
- const zones = await fetchResult<{ id: string }[]>(
880
- `/zones`,
881
- {},
882
- new URLSearchParams({ name: host })
883
- );
884
- return zones[0]?.id;
885
- }
1083
+ if (args["experimental-public"]) {
1084
+ logger.warn(
1085
+ "The --experimental-public field is experimental and will change in the future."
1086
+ );
1087
+ }
886
1088
 
887
- // When we're given a host (in one of the above ways), we do 2 things:
888
- // - We try to extract a host from it
889
- // - We try to get a zone id from the host
890
- //
891
- // So it turns out it's particularly hard to get a 'valid' domain
892
- // from a string, so we don't even try to validate TLDs, etc.
893
- // Once we get something that looks like w.x.y.z-ish, we then try to
894
- // get a zone id for it, by lopping off subdomains until we get a hit
895
- // from the API. That's it!
896
-
897
- let zone: { host: string; id: string } | undefined;
898
-
899
- if (!args.local) {
900
- const hostLike =
901
- args.host ||
902
- config.dev.host ||
903
- (args.routes && args.routes[0]) ||
904
- config.route ||
905
- (config.routes && config.routes[0]);
906
-
907
- let zoneId: string | undefined =
908
- typeof hostLike === "object" && "zone_id" in hostLike
909
- ? hostLike.zone_id
910
- : undefined;
911
-
912
- const host =
913
- typeof hostLike === "string"
914
- ? getHost(hostLike)
915
- : typeof hostLike === "object"
916
- ? "zone_name" in hostLike
917
- ? getHost(hostLike.zone_name)
918
- : getHost(hostLike.pattern)
919
- : undefined;
920
-
921
- const hostPieces =
922
- typeof host === "string" ? host.split(".") : undefined;
923
-
924
- while (!zoneId && hostPieces && hostPieces.length > 1) {
925
- zoneId = await getZoneId(hostPieces.join("."));
926
- hostPieces.shift();
1089
+ if (args.public) {
1090
+ throw new Error(
1091
+ "The --public field has been renamed to --experimental-public, and will change behaviour in the future."
1092
+ );
927
1093
  }
928
1094
 
929
- if (host && !zoneId) {
930
- throw new Error(`Could not find zone for ${hostLike}`);
1095
+ const upstreamProtocol =
1096
+ args["upstream-protocol"] || config.dev.upstream_protocol;
1097
+ if (upstreamProtocol === "http") {
1098
+ logger.warn(
1099
+ "Setting upstream-protocol to http is not currently implemented.\n" +
1100
+ "If this is required in your project, please add your use case to the following issue:\n" +
1101
+ "https://github.com/cloudflare/wrangler2/issues/583."
1102
+ );
931
1103
  }
932
1104
 
933
- zone =
934
- typeof zoneId === "string" && typeof host === "string"
935
- ? {
936
- host,
937
- id: zoneId,
938
- }
939
- : undefined;
940
- }
1105
+ // TODO: if worker_dev = false and no routes, then error (only for dev)
941
1106
 
942
- const nodeCompat = args.nodeCompat ?? config.node_compat;
943
- if (nodeCompat) {
944
- logger.warn(
945
- "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."
946
- );
947
- }
1107
+ // Compute zone info from the `host` and `route` args and config;
1108
+ let host = args.host || config.dev.host;
1109
+ let zoneId: string | undefined;
948
1110
 
949
- const { waitUntilExit } = render(
950
- <Dev
951
- name={getScriptName(args, config)}
952
- entry={entry}
953
- env={args.env}
954
- zone={zone}
955
- rules={getRules(config)}
956
- legacyEnv={isLegacyEnv(config)}
957
- minify={args.minify ?? config.minify}
958
- nodeCompat={nodeCompat}
959
- build={config.build || {}}
960
- initialMode={args.local ? "local" : "remote"}
961
- jsxFactory={args["jsx-factory"] || config.jsx_factory}
962
- jsxFragment={args["jsx-fragment"] || config.jsx_fragment}
963
- tsconfig={args.tsconfig ?? config.tsconfig}
964
- upstreamProtocol={upstreamProtocol}
965
- localProtocol={args["local-protocol"] || config.dev.local_protocol}
966
- enableLocalPersistence={
967
- args["experimental-enable-local-persistence"] || false
968
- }
969
- accountId={accountId}
970
- assetPaths={getAssetPaths(
971
- config,
972
- args.site,
973
- args.siteInclude,
974
- args.siteExclude
975
- )}
976
- port={
977
- args.port || config.dev?.port || (await getPort({ port: 8787 }))
978
- }
979
- ip={args.ip || config.dev.ip}
980
- inspectorPort={
981
- args["inspector-port"] ?? (await getPort({ port: 9229 }))
1111
+ if (!args.local) {
1112
+ if (host) {
1113
+ zoneId = await getZoneIdFromHost(host);
982
1114
  }
983
- public={args["experimental-public"]}
984
- compatibilityDate={getDevCompatibilityDate(
985
- config,
986
- args["compatibility-date"]
987
- )}
988
- compatibilityFlags={
989
- args["compatibility-flags"] || config.compatibility_flags
1115
+ const routes = args.routes || config.route || config.routes;
1116
+ if (!zoneId && routes) {
1117
+ const firstRoute = Array.isArray(routes) ? routes[0] : routes;
1118
+ const zone = await getZoneForRoute(firstRoute);
1119
+ if (zone) {
1120
+ zoneId = zone.id;
1121
+ host = zone.host;
1122
+ }
990
1123
  }
991
- usageModel={config.usage_model}
992
- bindings={{
993
- kv_namespaces: config.kv_namespaces?.map(
1124
+ }
1125
+
1126
+ const nodeCompat = args.nodeCompat ?? config.node_compat;
1127
+ if (nodeCompat) {
1128
+ logger.warn(
1129
+ "Enabling node.js compatibility mode for built-ins and globals. This is experimental and has serious tradeoffs. Please see https://github.com/ionic-team/rollup-plugin-node-polyfills/ for more details."
1130
+ );
1131
+ }
1132
+
1133
+ // eslint-disable-next-line no-inner-declarations
1134
+ async function getBindings(configParam: Config) {
1135
+ return {
1136
+ kv_namespaces: configParam.kv_namespaces?.map(
994
1137
  ({ binding, preview_id, id: _id }) => {
995
1138
  // In `dev`, we make folks use a separate kv namespace called
996
1139
  // `preview_id` instead of `id` so that they don't
@@ -1011,12 +1154,13 @@ export async function main(argv: string[]): Promise<void> {
1011
1154
  };
1012
1155
  }
1013
1156
  ),
1014
- vars: getVarsForDev(config),
1015
- wasm_modules: config.wasm_modules,
1016
- text_blobs: config.text_blobs,
1017
- data_blobs: config.data_blobs,
1018
- durable_objects: config.durable_objects,
1019
- r2_buckets: config.r2_buckets?.map(
1157
+ // Use a copy of combinedVars since we're modifying it later
1158
+ vars: getVarsForDev(configParam),
1159
+ wasm_modules: configParam.wasm_modules,
1160
+ text_blobs: configParam.text_blobs,
1161
+ data_blobs: configParam.data_blobs,
1162
+ durable_objects: configParam.durable_objects,
1163
+ r2_buckets: configParam.r2_buckets?.map(
1020
1164
  ({ binding, preview_bucket_name, bucket_name: _bucket_name }) => {
1021
1165
  // same idea as kv namespace preview id,
1022
1166
  // same copy-on-write TODO
@@ -1031,12 +1175,90 @@ export async function main(argv: string[]): Promise<void> {
1031
1175
  };
1032
1176
  }
1033
1177
  ),
1034
- unsafe: config.unsafe?.bindings,
1035
- }}
1036
- crons={config.triggers.crons}
1037
- />
1038
- );
1039
- await waitUntilExit();
1178
+ services: configParam.services,
1179
+ unsafe: configParam.unsafe?.bindings,
1180
+ };
1181
+ }
1182
+
1183
+ const getLocalPort = memoizeGetPort(DEFAULT_LOCAL_PORT);
1184
+ const getInspectorPort = memoizeGetPort(9229);
1185
+
1186
+ // eslint-disable-next-line no-inner-declarations
1187
+ async function getDevReactElement(configParam: Config) {
1188
+ // now log all available bindings into the terminal
1189
+ const bindings = await getBindings(configParam);
1190
+ // mask anything that was overridden in .dev.vars
1191
+ // so that we don't log potential secrets into the terminal
1192
+ const maskedVars = { ...bindings.vars };
1193
+ for (const key of Object.keys(maskedVars)) {
1194
+ if (maskedVars[key] !== configParam.vars[key]) {
1195
+ // This means it was overridden in .dev.vars
1196
+ // so let's mask it
1197
+ maskedVars[key] = "(hidden)";
1198
+ }
1199
+ }
1200
+
1201
+ printBindings({
1202
+ ...bindings,
1203
+ vars: maskedVars,
1204
+ });
1205
+
1206
+ return (
1207
+ <Dev
1208
+ name={getScriptName(args, config)}
1209
+ entry={entry}
1210
+ env={args.env}
1211
+ zone={zoneId}
1212
+ host={host}
1213
+ rules={getRules(config)}
1214
+ legacyEnv={isLegacyEnv(config)}
1215
+ minify={args.minify ?? config.minify}
1216
+ nodeCompat={nodeCompat}
1217
+ build={config.build || {}}
1218
+ initialMode={args.local ? "local" : "remote"}
1219
+ jsxFactory={args["jsx-factory"] || config.jsx_factory}
1220
+ jsxFragment={args["jsx-fragment"] || config.jsx_fragment}
1221
+ tsconfig={args.tsconfig ?? config.tsconfig}
1222
+ upstreamProtocol={upstreamProtocol}
1223
+ localProtocol={
1224
+ args["local-protocol"] || config.dev.local_protocol
1225
+ }
1226
+ enableLocalPersistence={
1227
+ args["experimental-enable-local-persistence"] || false
1228
+ }
1229
+ accountId={config.account_id}
1230
+ assetPaths={getAssetPaths(
1231
+ config,
1232
+ args.site,
1233
+ args.siteInclude,
1234
+ args.siteExclude
1235
+ )}
1236
+ port={args.port || config.dev.port || (await getLocalPort())}
1237
+ ip={args.ip || config.dev.ip}
1238
+ inspectorPort={
1239
+ args["inspector-port"] ?? (await getInspectorPort())
1240
+ }
1241
+ public={args["experimental-public"]}
1242
+ compatibilityDate={getDevCompatibilityDate(
1243
+ config,
1244
+ args["compatibility-date"]
1245
+ )}
1246
+ compatibilityFlags={
1247
+ args["compatibility-flags"] || config.compatibility_flags
1248
+ }
1249
+ usageModel={config.usage_model}
1250
+ bindings={bindings}
1251
+ crons={config.triggers.crons}
1252
+ />
1253
+ );
1254
+ }
1255
+ const { waitUntilExit, rerender } = render(
1256
+ await getDevReactElement(config)
1257
+ );
1258
+ await waitUntilExit();
1259
+ } finally {
1260
+ await watcher?.close();
1261
+ }
1040
1262
  }
1041
1263
  );
1042
1264
 
@@ -1153,6 +1375,11 @@ export async function main(argv: string[]): Promise<void> {
1153
1375
  .option("dry-run", {
1154
1376
  describe: "Don't actually publish",
1155
1377
  type: "boolean",
1378
+ })
1379
+ .option("legacy-env", {
1380
+ type: "boolean",
1381
+ describe: "Use legacy environments",
1382
+ hidden: true,
1156
1383
  });
1157
1384
  },
1158
1385
  async (args) => {
@@ -1180,7 +1407,7 @@ export async function main(argv: string[]): Promise<void> {
1180
1407
  );
1181
1408
  }
1182
1409
 
1183
- const accountId = await requireAuth(config);
1410
+ const accountId = args.dryRun ? undefined : await requireAuth(config);
1184
1411
 
1185
1412
  const assetPaths = getAssetPaths(
1186
1413
  config,
@@ -1188,6 +1415,7 @@ export async function main(argv: string[]): Promise<void> {
1188
1415
  args.siteInclude,
1189
1416
  args.siteExclude
1190
1417
  );
1418
+
1191
1419
  await publish({
1192
1420
  config,
1193
1421
  accountId,
@@ -1274,6 +1502,11 @@ export async function main(argv: string[]): Promise<void> {
1274
1502
  default: false,
1275
1503
  describe:
1276
1504
  "If a log would have been filtered out, send it through anyway alongside the filter which would have blocked it.",
1505
+ })
1506
+ .option("legacy-env", {
1507
+ type: "boolean",
1508
+ describe: "Use legacy environments",
1509
+ hidden: true,
1277
1510
  });
1278
1511
  },
1279
1512
  async (args) => {
@@ -1410,6 +1643,7 @@ export async function main(argv: string[]): Promise<void> {
1410
1643
  rules={getRules(config)}
1411
1644
  env={args.env}
1412
1645
  zone={undefined}
1646
+ host={undefined}
1413
1647
  legacyEnv={isLegacyEnv(config)}
1414
1648
  build={config.build || {}}
1415
1649
  minify={undefined}
@@ -1423,7 +1657,9 @@ export async function main(argv: string[]): Promise<void> {
1423
1657
  enableLocalPersistence={false}
1424
1658
  accountId={accountId}
1425
1659
  assetPaths={undefined}
1426
- port={config.dev?.port}
1660
+ port={
1661
+ config.dev.port || (await getPort({ port: DEFAULT_LOCAL_PORT }))
1662
+ }
1427
1663
  ip={config.dev.ip}
1428
1664
  public={undefined}
1429
1665
  compatibilityDate={getDevCompatibilityDate(config)}
@@ -1471,6 +1707,7 @@ export async function main(argv: string[]): Promise<void> {
1471
1707
  };
1472
1708
  }
1473
1709
  ),
1710
+ services: config.services,
1474
1711
  unsafe: config.unsafe?.bindings,
1475
1712
  }}
1476
1713
  crons={config.triggers.crons}
@@ -1570,7 +1807,7 @@ export async function main(argv: string[]): Promise<void> {
1570
1807
  },
1571
1808
  () => {
1572
1809
  throw new DeprecationError(
1573
- "`wrangler subdomain` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#subdomain for alternatives"
1810
+ "`wrangler subdomain` has been deprecated, please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#subdomain for alternatives"
1574
1811
  );
1575
1812
  }
1576
1813
  );
@@ -1582,6 +1819,11 @@ export async function main(argv: string[]): Promise<void> {
1582
1819
  (secretYargs) => {
1583
1820
  return secretYargs
1584
1821
  .command(subHelp)
1822
+ .option("legacy-env", {
1823
+ type: "boolean",
1824
+ describe: "Use legacy environments",
1825
+ hidden: true,
1826
+ })
1585
1827
  .command(
1586
1828
  "put <key>",
1587
1829
  "Create or update a secret variable for a script",
@@ -1616,9 +1858,11 @@ export async function main(argv: string[]): Promise<void> {
1616
1858
  const accountId = await requireAuth(config);
1617
1859
 
1618
1860
  const isInteractive = process.stdin.isTTY;
1619
- const secretValue = isInteractive
1620
- ? await prompt("Enter a secret value:", "password")
1621
- : await readFromStdin();
1861
+ const secretValue = trimTrailingWhitespace(
1862
+ isInteractive
1863
+ ? await prompt("Enter a secret value:", "password")
1864
+ : await readFromStdin()
1865
+ );
1622
1866
 
1623
1867
  logger.log(
1624
1868
  `🌀 Creating the secret for script ${scriptName} ${
@@ -1663,6 +1907,7 @@ export async function main(argv: string[]): Promise<void> {
1663
1907
  vars: {},
1664
1908
  durable_objects: { bindings: [] },
1665
1909
  r2_buckets: [],
1910
+ services: [],
1666
1911
  wasm_modules: {},
1667
1912
  text_blobs: {},
1668
1913
  data_blobs: {},
@@ -1831,7 +2076,7 @@ export async function main(argv: string[]): Promise<void> {
1831
2076
  async (args) => {
1832
2077
  await printWranglerBanner();
1833
2078
 
1834
- if (!isValidNamespaceBinding(args.namespace)) {
2079
+ if (!isValidKVNamespaceBinding(args.namespace)) {
1835
2080
  throw new CommandLineArgsError(
1836
2081
  `The namespace binding name "${args.namespace}" is invalid. It can only have alphanumeric and _ characters, and cannot begin with a number.`
1837
2082
  );
@@ -1854,7 +2099,7 @@ export async function main(argv: string[]): Promise<void> {
1854
2099
  // TODO: generate a binding name stripping non alphanumeric chars
1855
2100
 
1856
2101
  logger.log(`🌀 Creating namespace with title "${title}"`);
1857
- const namespaceId = await createNamespace(accountId, title);
2102
+ const namespaceId = await createKVNamespace(accountId, title);
1858
2103
 
1859
2104
  logger.log("✨ Success!");
1860
2105
  const envString = args.env ? ` under [env.${args.env}]` : "";
@@ -1881,7 +2126,7 @@ export async function main(argv: string[]): Promise<void> {
1881
2126
  // TODO: we should show bindings if they exist for given ids
1882
2127
 
1883
2128
  logger.log(
1884
- JSON.stringify(await listNamespaces(accountId), null, " ")
2129
+ JSON.stringify(await listKVNamespaces(accountId), null, " ")
1885
2130
  );
1886
2131
  }
1887
2132
  )
@@ -1918,7 +2163,7 @@ export async function main(argv: string[]): Promise<void> {
1918
2163
 
1919
2164
  let id;
1920
2165
  try {
1921
- id = getNamespaceId(args, config);
2166
+ id = getKVNamespaceId(args, config);
1922
2167
  } catch (e) {
1923
2168
  throw new CommandLineArgsError(
1924
2169
  "Not able to delete namespace.\n" + ((e as Error).message ?? e)
@@ -1927,10 +2172,7 @@ export async function main(argv: string[]): Promise<void> {
1927
2172
 
1928
2173
  const accountId = await requireAuth(config);
1929
2174
 
1930
- await fetchResult<{ id: string }>(
1931
- `/accounts/${accountId}/storage/kv/namespaces/${id}`,
1932
- { method: "DELETE" }
1933
- );
2175
+ await deleteKVNamespace(accountId, id);
1934
2176
 
1935
2177
  // TODO: recommend they remove it from wrangler.toml
1936
2178
 
@@ -2015,7 +2257,7 @@ export async function main(argv: string[]): Promise<void> {
2015
2257
  async ({ key, ttl, expiration, ...args }) => {
2016
2258
  await printWranglerBanner();
2017
2259
  const config = readConfig(args.config as ConfigPath, args);
2018
- const namespaceId = getNamespaceId(args, config);
2260
+ const namespaceId = getKVNamespaceId(args, config);
2019
2261
  // One of `args.path` and `args.value` must be defined
2020
2262
  const value = args.path
2021
2263
  ? readFileSync(args.path)
@@ -2034,7 +2276,7 @@ export async function main(argv: string[]): Promise<void> {
2034
2276
 
2035
2277
  const accountId = await requireAuth(config);
2036
2278
 
2037
- await putKeyValue(accountId, namespaceId, {
2279
+ await putKVKeyValue(accountId, namespaceId, {
2038
2280
  key,
2039
2281
  value,
2040
2282
  expiration,
@@ -2079,11 +2321,11 @@ export async function main(argv: string[]): Promise<void> {
2079
2321
  async ({ prefix, ...args }) => {
2080
2322
  // TODO: support for limit+cursor (pagination)
2081
2323
  const config = readConfig(args.config as ConfigPath, args);
2082
- const namespaceId = getNamespaceId(args, config);
2324
+ const namespaceId = getKVNamespaceId(args, config);
2083
2325
 
2084
2326
  const accountId = await requireAuth(config);
2085
2327
 
2086
- const results = await listNamespaceKeys(
2328
+ const results = await listKVNamespaceKeys(
2087
2329
  accountId,
2088
2330
  namespaceId,
2089
2331
  prefix
@@ -2131,11 +2373,11 @@ export async function main(argv: string[]): Promise<void> {
2131
2373
  },
2132
2374
  async ({ key, ...args }) => {
2133
2375
  const config = readConfig(args.config as ConfigPath, args);
2134
- const namespaceId = getNamespaceId(args, config);
2376
+ const namespaceId = getKVNamespaceId(args, config);
2135
2377
 
2136
2378
  const accountId = await requireAuth(config);
2137
2379
 
2138
- logger.log(await getKeyValue(accountId, namespaceId, key));
2380
+ logger.log(await getKVKeyValue(accountId, namespaceId, key));
2139
2381
  }
2140
2382
  )
2141
2383
  .command(
@@ -2173,7 +2415,7 @@ export async function main(argv: string[]): Promise<void> {
2173
2415
  async ({ key, ...args }) => {
2174
2416
  await printWranglerBanner();
2175
2417
  const config = readConfig(args.config as ConfigPath, args);
2176
- const namespaceId = getNamespaceId(args, config);
2418
+ const namespaceId = getKVNamespaceId(args, config);
2177
2419
 
2178
2420
  logger.log(
2179
2421
  `Deleting the key "${key}" on namespace ${namespaceId}.`
@@ -2181,10 +2423,7 @@ export async function main(argv: string[]): Promise<void> {
2181
2423
 
2182
2424
  const accountId = await requireAuth(config);
2183
2425
 
2184
- await fetchResult(
2185
- `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`,
2186
- { method: "DELETE" }
2187
- );
2426
+ await deleteKVKeyValue(accountId, namespaceId, key);
2188
2427
  }
2189
2428
  );
2190
2429
  }
@@ -2236,7 +2475,7 @@ export async function main(argv: string[]): Promise<void> {
2236
2475
  // but we'll do that in the future if needed.
2237
2476
 
2238
2477
  const config = readConfig(args.config as ConfigPath, args);
2239
- const namespaceId = getNamespaceId(args, config);
2478
+ const namespaceId = getKVNamespaceId(args, config);
2240
2479
  const content = parseJSON(readFileSync(filename), filename);
2241
2480
 
2242
2481
  if (!Array.isArray(content)) {
@@ -2250,18 +2489,12 @@ export async function main(argv: string[]): Promise<void> {
2250
2489
  const warnings: string[] = [];
2251
2490
  for (let i = 0; i < content.length; i++) {
2252
2491
  const keyValue = content[i];
2253
- if (typeof keyValue !== "object") {
2254
- errors.push(
2255
- `The item at index ${i} is type: "${typeof keyValue}" - ${JSON.stringify(
2256
- keyValue
2257
- )}`
2258
- );
2259
- } else if (!isKeyValue(keyValue)) {
2492
+ if (!isKVKeyValue(keyValue)) {
2260
2493
  errors.push(
2261
2494
  `The item at index ${i} is ${JSON.stringify(keyValue)}`
2262
2495
  );
2263
2496
  } else {
2264
- const props = unexpectedKeyValueProps(keyValue);
2497
+ const props = unexpectedKVKeyValueProps(keyValue);
2265
2498
  if (props.length > 0) {
2266
2499
  warnings.push(
2267
2500
  `The item at index ${i} contains unexpected properties: ${JSON.stringify(
@@ -2294,14 +2527,7 @@ export async function main(argv: string[]): Promise<void> {
2294
2527
  }
2295
2528
 
2296
2529
  const accountId = await requireAuth(config);
2297
- await putBulkKeyValue(
2298
- accountId,
2299
- namespaceId,
2300
- content,
2301
- (index, total) => {
2302
- logger.log(`Uploaded ${index} of ${total}.`);
2303
- }
2304
- );
2530
+ await putKVBulkKeyValue(accountId, namespaceId, content);
2305
2531
 
2306
2532
  logger.log("Success!");
2307
2533
  }
@@ -2346,7 +2572,7 @@ export async function main(argv: string[]): Promise<void> {
2346
2572
  async ({ filename, ...args }) => {
2347
2573
  await printWranglerBanner();
2348
2574
  const config = readConfig(args.config as ConfigPath, args);
2349
- const namespaceId = getNamespaceId(args, config);
2575
+ const namespaceId = getKVNamespaceId(args, config);
2350
2576
 
2351
2577
  if (!args.force) {
2352
2578
  const result = await confirm(
@@ -2391,14 +2617,7 @@ export async function main(argv: string[]): Promise<void> {
2391
2617
 
2392
2618
  const accountId = await requireAuth(config);
2393
2619
 
2394
- await deleteBulkKeyValue(
2395
- accountId,
2396
- namespaceId,
2397
- content,
2398
- (index, total) => {
2399
- logger.log(`Deleted ${index} of ${total}.`);
2400
- }
2401
- );
2620
+ await deleteKVBulkKeyValue(accountId, namespaceId, content);
2402
2621
 
2403
2622
  logger.log("Success!");
2404
2623
  }
@@ -2551,41 +2770,43 @@ export async function main(argv: string[]): Promise<void> {
2551
2770
  }
2552
2771
  );
2553
2772
 
2554
- wrangler
2555
- .option("legacy-env", {
2556
- type: "boolean",
2557
- describe: "Use legacy environments",
2558
- })
2559
- .option("config", {
2560
- alias: "c",
2561
- describe: "Path to .toml configuration file",
2562
- type: "string",
2563
- requiresArg: true,
2564
- });
2773
+ wrangler.option("config", {
2774
+ alias: "c",
2775
+ describe: "Path to .toml configuration file",
2776
+ type: "string",
2777
+ requiresArg: true,
2778
+ });
2565
2779
 
2566
- wrangler.group(["config", "help", "version", "legacy-env"], "Flags:");
2780
+ wrangler.group(["config", "help", "version"], "Flags:");
2567
2781
  wrangler.help().alias("h", "help");
2568
2782
  wrangler.version(wranglerVersion).alias("v", "version");
2569
2783
  wrangler.exitProcess(false);
2570
2784
 
2785
+ return wrangler;
2786
+ }
2787
+
2788
+ export async function main(argv: string[]): Promise<void> {
2789
+ const wrangler = createCLIParser(argv);
2571
2790
  try {
2572
2791
  await wrangler.parse();
2573
2792
  } catch (e) {
2574
2793
  logger.log(""); // Just adds a bit of space
2575
2794
  if (e instanceof CommandLineArgsError) {
2576
- wrangler.showHelp("error");
2577
- logger.log(""); // Add a bit of space.
2578
2795
  logger.error(e.message);
2796
+ // We are not able to ask the `wrangler` CLI parser to show help for a subcommand programmatically.
2797
+ // The workaround is to re-run the parsing with an additional `--help` flag, which will result in the correct help message being displayed.
2798
+ // The `wrangler` object is "frozen"; we cannot reuse that with different args, so we must create a new CLI parser to generate the help message.
2799
+ await createCLIParser([...argv, "--help"]).parse();
2579
2800
  } else if (e instanceof ParseError) {
2580
2801
  e.notes.push({
2581
- text: "\nIf you think this is a bug, please open an issue at: https://github.com/cloudflare/wrangler2/issues/new",
2802
+ text: "\nIf you think this is a bug, please open an issue at: https://github.com/cloudflare/wrangler2/issues/new/choose",
2582
2803
  });
2583
- logger.error(formatMessage(e));
2804
+ logger.log(formatMessage(e));
2584
2805
  } else {
2585
2806
  logger.error(e instanceof Error ? e.message : e);
2586
2807
  logger.log(
2587
2808
  `${fgGreenColor}%s${resetColor}`,
2588
- "If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
2809
+ "If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
2589
2810
  );
2590
2811
  }
2591
2812
  throw e;
@@ -2613,3 +2834,13 @@ function getDevCompatibilityDate(
2613
2834
  }
2614
2835
  return compatibilityDate ?? currentDate;
2615
2836
  }
2837
+
2838
+ /**
2839
+ * Avoiding calling `getPort()` multiple times by memoizing the first result.
2840
+ */
2841
+ function memoizeGetPort(defaultPort: number) {
2842
+ let portValue: number;
2843
+ return async () => {
2844
+ return portValue || (portValue = await getPort({ port: defaultPort }));
2845
+ };
2846
+ }