wrangler 0.0.0-e95e5a0 → 0.0.0-e978986

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 (52) 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 +11 -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 +336 -87
  9. package/src/__tests__/dev.test.tsx +243 -25
  10. package/src/__tests__/helpers/mock-dialogs.ts +41 -1
  11. package/src/__tests__/index.test.ts +79 -72
  12. package/src/__tests__/init.test.ts +182 -131
  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 +381 -53
  17. package/src/__tests__/parse.test.ts +8 -0
  18. package/src/__tests__/publish.test.ts +682 -89
  19. package/src/__tests__/r2.test.ts +55 -36
  20. package/src/__tests__/secret.test.ts +39 -4
  21. package/src/abort.d.ts +3 -0
  22. package/src/bundle.ts +33 -2
  23. package/src/cfetch/index.ts +21 -4
  24. package/src/cfetch/internal.ts +17 -9
  25. package/src/config/config.ts +1 -1
  26. package/src/config/environment.ts +40 -14
  27. package/src/config/index.ts +162 -0
  28. package/src/config/validation.ts +180 -65
  29. package/src/create-worker-preview.ts +17 -7
  30. package/src/create-worker-upload-form.ts +22 -8
  31. package/src/dev/dev.tsx +7 -6
  32. package/src/dev/local.tsx +6 -0
  33. package/src/dev/remote.tsx +15 -1
  34. package/src/dialogs.tsx +48 -0
  35. package/src/durable.ts +102 -0
  36. package/src/entry.ts +64 -9
  37. package/src/index.tsx +357 -177
  38. package/src/inspect.ts +39 -0
  39. package/src/kv.ts +114 -25
  40. package/src/open-in-browser.ts +5 -12
  41. package/src/package-manager.ts +50 -3
  42. package/src/pages.tsx +271 -110
  43. package/src/parse.ts +23 -4
  44. package/src/proxy.ts +38 -22
  45. package/src/publish.ts +227 -113
  46. package/src/sites.tsx +19 -17
  47. package/src/user.tsx +11 -1
  48. package/src/worker.ts +8 -0
  49. package/templates/new-worker-scheduled.js +17 -0
  50. package/templates/new-worker-scheduled.ts +32 -0
  51. package/templates/new-worker.ts +16 -1
  52. package/wrangler-dist/cli.js +36017 -22687
package/src/index.tsx CHANGED
@@ -11,28 +11,31 @@ import { render } from "ink";
11
11
  import React from "react";
12
12
  import onExit from "signal-exit";
13
13
  import supportsColor from "supports-color";
14
+ import { setGlobalDispatcher, ProxyAgent } from "undici";
14
15
  import makeCLI from "yargs";
15
16
  import { version as wranglerVersion } from "../package.json";
16
17
  import { fetchResult } from "./cfetch";
17
- import { findWranglerToml, readConfig } from "./config";
18
+ import { findWranglerToml, printBindings, readConfig } from "./config";
18
19
  import { createWorkerUploadForm } from "./create-worker-upload-form";
19
20
  import Dev from "./dev/dev";
20
21
  import { getVarsForDev } from "./dev/dev-vars";
21
- import { confirm, prompt } from "./dialogs";
22
+ import { confirm, prompt, select } from "./dialogs";
22
23
  import { getEntry } from "./entry";
23
24
  import { DeprecationError } from "./errors";
24
25
  import {
25
- getNamespaceId,
26
- listNamespaces,
27
- listNamespaceKeys,
28
- putKeyValue,
29
- putBulkKeyValue,
30
- deleteBulkKeyValue,
31
- createNamespace,
32
- isValidNamespaceBinding,
33
- getKeyValue,
34
- isKeyValue,
35
- unexpectedKeyValueProps,
26
+ getKVNamespaceId,
27
+ listKVNamespaces,
28
+ listKVNamespaceKeys,
29
+ putKVKeyValue,
30
+ putKVBulkKeyValue,
31
+ deleteKVBulkKeyValue,
32
+ createKVNamespace,
33
+ isValidKVNamespaceBinding,
34
+ getKVKeyValue,
35
+ isKVKeyValue,
36
+ unexpectedKVKeyValueProps,
37
+ deleteKVNamespace,
38
+ deleteKVKeyValue,
36
39
  } from "./kv";
37
40
  import { logger } from "./logger";
38
41
  import { getPackageManager } from "./package-manager";
@@ -41,6 +44,7 @@ import {
41
44
  formatMessage,
42
45
  ParseError,
43
46
  parseJSON,
47
+ parsePackageJSON,
44
48
  parseTOML,
45
49
  readFileSync,
46
50
  } from "./parse";
@@ -73,6 +77,18 @@ type ConfigPath = string | undefined;
73
77
 
74
78
  const resetColor = "\x1b[0m";
75
79
  const fgGreenColor = "\x1b[32m";
80
+ const DEFAULT_LOCAL_PORT = 8787;
81
+
82
+ const proxy =
83
+ process.env.https_proxy ||
84
+ process.env.HTTPS_PROXY ||
85
+ process.env.http_proxy ||
86
+ process.env.HTTP_PROXY ||
87
+ undefined;
88
+
89
+ if (proxy) {
90
+ setGlobalDispatcher(new ProxyAgent(proxy));
91
+ }
76
92
 
77
93
  function getRules(config: Config): Config["rules"] {
78
94
  const rules = config.rules ?? config.build?.upload?.rules ?? [];
@@ -203,9 +219,17 @@ function demandOneOfOption(...options: string[]) {
203
219
  };
204
220
  }
205
221
 
222
+ /**
223
+ * Remove trailing white space from inputs.
224
+ * Matching Wrangler legacy behavior with handling inputs
225
+ */
226
+ function trimTrailingWhitespace(str: string) {
227
+ return str.trimEnd();
228
+ }
229
+
206
230
  class CommandLineArgsError extends Error {}
207
231
 
208
- export async function main(argv: string[]): Promise<void> {
232
+ function createCLIParser(argv: string[]) {
209
233
  const wrangler = makeCLI(argv)
210
234
  .strict()
211
235
  // We handle errors ourselves in a try-catch around `yargs.parse`.
@@ -322,6 +346,9 @@ export async function main(argv: string[]): Promise<void> {
322
346
  throw new CommandLineArgsError(message);
323
347
  }
324
348
 
349
+ const devDepsToInstall: string[] = [];
350
+ const instructions: string[] = [];
351
+ let shouldRunPackageManagerInstall = false;
325
352
  const creationDirectory = path.resolve(process.cwd(), args.name ?? "");
326
353
 
327
354
  if (args.site) {
@@ -406,13 +433,20 @@ export async function main(argv: string[]): Promise<void> {
406
433
  const isInsideGitProject = Boolean(
407
434
  await findUp(".git", { cwd: creationDirectory, type: "directory" })
408
435
  );
409
- const isGitInstalled = (await execa("git", ["--version"])).exitCode === 0;
436
+ let isGitInstalled;
437
+ try {
438
+ isGitInstalled = (await execa("git", ["--version"])).exitCode === 0;
439
+ } catch (err) {
440
+ isGitInstalled = false;
441
+ }
410
442
  if (!isInsideGitProject && isGitInstalled) {
411
443
  const shouldInitGit =
412
444
  yesFlag ||
413
445
  (await confirm("Would you like to use git to manage this Worker?"));
414
446
  if (shouldInitGit) {
415
- await execa("git", ["init"], { cwd: creationDirectory });
447
+ await execa("git", ["init", "--initial-branch=main"], {
448
+ cwd: creationDirectory,
449
+ });
416
450
  await writeFile(
417
451
  path.join(creationDirectory, ".gitignore"),
418
452
  readFileSync(path.join(__dirname, "../templates/gitignore"))
@@ -458,7 +492,7 @@ export async function main(argv: string[]): Promise<void> {
458
492
  ) + "\n"
459
493
  );
460
494
 
461
- await packageManager.install();
495
+ shouldRunPackageManagerInstall = true;
462
496
  pathToPackageJson = path.join(creationDirectory, "package.json");
463
497
  logger.log(
464
498
  `✨ Created ${path.relative(process.cwd(), pathToPackageJson)}`
@@ -469,7 +503,7 @@ export async function main(argv: string[]): Promise<void> {
469
503
  } else {
470
504
  // If package.json exists and wrangler isn't installed,
471
505
  // then ask to add wrangler to devDependencies
472
- const packageJson = parseJSON(
506
+ const packageJson = parsePackageJSON(
473
507
  readFileSync(pathToPackageJson),
474
508
  pathToPackageJson
475
509
  );
@@ -488,8 +522,7 @@ export async function main(argv: string[]): Promise<void> {
488
522
  )}?`
489
523
  ));
490
524
  if (shouldInstall) {
491
- await packageManager.addDevDeps(`wrangler@${wranglerVersion}`);
492
- logger.log(`✨ Installed wrangler`);
525
+ devDepsToInstall.push(`wrangler@${wranglerVersion}`);
493
526
  }
494
527
  }
495
528
  }
@@ -507,23 +540,18 @@ export async function main(argv: string[]): Promise<void> {
507
540
  path.join(creationDirectory, "./tsconfig.json"),
508
541
  readFileSync(path.join(__dirname, "../templates/tsconfig.json"))
509
542
  );
510
- await packageManager.addDevDeps(
511
- "@cloudflare/workers-types",
512
- "typescript"
513
- );
543
+ devDepsToInstall.push("@cloudflare/workers-types");
544
+ devDepsToInstall.push("typescript");
514
545
  pathToTSConfig = path.join(creationDirectory, "tsconfig.json");
515
546
  logger.log(
516
- `✨ Created ${path.relative(
517
- process.cwd(),
518
- pathToTSConfig
519
- )}, installed @cloudflare/workers-types into devDependencies`
547
+ `✨ Created ${path.relative(process.cwd(), pathToTSConfig)}`
520
548
  );
521
549
  }
522
550
  } else {
523
551
  isTypescriptProject = true;
524
552
  // If there's a tsconfig, check if @cloudflare/workers-types
525
553
  // is already installed, and offer to install it if not
526
- const packageJson = parseJSON(
554
+ const packageJson = parsePackageJSON(
527
555
  readFileSync(pathToPackageJson),
528
556
  pathToPackageJson
529
557
  );
@@ -537,13 +565,13 @@ export async function main(argv: string[]): Promise<void> {
537
565
  "Would you like to install the type definitions for Workers into your package.json?"
538
566
  );
539
567
  if (shouldInstall) {
540
- await packageManager.addDevDeps("@cloudflare/workers-types");
568
+ devDepsToInstall.push("@cloudflare/workers-types");
541
569
  // We don't update the tsconfig.json because
542
570
  // it could be complicated in existing projects
543
571
  // and we don't want to break them. Instead, we simply
544
572
  // tell the user that they need to update their tsconfig.json
545
- logger.log(
546
- `✨ Installed @cloudflare/workers-types.\nPlease add "@cloudflare/workers-types" to compilerOptions.types in ${path.relative(
573
+ instructions.push(
574
+ `🚨 Please add "@cloudflare/workers-types" to compilerOptions.types in ${path.relative(
547
575
  process.cwd(),
548
576
  pathToTSConfig
549
577
  )}`
@@ -552,7 +580,7 @@ export async function main(argv: string[]): Promise<void> {
552
580
  }
553
581
  }
554
582
 
555
- const packageJsonContent = parseJSON(
583
+ const packageJsonContent = parsePackageJSON(
556
584
  readFileSync(pathToPackageJson),
557
585
  pathToPackageJson
558
586
  );
@@ -561,6 +589,41 @@ export async function main(argv: string[]): Promise<void> {
561
589
  !packageJsonContent.scripts?.publish &&
562
590
  shouldCreatePackageJson;
563
591
 
592
+ /*
593
+ * Passes the array of accumulated devDeps to install through to
594
+ * the package manager. Also generates a human-readable list
595
+ * of packages it installed.
596
+ * If there are no devDeps to install, optionally runs
597
+ * the package manager's install command.
598
+ */
599
+ async function installPackages(
600
+ shouldRunInstall: boolean,
601
+ depsToInstall: string[]
602
+ ) {
603
+ //lets install the devDeps they asked for
604
+ //and run their package manager's install command if needed
605
+ if (depsToInstall.length > 0) {
606
+ const formatter = new Intl.ListFormat("en", {
607
+ style: "long",
608
+ type: "conjunction",
609
+ });
610
+ await packageManager.addDevDeps(...depsToInstall);
611
+ const versionlessPackages = depsToInstall.map((dep) =>
612
+ dep === `wrangler@${wranglerVersion}` ? "wrangler" : dep
613
+ );
614
+
615
+ logger.log(
616
+ `✨ Installed ${formatter.format(
617
+ versionlessPackages
618
+ )} into devDependencies`
619
+ );
620
+ } else {
621
+ if (shouldRunInstall) {
622
+ await packageManager.install();
623
+ }
624
+ }
625
+ }
626
+
564
627
  async function writePackageJsonScriptsAndUpdateWranglerToml(
565
628
  isWritingScripts: boolean,
566
629
  isCreatingWranglerToml: boolean,
@@ -595,7 +658,7 @@ export async function main(argv: string[]): Promise<void> {
595
658
  start: isCreatingWranglerToml
596
659
  ? `wrangler dev`
597
660
  : `wrangler dev ${scriptPath}`,
598
- publish: isCreatingWranglerToml
661
+ deploy: isCreatingWranglerToml
599
662
  ? `wrangler publish`
600
663
  : `wrangler publish ${scriptPath}`,
601
664
  },
@@ -604,21 +667,21 @@ export async function main(argv: string[]): Promise<void> {
604
667
  2
605
668
  ) + "\n"
606
669
  );
607
- logger.log(
670
+ instructions.push(
608
671
  `\nTo start developing your Worker, run \`${
609
672
  isNamedWorker ? `cd ${args.name} && ` : ""
610
673
  }npm start\``
611
674
  );
612
- logger.log(
613
- `To publish your Worker to the Internet, run \`npm run publish\``
675
+ instructions.push(
676
+ `To publish your Worker to the Internet, run \`npm run deploy\``
614
677
  );
615
678
  } else {
616
- logger.log(
679
+ instructions.push(
617
680
  `\nTo start developing your Worker, run \`npx wrangler dev\`${
618
681
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
619
682
  }`
620
683
  );
621
- logger.log(
684
+ instructions.push(
622
685
  `To publish your Worker to the Internet, run \`npx wrangler publish\`${
623
686
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
624
687
  }`
@@ -626,24 +689,61 @@ export async function main(argv: string[]): Promise<void> {
626
689
  }
627
690
  }
628
691
 
692
+ async function getNewWorkerType(newWorkerFilename: string) {
693
+ return select(
694
+ `Would you like to create a Worker at ${newWorkerFilename}?`,
695
+ [
696
+ {
697
+ value: "none",
698
+ label: "None",
699
+ },
700
+ {
701
+ value: "fetch",
702
+ label: "Fetch handler",
703
+ },
704
+ {
705
+ value: "scheduled",
706
+ label: "Scheduled handler",
707
+ },
708
+ ],
709
+ 1
710
+ ) as Promise<"none" | "fetch" | "scheduled">;
711
+ }
712
+
713
+ function getNewWorkerTemplate(
714
+ lang: "js" | "ts",
715
+ workerType: "fetch" | "scheduled"
716
+ ) {
717
+ const templates = {
718
+ "js-fetch": "new-worker.js",
719
+ "js-scheduled": "new-worker-scheduled.js",
720
+ "ts-fetch": "new-worker.ts",
721
+ "ts-scheduled": "new-worker-scheduled.ts",
722
+ };
723
+
724
+ return templates[`${lang}-${workerType}`];
725
+ }
726
+
629
727
  if (isTypescriptProject) {
630
728
  if (!fs.existsSync(path.join(creationDirectory, "./src/index.ts"))) {
631
- const shouldCreateSource =
632
- yesFlag ||
633
- (await confirm(
634
- `Would you like to create a Worker at ${path.relative(
635
- process.cwd(),
636
- path.join(creationDirectory, "./src/index.ts")
637
- )}?`
638
- ));
729
+ const newWorkerFilename = path.relative(
730
+ process.cwd(),
731
+ path.join(creationDirectory, "./src/index.ts")
732
+ );
733
+
734
+ const newWorkerType = yesFlag
735
+ ? "fetch"
736
+ : await getNewWorkerType(newWorkerFilename);
737
+
738
+ if (newWorkerType !== "none") {
739
+ const template = getNewWorkerTemplate("ts", newWorkerType);
639
740
 
640
- if (shouldCreateSource) {
641
741
  await mkdir(path.join(creationDirectory, "./src"), {
642
742
  recursive: true,
643
743
  });
644
744
  await writeFile(
645
745
  path.join(creationDirectory, "./src/index.ts"),
646
- readFileSync(path.join(__dirname, "../templates/new-worker.ts"))
746
+ readFileSync(path.join(__dirname, `../templates/${template}`))
647
747
  );
648
748
 
649
749
  logger.log(
@@ -663,22 +763,24 @@ export async function main(argv: string[]): Promise<void> {
663
763
  }
664
764
  } else {
665
765
  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
- ));
766
+ const newWorkerFilename = path.relative(
767
+ process.cwd(),
768
+ path.join(creationDirectory, "./src/index.js")
769
+ );
770
+
771
+ const newWorkerType = yesFlag
772
+ ? "fetch"
773
+ : await getNewWorkerType(newWorkerFilename);
774
+
775
+ if (newWorkerType !== "none") {
776
+ const template = getNewWorkerTemplate("js", newWorkerType);
674
777
 
675
- if (shouldCreateSource) {
676
778
  await mkdir(path.join(creationDirectory, "./src"), {
677
779
  recursive: true,
678
780
  });
679
781
  await writeFile(
680
782
  path.join(creationDirectory, "./src/index.js"),
681
- readFileSync(path.join(__dirname, "../templates/new-worker.js"))
783
+ readFileSync(path.join(__dirname, `../templates/${template}`))
682
784
  );
683
785
 
684
786
  logger.log(
@@ -697,6 +799,21 @@ export async function main(argv: string[]): Promise<void> {
697
799
  }
698
800
  }
699
801
  }
802
+ // install packages as the final step of init
803
+ try {
804
+ await installPackages(shouldRunPackageManagerInstall, devDepsToInstall);
805
+ } catch (e) {
806
+ // fetching packages could fail due to loss of internet, etc
807
+ // we should let folks know we failed to fetch, but their
808
+ // workers project is still ready to go
809
+ logger.error(e instanceof Error ? e.message : e);
810
+ instructions.push(
811
+ "\n🚨 wrangler was unable to fetch your npm packages, but your project is ready to go"
812
+ );
813
+ }
814
+
815
+ // let users know what to do now
816
+ instructions.forEach((instruction) => logger.log(instruction));
700
817
  }
701
818
  );
702
819
 
@@ -707,13 +824,34 @@ export async function main(argv: string[]): Promise<void> {
707
824
  (yargs) => {
708
825
  return yargs.option("env", {
709
826
  describe: "Perform on a specific environment",
827
+ type: "string",
710
828
  });
711
829
  },
712
- () => {
830
+ async (buildArgs) => {
713
831
  // "[DEPRECATED] 🦀 Build your project (if applicable)",
714
- throw new DeprecationError(
715
- "`wrangler build` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#build for alternatives"
832
+
833
+ const envFlag = buildArgs.env ? ` --env=${buildArgs.env}` : "";
834
+ logger.log(
835
+ formatMessage({
836
+ kind: "warning",
837
+ text: "Deprecation: `wrangler build` has been deprecated.",
838
+ notes: [
839
+ {
840
+ text: "Please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#build for more information.",
841
+ },
842
+ {
843
+ text: `Attempting to run \`wrangler publish --dry-run --outdir=dist${envFlag}\` for you instead:`,
844
+ },
845
+ ],
846
+ })
716
847
  );
848
+
849
+ await createCLIParser([
850
+ "publish",
851
+ "--dry-run",
852
+ "--outdir=dist",
853
+ ...(buildArgs.env ? ["--env", buildArgs.env] : []),
854
+ ]).parse();
717
855
  }
718
856
  );
719
857
 
@@ -725,7 +863,7 @@ export async function main(argv: string[]): Promise<void> {
725
863
  () => {
726
864
  // "🕵️ Authenticate Wrangler with a Cloudflare API Token",
727
865
  throw new DeprecationError(
728
- "`wrangler config` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#config for alternatives"
866
+ "`wrangler config` has been deprecated, please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#config for alternatives"
729
867
  );
730
868
  }
731
869
  );
@@ -869,6 +1007,11 @@ export async function main(argv: string[]): Promise<void> {
869
1007
  describe: "Enable dev tools",
870
1008
  type: "boolean",
871
1009
  deprecated: true,
1010
+ })
1011
+ .option("legacy-env", {
1012
+ type: "boolean",
1013
+ describe: "Use legacy environments",
1014
+ hidden: true,
872
1015
  });
873
1016
  },
874
1017
  async (args) => {
@@ -879,6 +1022,19 @@ export async function main(argv: string[]): Promise<void> {
879
1022
  const config = readConfig(configPath, args);
880
1023
  const entry = await getEntry(args, config, "dev");
881
1024
 
1025
+ if (config.services && config.services.length > 0) {
1026
+ logger.warn(
1027
+ `This worker is bound to live services: ${config.services
1028
+ .map(
1029
+ (service) =>
1030
+ `${service.binding} (${service.service}${
1031
+ service.environment ? `@${service.environment}` : ""
1032
+ })`
1033
+ )
1034
+ .join(", ")}`
1035
+ );
1036
+ }
1037
+
882
1038
  if (args.inspect) {
883
1039
  logger.warn(
884
1040
  "Passing --inspect is unnecessary, now you can always connect to devtools."
@@ -916,6 +1072,9 @@ export async function main(argv: string[]): Promise<void> {
916
1072
  * try to extract a host from it
917
1073
  */
918
1074
  function getHost(urlLike: string): string | undefined {
1075
+ // strip leading * / *.
1076
+ urlLike = urlLike.replace(/^\*(\.)?/g, "");
1077
+
919
1078
  if (
920
1079
  !(urlLike.startsWith("http://") || urlLike.startsWith("https://"))
921
1080
  ) {
@@ -995,10 +1154,74 @@ export async function main(argv: string[]): Promise<void> {
995
1154
  const nodeCompat = args.nodeCompat ?? config.node_compat;
996
1155
  if (nodeCompat) {
997
1156
  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."
1157
+ "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."
999
1158
  );
1000
1159
  }
1001
1160
 
1161
+ const bindings = {
1162
+ kv_namespaces: config.kv_namespaces?.map(
1163
+ ({ binding, preview_id, id: _id }) => {
1164
+ // In `dev`, we make folks use a separate kv namespace called
1165
+ // `preview_id` instead of `id` so that they don't
1166
+ // break production data. So here we check that a `preview_id`
1167
+ // has actually been configured.
1168
+ // This whole block of code will be obsoleted in the future
1169
+ // when we have copy-on-write for previews on edge workers.
1170
+ if (!preview_id) {
1171
+ // TODO: This error has to be a _lot_ better, ideally just asking
1172
+ // to create a preview namespace for the user automatically
1173
+ throw new Error(
1174
+ `In development, you should use a separate kv namespace than the one you'd use in production. Please create a new kv namespace with "wrangler kv:namespace create <name> --preview" and add its id as preview_id to the kv_namespace "${binding}" in your wrangler.toml`
1175
+ ); // Ugh, I really don't like this message very much
1176
+ }
1177
+ return {
1178
+ binding,
1179
+ id: preview_id,
1180
+ };
1181
+ }
1182
+ ),
1183
+ // Use a copy of combinedVars since we're modifying it later
1184
+ vars: getVarsForDev(config),
1185
+ wasm_modules: config.wasm_modules,
1186
+ text_blobs: config.text_blobs,
1187
+ data_blobs: config.data_blobs,
1188
+ durable_objects: config.durable_objects,
1189
+ r2_buckets: config.r2_buckets?.map(
1190
+ ({ binding, preview_bucket_name, bucket_name: _bucket_name }) => {
1191
+ // same idea as kv namespace preview id,
1192
+ // same copy-on-write TODO
1193
+ if (!preview_bucket_name) {
1194
+ throw new Error(
1195
+ `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`
1196
+ );
1197
+ }
1198
+ return {
1199
+ binding,
1200
+ bucket_name: preview_bucket_name,
1201
+ };
1202
+ }
1203
+ ),
1204
+ services: config.services,
1205
+ unsafe: config.unsafe?.bindings,
1206
+ };
1207
+
1208
+ // mask anything that was overridden in .dev.vars
1209
+ // so that we don't log potential secrets into the terminal
1210
+ const maskedVars = { ...bindings.vars };
1211
+ for (const key of Object.keys(maskedVars)) {
1212
+ if (maskedVars[key] !== config.vars[key]) {
1213
+ // This means it was overridden in .dev.vars
1214
+ // so let's mask it
1215
+ maskedVars[key] = "(hidden)";
1216
+ }
1217
+ }
1218
+
1219
+ // now log all available bindings into the terminal
1220
+ printBindings({
1221
+ ...bindings,
1222
+ vars: maskedVars,
1223
+ });
1224
+
1002
1225
  const { waitUntilExit } = render(
1003
1226
  <Dev
1004
1227
  name={getScriptName(args, config)}
@@ -1027,7 +1250,9 @@ export async function main(argv: string[]): Promise<void> {
1027
1250
  args.siteExclude
1028
1251
  )}
1029
1252
  port={
1030
- args.port || config.dev?.port || (await getPort({ port: 8787 }))
1253
+ args.port ||
1254
+ config.dev.port ||
1255
+ (await getPort({ port: DEFAULT_LOCAL_PORT }))
1031
1256
  }
1032
1257
  ip={args.ip || config.dev.ip}
1033
1258
  inspectorPort={
@@ -1042,50 +1267,7 @@ export async function main(argv: string[]): Promise<void> {
1042
1267
  args["compatibility-flags"] || config.compatibility_flags
1043
1268
  }
1044
1269
  usageModel={config.usage_model}
1045
- bindings={{
1046
- kv_namespaces: config.kv_namespaces?.map(
1047
- ({ binding, preview_id, id: _id }) => {
1048
- // In `dev`, we make folks use a separate kv namespace called
1049
- // `preview_id` instead of `id` so that they don't
1050
- // break production data. So here we check that a `preview_id`
1051
- // has actually been configured.
1052
- // This whole block of code will be obsoleted in the future
1053
- // when we have copy-on-write for previews on edge workers.
1054
- if (!preview_id) {
1055
- // TODO: This error has to be a _lot_ better, ideally just asking
1056
- // to create a preview namespace for the user automatically
1057
- throw new Error(
1058
- `In development, you should use a separate kv namespace than the one you'd use in production. Please create a new kv namespace with "wrangler kv:namespace create <name> --preview" and add its id as preview_id to the kv_namespace "${binding}" in your wrangler.toml`
1059
- ); // Ugh, I really don't like this message very much
1060
- }
1061
- return {
1062
- binding,
1063
- id: preview_id,
1064
- };
1065
- }
1066
- ),
1067
- vars: getVarsForDev(config),
1068
- wasm_modules: config.wasm_modules,
1069
- text_blobs: config.text_blobs,
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,
1088
- }}
1270
+ bindings={bindings}
1089
1271
  crons={config.triggers.crons}
1090
1272
  />
1091
1273
  );
@@ -1206,6 +1388,11 @@ export async function main(argv: string[]): Promise<void> {
1206
1388
  .option("dry-run", {
1207
1389
  describe: "Don't actually publish",
1208
1390
  type: "boolean",
1391
+ })
1392
+ .option("legacy-env", {
1393
+ type: "boolean",
1394
+ describe: "Use legacy environments",
1395
+ hidden: true,
1209
1396
  });
1210
1397
  },
1211
1398
  async (args) => {
@@ -1233,7 +1420,7 @@ export async function main(argv: string[]): Promise<void> {
1233
1420
  );
1234
1421
  }
1235
1422
 
1236
- const accountId = await requireAuth(config);
1423
+ const accountId = args.dryRun ? undefined : await requireAuth(config);
1237
1424
 
1238
1425
  const assetPaths = getAssetPaths(
1239
1426
  config,
@@ -1241,6 +1428,7 @@ export async function main(argv: string[]): Promise<void> {
1241
1428
  args.siteInclude,
1242
1429
  args.siteExclude
1243
1430
  );
1431
+
1244
1432
  await publish({
1245
1433
  config,
1246
1434
  accountId,
@@ -1327,6 +1515,11 @@ export async function main(argv: string[]): Promise<void> {
1327
1515
  default: false,
1328
1516
  describe:
1329
1517
  "If a log would have been filtered out, send it through anyway alongside the filter which would have blocked it.",
1518
+ })
1519
+ .option("legacy-env", {
1520
+ type: "boolean",
1521
+ describe: "Use legacy environments",
1522
+ hidden: true,
1330
1523
  });
1331
1524
  },
1332
1525
  async (args) => {
@@ -1476,7 +1669,9 @@ export async function main(argv: string[]): Promise<void> {
1476
1669
  enableLocalPersistence={false}
1477
1670
  accountId={accountId}
1478
1671
  assetPaths={undefined}
1479
- port={config.dev?.port}
1672
+ port={
1673
+ config.dev.port || (await getPort({ port: DEFAULT_LOCAL_PORT }))
1674
+ }
1480
1675
  ip={config.dev.ip}
1481
1676
  public={undefined}
1482
1677
  compatibilityDate={getDevCompatibilityDate(config)}
@@ -1524,6 +1719,7 @@ export async function main(argv: string[]): Promise<void> {
1524
1719
  };
1525
1720
  }
1526
1721
  ),
1722
+ services: config.services,
1527
1723
  unsafe: config.unsafe?.bindings,
1528
1724
  }}
1529
1725
  crons={config.triggers.crons}
@@ -1623,7 +1819,7 @@ export async function main(argv: string[]): Promise<void> {
1623
1819
  },
1624
1820
  () => {
1625
1821
  throw new DeprecationError(
1626
- "`wrangler subdomain` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#subdomain for alternatives"
1822
+ "`wrangler subdomain` has been deprecated, please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#subdomain for alternatives"
1627
1823
  );
1628
1824
  }
1629
1825
  );
@@ -1635,6 +1831,11 @@ export async function main(argv: string[]): Promise<void> {
1635
1831
  (secretYargs) => {
1636
1832
  return secretYargs
1637
1833
  .command(subHelp)
1834
+ .option("legacy-env", {
1835
+ type: "boolean",
1836
+ describe: "Use legacy environments",
1837
+ hidden: true,
1838
+ })
1638
1839
  .command(
1639
1840
  "put <key>",
1640
1841
  "Create or update a secret variable for a script",
@@ -1669,9 +1870,11 @@ export async function main(argv: string[]): Promise<void> {
1669
1870
  const accountId = await requireAuth(config);
1670
1871
 
1671
1872
  const isInteractive = process.stdin.isTTY;
1672
- const secretValue = isInteractive
1673
- ? await prompt("Enter a secret value:", "password")
1674
- : await readFromStdin();
1873
+ const secretValue = trimTrailingWhitespace(
1874
+ isInteractive
1875
+ ? await prompt("Enter a secret value:", "password")
1876
+ : await readFromStdin()
1877
+ );
1675
1878
 
1676
1879
  logger.log(
1677
1880
  `🌀 Creating the secret for script ${scriptName} ${
@@ -1716,6 +1919,7 @@ export async function main(argv: string[]): Promise<void> {
1716
1919
  vars: {},
1717
1920
  durable_objects: { bindings: [] },
1718
1921
  r2_buckets: [],
1922
+ services: [],
1719
1923
  wasm_modules: {},
1720
1924
  text_blobs: {},
1721
1925
  data_blobs: {},
@@ -1884,7 +2088,7 @@ export async function main(argv: string[]): Promise<void> {
1884
2088
  async (args) => {
1885
2089
  await printWranglerBanner();
1886
2090
 
1887
- if (!isValidNamespaceBinding(args.namespace)) {
2091
+ if (!isValidKVNamespaceBinding(args.namespace)) {
1888
2092
  throw new CommandLineArgsError(
1889
2093
  `The namespace binding name "${args.namespace}" is invalid. It can only have alphanumeric and _ characters, and cannot begin with a number.`
1890
2094
  );
@@ -1907,7 +2111,7 @@ export async function main(argv: string[]): Promise<void> {
1907
2111
  // TODO: generate a binding name stripping non alphanumeric chars
1908
2112
 
1909
2113
  logger.log(`🌀 Creating namespace with title "${title}"`);
1910
- const namespaceId = await createNamespace(accountId, title);
2114
+ const namespaceId = await createKVNamespace(accountId, title);
1911
2115
 
1912
2116
  logger.log("✨ Success!");
1913
2117
  const envString = args.env ? ` under [env.${args.env}]` : "";
@@ -1934,7 +2138,7 @@ export async function main(argv: string[]): Promise<void> {
1934
2138
  // TODO: we should show bindings if they exist for given ids
1935
2139
 
1936
2140
  logger.log(
1937
- JSON.stringify(await listNamespaces(accountId), null, " ")
2141
+ JSON.stringify(await listKVNamespaces(accountId), null, " ")
1938
2142
  );
1939
2143
  }
1940
2144
  )
@@ -1971,7 +2175,7 @@ export async function main(argv: string[]): Promise<void> {
1971
2175
 
1972
2176
  let id;
1973
2177
  try {
1974
- id = getNamespaceId(args, config);
2178
+ id = getKVNamespaceId(args, config);
1975
2179
  } catch (e) {
1976
2180
  throw new CommandLineArgsError(
1977
2181
  "Not able to delete namespace.\n" + ((e as Error).message ?? e)
@@ -1980,10 +2184,7 @@ export async function main(argv: string[]): Promise<void> {
1980
2184
 
1981
2185
  const accountId = await requireAuth(config);
1982
2186
 
1983
- await fetchResult<{ id: string }>(
1984
- `/accounts/${accountId}/storage/kv/namespaces/${id}`,
1985
- { method: "DELETE" }
1986
- );
2187
+ await deleteKVNamespace(accountId, id);
1987
2188
 
1988
2189
  // TODO: recommend they remove it from wrangler.toml
1989
2190
 
@@ -2068,7 +2269,7 @@ export async function main(argv: string[]): Promise<void> {
2068
2269
  async ({ key, ttl, expiration, ...args }) => {
2069
2270
  await printWranglerBanner();
2070
2271
  const config = readConfig(args.config as ConfigPath, args);
2071
- const namespaceId = getNamespaceId(args, config);
2272
+ const namespaceId = getKVNamespaceId(args, config);
2072
2273
  // One of `args.path` and `args.value` must be defined
2073
2274
  const value = args.path
2074
2275
  ? readFileSync(args.path)
@@ -2087,7 +2288,7 @@ export async function main(argv: string[]): Promise<void> {
2087
2288
 
2088
2289
  const accountId = await requireAuth(config);
2089
2290
 
2090
- await putKeyValue(accountId, namespaceId, {
2291
+ await putKVKeyValue(accountId, namespaceId, {
2091
2292
  key,
2092
2293
  value,
2093
2294
  expiration,
@@ -2132,11 +2333,11 @@ export async function main(argv: string[]): Promise<void> {
2132
2333
  async ({ prefix, ...args }) => {
2133
2334
  // TODO: support for limit+cursor (pagination)
2134
2335
  const config = readConfig(args.config as ConfigPath, args);
2135
- const namespaceId = getNamespaceId(args, config);
2336
+ const namespaceId = getKVNamespaceId(args, config);
2136
2337
 
2137
2338
  const accountId = await requireAuth(config);
2138
2339
 
2139
- const results = await listNamespaceKeys(
2340
+ const results = await listKVNamespaceKeys(
2140
2341
  accountId,
2141
2342
  namespaceId,
2142
2343
  prefix
@@ -2184,11 +2385,11 @@ export async function main(argv: string[]): Promise<void> {
2184
2385
  },
2185
2386
  async ({ key, ...args }) => {
2186
2387
  const config = readConfig(args.config as ConfigPath, args);
2187
- const namespaceId = getNamespaceId(args, config);
2388
+ const namespaceId = getKVNamespaceId(args, config);
2188
2389
 
2189
2390
  const accountId = await requireAuth(config);
2190
2391
 
2191
- logger.log(await getKeyValue(accountId, namespaceId, key));
2392
+ logger.log(await getKVKeyValue(accountId, namespaceId, key));
2192
2393
  }
2193
2394
  )
2194
2395
  .command(
@@ -2226,7 +2427,7 @@ export async function main(argv: string[]): Promise<void> {
2226
2427
  async ({ key, ...args }) => {
2227
2428
  await printWranglerBanner();
2228
2429
  const config = readConfig(args.config as ConfigPath, args);
2229
- const namespaceId = getNamespaceId(args, config);
2430
+ const namespaceId = getKVNamespaceId(args, config);
2230
2431
 
2231
2432
  logger.log(
2232
2433
  `Deleting the key "${key}" on namespace ${namespaceId}.`
@@ -2234,10 +2435,7 @@ export async function main(argv: string[]): Promise<void> {
2234
2435
 
2235
2436
  const accountId = await requireAuth(config);
2236
2437
 
2237
- await fetchResult(
2238
- `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`,
2239
- { method: "DELETE" }
2240
- );
2438
+ await deleteKVKeyValue(accountId, namespaceId, key);
2241
2439
  }
2242
2440
  );
2243
2441
  }
@@ -2289,7 +2487,7 @@ export async function main(argv: string[]): Promise<void> {
2289
2487
  // but we'll do that in the future if needed.
2290
2488
 
2291
2489
  const config = readConfig(args.config as ConfigPath, args);
2292
- const namespaceId = getNamespaceId(args, config);
2490
+ const namespaceId = getKVNamespaceId(args, config);
2293
2491
  const content = parseJSON(readFileSync(filename), filename);
2294
2492
 
2295
2493
  if (!Array.isArray(content)) {
@@ -2303,18 +2501,12 @@ export async function main(argv: string[]): Promise<void> {
2303
2501
  const warnings: string[] = [];
2304
2502
  for (let i = 0; i < content.length; i++) {
2305
2503
  const keyValue = content[i];
2306
- if (typeof keyValue !== "object") {
2307
- errors.push(
2308
- `The item at index ${i} is type: "${typeof keyValue}" - ${JSON.stringify(
2309
- keyValue
2310
- )}`
2311
- );
2312
- } else if (!isKeyValue(keyValue)) {
2504
+ if (!isKVKeyValue(keyValue)) {
2313
2505
  errors.push(
2314
2506
  `The item at index ${i} is ${JSON.stringify(keyValue)}`
2315
2507
  );
2316
2508
  } else {
2317
- const props = unexpectedKeyValueProps(keyValue);
2509
+ const props = unexpectedKVKeyValueProps(keyValue);
2318
2510
  if (props.length > 0) {
2319
2511
  warnings.push(
2320
2512
  `The item at index ${i} contains unexpected properties: ${JSON.stringify(
@@ -2347,14 +2539,7 @@ export async function main(argv: string[]): Promise<void> {
2347
2539
  }
2348
2540
 
2349
2541
  const accountId = await requireAuth(config);
2350
- await putBulkKeyValue(
2351
- accountId,
2352
- namespaceId,
2353
- content,
2354
- (index, total) => {
2355
- logger.log(`Uploaded ${index} of ${total}.`);
2356
- }
2357
- );
2542
+ await putKVBulkKeyValue(accountId, namespaceId, content);
2358
2543
 
2359
2544
  logger.log("Success!");
2360
2545
  }
@@ -2399,7 +2584,7 @@ export async function main(argv: string[]): Promise<void> {
2399
2584
  async ({ filename, ...args }) => {
2400
2585
  await printWranglerBanner();
2401
2586
  const config = readConfig(args.config as ConfigPath, args);
2402
- const namespaceId = getNamespaceId(args, config);
2587
+ const namespaceId = getKVNamespaceId(args, config);
2403
2588
 
2404
2589
  if (!args.force) {
2405
2590
  const result = await confirm(
@@ -2444,14 +2629,7 @@ export async function main(argv: string[]): Promise<void> {
2444
2629
 
2445
2630
  const accountId = await requireAuth(config);
2446
2631
 
2447
- await deleteBulkKeyValue(
2448
- accountId,
2449
- namespaceId,
2450
- content,
2451
- (index, total) => {
2452
- logger.log(`Deleted ${index} of ${total}.`);
2453
- }
2454
- );
2632
+ await deleteKVBulkKeyValue(accountId, namespaceId, content);
2455
2633
 
2456
2634
  logger.log("Success!");
2457
2635
  }
@@ -2604,41 +2782,43 @@ export async function main(argv: string[]): Promise<void> {
2604
2782
  }
2605
2783
  );
2606
2784
 
2607
- wrangler
2608
- .option("legacy-env", {
2609
- type: "boolean",
2610
- describe: "Use legacy environments",
2611
- })
2612
- .option("config", {
2613
- alias: "c",
2614
- describe: "Path to .toml configuration file",
2615
- type: "string",
2616
- requiresArg: true,
2617
- });
2785
+ wrangler.option("config", {
2786
+ alias: "c",
2787
+ describe: "Path to .toml configuration file",
2788
+ type: "string",
2789
+ requiresArg: true,
2790
+ });
2618
2791
 
2619
- wrangler.group(["config", "help", "version", "legacy-env"], "Flags:");
2792
+ wrangler.group(["config", "help", "version"], "Flags:");
2620
2793
  wrangler.help().alias("h", "help");
2621
2794
  wrangler.version(wranglerVersion).alias("v", "version");
2622
2795
  wrangler.exitProcess(false);
2623
2796
 
2797
+ return wrangler;
2798
+ }
2799
+
2800
+ export async function main(argv: string[]): Promise<void> {
2801
+ const wrangler = createCLIParser(argv);
2624
2802
  try {
2625
2803
  await wrangler.parse();
2626
2804
  } catch (e) {
2627
2805
  logger.log(""); // Just adds a bit of space
2628
2806
  if (e instanceof CommandLineArgsError) {
2629
- wrangler.showHelp("error");
2630
- logger.log(""); // Add a bit of space.
2631
2807
  logger.error(e.message);
2808
+ // We are not able to ask the `wrangler` CLI parser to show help for a subcommand programmatically.
2809
+ // The workaround is to re-run the parsing with an additional `--help` flag, which will result in the correct help message being displayed.
2810
+ // 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.
2811
+ await createCLIParser([...argv, "--help"]).parse();
2632
2812
  } else if (e instanceof ParseError) {
2633
2813
  e.notes.push({
2634
- text: "\nIf you think this is a bug, please open an issue at: https://github.com/cloudflare/wrangler2/issues/new",
2814
+ text: "\nIf you think this is a bug, please open an issue at: https://github.com/cloudflare/wrangler2/issues/new/choose",
2635
2815
  });
2636
- logger.error(formatMessage(e));
2816
+ logger.log(formatMessage(e));
2637
2817
  } else {
2638
2818
  logger.error(e instanceof Error ? e.message : e);
2639
2819
  logger.log(
2640
2820
  `${fgGreenColor}%s${resetColor}`,
2641
- "If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
2821
+ "If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
2642
2822
  );
2643
2823
  }
2644
2824
  throw e;