wrangler 0.0.0-e2f9bb2 → 0.0.0-e322475

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 (103) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +3 -1
  3. package/bin/wrangler.js +36 -4
  4. package/kv-asset-handler.js +1 -0
  5. package/package.json +15 -7
  6. package/pages/functions/buildPlugin.ts +13 -0
  7. package/pages/functions/buildWorker.ts +14 -1
  8. package/pages/functions/template-plugin.ts +3 -2
  9. package/src/__tests__/configuration.test.ts +652 -229
  10. package/src/__tests__/dev.test.tsx +372 -79
  11. package/src/__tests__/helpers/mock-cfetch.ts +2 -2
  12. package/src/__tests__/helpers/mock-console.ts +11 -1
  13. package/src/__tests__/helpers/mock-dialogs.ts +75 -11
  14. package/src/__tests__/helpers/mock-process.ts +24 -0
  15. package/src/__tests__/index.test.ts +81 -72
  16. package/src/__tests__/init.test.ts +1360 -410
  17. package/src/__tests__/jest.setup.ts +38 -0
  18. package/src/__tests__/kv.test.ts +218 -152
  19. package/src/__tests__/logger.test.ts +26 -0
  20. package/src/__tests__/package-manager.test.ts +154 -7
  21. package/src/__tests__/pages.test.ts +478 -53
  22. package/src/__tests__/parse.test.ts +8 -0
  23. package/src/__tests__/publish.test.ts +1614 -333
  24. package/src/__tests__/r2.test.ts +55 -36
  25. package/src/__tests__/secret.test.ts +39 -4
  26. package/src/__tests__/user.test.ts +1 -0
  27. package/src/__tests__/whoami.test.tsx +34 -0
  28. package/src/abort.d.ts +3 -0
  29. package/src/bundle-reporter.tsx +29 -0
  30. package/src/bundle.ts +42 -7
  31. package/src/cfetch/index.ts +21 -4
  32. package/src/cfetch/internal.ts +25 -20
  33. package/src/config/config.ts +11 -2
  34. package/src/config/environment.ts +41 -15
  35. package/src/config/index.ts +162 -0
  36. package/src/config/validation-helpers.ts +10 -1
  37. package/src/config/validation.ts +234 -78
  38. package/src/create-worker-preview.ts +32 -22
  39. package/src/create-worker-upload-form.ts +22 -8
  40. package/src/dev/dev.tsx +48 -67
  41. package/src/dev/local.tsx +16 -7
  42. package/src/dev/remote.tsx +57 -21
  43. package/src/dev/use-esbuild.ts +1 -7
  44. package/src/dialogs.tsx +48 -0
  45. package/src/durable.ts +102 -0
  46. package/src/entry.ts +71 -10
  47. package/src/generate-auth-url.ts +33 -0
  48. package/src/generate-random-state.ts +16 -0
  49. package/src/git-client.ts +42 -0
  50. package/src/index.tsx +619 -338
  51. package/src/inspect.ts +39 -0
  52. package/src/kv.ts +116 -27
  53. package/src/logger.ts +8 -1
  54. package/src/open-in-browser.ts +3 -12
  55. package/src/package-manager.ts +50 -3
  56. package/src/pages.tsx +434 -207
  57. package/src/parse.ts +44 -5
  58. package/src/proxy.ts +66 -44
  59. package/src/publish.ts +407 -137
  60. package/src/sites.tsx +103 -28
  61. package/src/user.tsx +35 -26
  62. package/src/whoami.tsx +3 -2
  63. package/src/worker.ts +10 -1
  64. package/src/zones.ts +73 -0
  65. package/templates/new-worker-scheduled.js +17 -0
  66. package/templates/new-worker-scheduled.ts +32 -0
  67. package/templates/new-worker.ts +16 -1
  68. package/templates/no-op-worker.js +10 -0
  69. package/templates/static-asset-facade.js +2 -6
  70. package/wrangler-dist/cli.js +46122 -32396
  71. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  72. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  73. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  74. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  75. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  76. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  77. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  78. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  79. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  80. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  81. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  82. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  83. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  84. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  85. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  86. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  87. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  88. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  89. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  90. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  91. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  92. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  93. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  94. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  95. package/vendor/wrangler-mime/LICENSE +0 -21
  96. package/vendor/wrangler-mime/Mime.js +0 -97
  97. package/vendor/wrangler-mime/README.md +0 -187
  98. package/vendor/wrangler-mime/cli.js +0 -46
  99. package/vendor/wrangler-mime/index.js +0 -4
  100. package/vendor/wrangler-mime/lite.js +0 -4
  101. package/vendor/wrangler-mime/package.json +0 -52
  102. package/vendor/wrangler-mime/types/other.js +0 -1
  103. package/vendor/wrangler-mime/types/standard.js +0 -1
package/src/index.tsx CHANGED
@@ -4,35 +4,39 @@ import path from "node:path";
4
4
  import { setTimeout } from "node:timers/promises";
5
5
  import TOML from "@iarna/toml";
6
6
  import chalk from "chalk";
7
- import { execa } from "execa";
7
+ import { watch } from "chokidar";
8
8
  import { findUp } from "find-up";
9
9
  import getPort from "get-port";
10
10
  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";
25
+ import { initializeGit, isGitInstalled, isInsideGitRepo } from "./git-client";
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,12 +45,14 @@ import {
41
45
  formatMessage,
42
46
  ParseError,
43
47
  parseJSON,
48
+ parsePackageJSON,
44
49
  parseTOML,
45
50
  readFileSync,
51
+ readFileSyncToBuffer,
46
52
  } from "./parse";
47
53
  import publish from "./publish";
48
54
  import { createR2Bucket, deleteR2Bucket, listR2Buckets } from "./r2";
49
- import { getAssetPaths } from "./sites";
55
+ import { getAssetPaths, getSiteAssetPaths } from "./sites";
50
56
  import {
51
57
  createTail,
52
58
  jsonPrintLogs,
@@ -62,6 +68,7 @@ import {
62
68
  requireAuth,
63
69
  } from "./user";
64
70
  import { whoami } from "./whoami";
71
+ import { getZoneIdFromHost, getZoneForRoute } from "./zones";
65
72
 
66
73
  import type { Config } from "./config";
67
74
  import type { TailCLIFilters } from "./tail";
@@ -73,6 +80,18 @@ type ConfigPath = string | undefined;
73
80
 
74
81
  const resetColor = "\x1b[0m";
75
82
  const fgGreenColor = "\x1b[32m";
83
+ const DEFAULT_LOCAL_PORT = 8787;
84
+
85
+ const proxy =
86
+ process.env.https_proxy ||
87
+ process.env.HTTPS_PROXY ||
88
+ process.env.http_proxy ||
89
+ process.env.HTTP_PROXY ||
90
+ undefined;
91
+
92
+ if (proxy) {
93
+ setGlobalDispatcher(new ProxyAgent(proxy));
94
+ }
76
95
 
77
96
  function getRules(config: Config): Config["rules"] {
78
97
  const rules = config.rules ?? config.build?.upload?.rules ?? [];
@@ -203,9 +222,17 @@ function demandOneOfOption(...options: string[]) {
203
222
  };
204
223
  }
205
224
 
225
+ /**
226
+ * Remove trailing white space from inputs.
227
+ * Matching Wrangler legacy behavior with handling inputs
228
+ */
229
+ function trimTrailingWhitespace(str: string) {
230
+ return str.trimEnd();
231
+ }
232
+
206
233
  class CommandLineArgsError extends Error {}
207
234
 
208
- export async function main(argv: string[]): Promise<void> {
235
+ function createCLIParser(argv: string[]) {
209
236
  const wrangler = makeCLI(argv)
210
237
  .strict()
211
238
  // We handle errors ourselves in a try-catch around `yargs.parse`.
@@ -322,6 +349,9 @@ export async function main(argv: string[]): Promise<void> {
322
349
  throw new CommandLineArgsError(message);
323
350
  }
324
351
 
352
+ const devDepsToInstall: string[] = [];
353
+ const instructions: string[] = [];
354
+ let shouldRunPackageManagerInstall = false;
325
355
  const creationDirectory = path.resolve(process.cwd(), args.name ?? "");
326
356
 
327
357
  if (args.site) {
@@ -359,7 +389,12 @@ export async function main(argv: string[]): Promise<void> {
359
389
  let justCreatedWranglerToml = false;
360
390
 
361
391
  if (fs.existsSync(wranglerTomlDestination)) {
362
- logger.warn(`${wranglerTomlDestination} file already exists!`);
392
+ logger.warn(
393
+ `${path.relative(
394
+ process.cwd(),
395
+ wranglerTomlDestination
396
+ )} already exists!`
397
+ );
363
398
  const shouldContinue = await confirm(
364
399
  "Do you want to continue initializing this project?"
365
400
  );
@@ -379,34 +414,55 @@ export async function main(argv: string[]): Promise<void> {
379
414
  }) + "\n"
380
415
  );
381
416
 
382
- logger.log(`✨ Successfully created wrangler.toml`);
417
+ logger.log(
418
+ `✨ Created ${path.relative(
419
+ process.cwd(),
420
+ wranglerTomlDestination
421
+ )}`
422
+ );
383
423
  justCreatedWranglerToml = true;
384
424
  } catch (err) {
385
425
  throw new Error(
386
- `Failed to create wrangler.toml.\n${(err as Error).message ?? err}`
426
+ `Failed to create ${path.relative(
427
+ process.cwd(),
428
+ wranglerTomlDestination
429
+ )}.\n${(err as Error).message ?? err}`
387
430
  );
388
431
  }
389
432
  }
390
433
 
391
434
  const yesFlag = args.yes ?? false;
392
435
 
393
- const isInsideGitProject = Boolean(await findUp(".git"));
394
- const isGitInstalled = (await execa("git", ["--version"])).exitCode === 0;
395
- if (!isInsideGitProject && isGitInstalled) {
436
+ if (
437
+ !(await isInsideGitRepo(creationDirectory)) &&
438
+ (await isGitInstalled())
439
+ ) {
396
440
  const shouldInitGit =
397
441
  yesFlag ||
398
442
  (await confirm("Would you like to use git to manage this Worker?"));
399
443
  if (shouldInitGit) {
400
- await execa("git", ["init"], { cwd: creationDirectory });
444
+ await initializeGit(creationDirectory);
401
445
  await writeFile(
402
446
  path.join(creationDirectory, ".gitignore"),
403
447
  readFileSync(path.join(__dirname, "../templates/gitignore"))
404
448
  );
405
- logger.log(`✨ Initialized git repository`);
449
+ logger.log(
450
+ args.name && args.name !== "."
451
+ ? `✨ Initialized git repository at ${path.relative(
452
+ process.cwd(),
453
+ creationDirectory
454
+ )}`
455
+ : `✨ Initialized git repository`
456
+ );
406
457
  }
407
458
  }
408
459
 
409
- let pathToPackageJson = await findUp("package.json");
460
+ const isolatedInit = !!args.name;
461
+ let pathToPackageJson = await findPath(
462
+ isolatedInit,
463
+ creationDirectory,
464
+ "package.json"
465
+ );
410
466
  let shouldCreatePackageJson = false;
411
467
 
412
468
  if (!pathToPackageJson) {
@@ -434,16 +490,18 @@ export async function main(argv: string[]): Promise<void> {
434
490
  ) + "\n"
435
491
  );
436
492
 
437
- await packageManager.install();
438
- logger.log(`✨ Created package.json`);
493
+ shouldRunPackageManagerInstall = true;
439
494
  pathToPackageJson = path.join(creationDirectory, "package.json");
495
+ logger.log(
496
+ `✨ Created ${path.relative(process.cwd(), pathToPackageJson)}`
497
+ );
440
498
  } else {
441
499
  return;
442
500
  }
443
501
  } else {
444
502
  // If package.json exists and wrangler isn't installed,
445
503
  // then ask to add wrangler to devDependencies
446
- const packageJson = parseJSON(
504
+ const packageJson = parsePackageJSON(
447
505
  readFileSync(pathToPackageJson),
448
506
  pathToPackageJson
449
507
  );
@@ -456,17 +514,23 @@ export async function main(argv: string[]): Promise<void> {
456
514
  const shouldInstall =
457
515
  yesFlag ||
458
516
  (await confirm(
459
- "Would you like to install wrangler into your package.json?"
517
+ `Would you like to install wrangler into ${path.relative(
518
+ process.cwd(),
519
+ pathToPackageJson
520
+ )}?`
460
521
  ));
461
522
  if (shouldInstall) {
462
- await packageManager.addDevDeps(`wrangler@${wranglerVersion}`);
463
- logger.log(`✨ Installed wrangler`);
523
+ devDepsToInstall.push(`wrangler@${wranglerVersion}`);
464
524
  }
465
525
  }
466
526
  }
467
527
 
468
528
  let isTypescriptProject = false;
469
- let pathToTSConfig = await findUp("tsconfig.json");
529
+ let pathToTSConfig = await findPath(
530
+ isolatedInit,
531
+ creationDirectory,
532
+ "tsconfig.json"
533
+ );
470
534
  if (!pathToTSConfig) {
471
535
  // If there's no tsconfig, offer to create one
472
536
  // and install @cloudflare/workers-types
@@ -476,21 +540,18 @@ export async function main(argv: string[]): Promise<void> {
476
540
  path.join(creationDirectory, "./tsconfig.json"),
477
541
  readFileSync(path.join(__dirname, "../templates/tsconfig.json"))
478
542
  );
479
- await packageManager.addDevDeps(
480
- "@cloudflare/workers-types",
481
- "typescript"
482
- );
483
-
543
+ devDepsToInstall.push("@cloudflare/workers-types");
544
+ devDepsToInstall.push("typescript");
545
+ pathToTSConfig = path.join(creationDirectory, "tsconfig.json");
484
546
  logger.log(
485
- `✨ Created tsconfig.json, installed @cloudflare/workers-types into devDependencies`
547
+ `✨ Created ${path.relative(process.cwd(), pathToTSConfig)}`
486
548
  );
487
- pathToTSConfig = path.join(creationDirectory, "tsconfig.json");
488
549
  }
489
550
  } else {
490
551
  isTypescriptProject = true;
491
552
  // If there's a tsconfig, check if @cloudflare/workers-types
492
553
  // is already installed, and offer to install it if not
493
- const packageJson = parseJSON(
554
+ const packageJson = parsePackageJSON(
494
555
  readFileSync(pathToPackageJson),
495
556
  pathToPackageJson
496
557
  );
@@ -504,19 +565,22 @@ export async function main(argv: string[]): Promise<void> {
504
565
  "Would you like to install the type definitions for Workers into your package.json?"
505
566
  );
506
567
  if (shouldInstall) {
507
- await packageManager.addDevDeps("@cloudflare/workers-types");
568
+ devDepsToInstall.push("@cloudflare/workers-types");
508
569
  // We don't update the tsconfig.json because
509
570
  // it could be complicated in existing projects
510
571
  // and we don't want to break them. Instead, we simply
511
572
  // 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`
573
+ instructions.push(
574
+ `🚨 Please add "@cloudflare/workers-types" to compilerOptions.types in ${path.relative(
575
+ process.cwd(),
576
+ pathToTSConfig
577
+ )}`
514
578
  );
515
579
  }
516
580
  }
517
581
  }
518
582
 
519
- const packageJsonContent = parseJSON(
583
+ const packageJsonContent = parsePackageJSON(
520
584
  readFileSync(pathToPackageJson),
521
585
  pathToPackageJson
522
586
  );
@@ -525,25 +589,60 @@ export async function main(argv: string[]): Promise<void> {
525
589
  !packageJsonContent.scripts?.publish &&
526
590
  shouldCreatePackageJson;
527
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
+
528
627
  async function writePackageJsonScriptsAndUpdateWranglerToml(
529
628
  isWritingScripts: boolean,
530
629
  isCreatingWranglerToml: boolean,
531
630
  packagePath: string,
532
- scriptPath: string
631
+ scriptPath: string,
632
+ extraToml: TOML.JsonMap
533
633
  ) {
534
634
  if (isCreatingWranglerToml) {
535
- // rewrite wrangler.toml with main = "path/to/script"
635
+ // rewrite wrangler.toml with main = "path/to/script" and any additional config specified in `extraToml`
536
636
  const parsedWranglerToml = parseTOML(
537
637
  readFileSync(wranglerTomlDestination)
538
638
  );
539
- fs.writeFileSync(
540
- wranglerTomlDestination,
541
- TOML.stringify({
542
- name: parsedWranglerToml.name,
543
- main: scriptPath,
544
- compatibility_date: parsedWranglerToml.compatibility_date,
545
- })
546
- );
639
+ const newToml = {
640
+ name: parsedWranglerToml.name,
641
+ main: scriptPath,
642
+ compatibility_date: parsedWranglerToml.compatibility_date,
643
+ ...extraToml,
644
+ };
645
+ fs.writeFileSync(wranglerTomlDestination, TOML.stringify(newToml));
547
646
  }
548
647
  const isNamedWorker =
549
648
  isCreatingWranglerToml && path.dirname(packagePath) !== process.cwd();
@@ -559,7 +658,7 @@ export async function main(argv: string[]): Promise<void> {
559
658
  start: isCreatingWranglerToml
560
659
  ? `wrangler dev`
561
660
  : `wrangler dev ${scriptPath}`,
562
- publish: isCreatingWranglerToml
661
+ deploy: isCreatingWranglerToml
563
662
  ? `wrangler publish`
564
663
  : `wrangler publish ${scriptPath}`,
565
664
  },
@@ -568,21 +667,21 @@ export async function main(argv: string[]): Promise<void> {
568
667
  2
569
668
  ) + "\n"
570
669
  );
571
- logger.log(
670
+ instructions.push(
572
671
  `\nTo start developing your Worker, run \`${
573
672
  isNamedWorker ? `cd ${args.name} && ` : ""
574
673
  }npm start\``
575
674
  );
576
- logger.log(
577
- `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\``
578
677
  );
579
678
  } else {
580
- logger.log(
679
+ instructions.push(
581
680
  `\nTo start developing your Worker, run \`npx wrangler dev\`${
582
681
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
583
682
  }`
584
683
  );
585
- logger.log(
684
+ instructions.push(
586
685
  `To publish your Worker to the Internet, run \`npx wrangler publish\`${
587
686
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
588
687
  }`
@@ -590,60 +689,147 @@ export async function main(argv: string[]): Promise<void> {
590
689
  }
591
690
  }
592
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
+
727
+ function getNewWorkerToml(
728
+ workerType: "fetch" | "scheduled"
729
+ ): TOML.JsonMap {
730
+ if (workerType === "scheduled") {
731
+ return {
732
+ triggers: {
733
+ crons: ["1 * * * *"],
734
+ },
735
+ };
736
+ }
737
+
738
+ return {};
739
+ }
740
+
593
741
  if (isTypescriptProject) {
594
742
  if (!fs.existsSync(path.join(creationDirectory, "./src/index.ts"))) {
595
- let shouldCreateSource = false;
743
+ const newWorkerFilename = path.relative(
744
+ process.cwd(),
745
+ path.join(creationDirectory, "./src/index.ts")
746
+ );
596
747
 
597
- shouldCreateSource =
598
- yesFlag ||
599
- (await confirm(
600
- `Would you like to create a Worker at src/index.ts?`
601
- ));
748
+ const newWorkerType = yesFlag
749
+ ? "fetch"
750
+ : await getNewWorkerType(newWorkerFilename);
751
+
752
+ if (newWorkerType !== "none") {
753
+ const template = getNewWorkerTemplate("ts", newWorkerType);
602
754
 
603
- if (shouldCreateSource) {
604
755
  await mkdir(path.join(creationDirectory, "./src"), {
605
756
  recursive: true,
606
757
  });
607
758
  await writeFile(
608
759
  path.join(creationDirectory, "./src/index.ts"),
609
- readFileSync(path.join(__dirname, "../templates/new-worker.ts"))
760
+ readFileSync(path.join(__dirname, `../templates/${template}`))
610
761
  );
611
762
 
612
- logger.log(`✨ Created src/index.ts`);
763
+ logger.log(
764
+ `✨ Created ${path.relative(
765
+ process.cwd(),
766
+ path.join(creationDirectory, "./src/index.ts")
767
+ )}`
768
+ );
613
769
 
614
770
  await writePackageJsonScriptsAndUpdateWranglerToml(
615
771
  shouldWritePackageJsonScripts,
616
772
  justCreatedWranglerToml,
617
773
  pathToPackageJson,
618
- "src/index.ts"
774
+ "src/index.ts",
775
+ getNewWorkerToml(newWorkerType)
619
776
  );
620
777
  }
621
778
  }
622
779
  } 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?`
780
+ if (!fs.existsSync(path.join(creationDirectory, "./src/index.js"))) {
781
+ const newWorkerFilename = path.relative(
782
+ process.cwd(),
783
+ path.join(creationDirectory, "./src/index.js")
626
784
  );
627
- if (shouldCreateSource) {
785
+
786
+ const newWorkerType = yesFlag
787
+ ? "fetch"
788
+ : await getNewWorkerType(newWorkerFilename);
789
+
790
+ if (newWorkerType !== "none") {
791
+ const template = getNewWorkerTemplate("js", newWorkerType);
792
+
628
793
  await mkdir(path.join(creationDirectory, "./src"), {
629
794
  recursive: true,
630
795
  });
631
796
  await writeFile(
632
- path.join(path.join(creationDirectory, "./src/index.js")),
633
- readFileSync(path.join(__dirname, "../templates/new-worker.js"))
797
+ path.join(creationDirectory, "./src/index.js"),
798
+ readFileSync(path.join(__dirname, `../templates/${template}`))
634
799
  );
635
800
 
636
- logger.log(`✨ Created src/index.js`);
801
+ logger.log(
802
+ `✨ Created ${path.relative(
803
+ process.cwd(),
804
+ path.join(creationDirectory, "./src/index.js")
805
+ )}`
806
+ );
637
807
 
638
808
  await writePackageJsonScriptsAndUpdateWranglerToml(
639
809
  shouldWritePackageJsonScripts,
640
810
  justCreatedWranglerToml,
641
811
  pathToPackageJson,
642
- "src/index.js"
812
+ "src/index.js",
813
+ getNewWorkerToml(newWorkerType)
643
814
  );
644
815
  }
645
816
  }
646
817
  }
818
+ // install packages as the final step of init
819
+ try {
820
+ await installPackages(shouldRunPackageManagerInstall, devDepsToInstall);
821
+ } catch (e) {
822
+ // fetching packages could fail due to loss of internet, etc
823
+ // we should let folks know we failed to fetch, but their
824
+ // workers project is still ready to go
825
+ logger.error(e instanceof Error ? e.message : e);
826
+ instructions.push(
827
+ "\n🚨 wrangler was unable to fetch your npm packages, but your project is ready to go"
828
+ );
829
+ }
830
+
831
+ // let users know what to do now
832
+ instructions.forEach((instruction) => logger.log(instruction));
647
833
  }
648
834
  );
649
835
 
@@ -654,13 +840,34 @@ export async function main(argv: string[]): Promise<void> {
654
840
  (yargs) => {
655
841
  return yargs.option("env", {
656
842
  describe: "Perform on a specific environment",
843
+ type: "string",
657
844
  });
658
845
  },
659
- () => {
846
+ async (buildArgs) => {
660
847
  // "[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"
848
+
849
+ const envFlag = buildArgs.env ? ` --env=${buildArgs.env}` : "";
850
+ logger.log(
851
+ formatMessage({
852
+ kind: "warning",
853
+ text: "Deprecation: `wrangler build` has been deprecated.",
854
+ notes: [
855
+ {
856
+ text: "Please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#build for more information.",
857
+ },
858
+ {
859
+ text: `Attempting to run \`wrangler publish --dry-run --outdir=dist${envFlag}\` for you instead:`,
860
+ },
861
+ ],
862
+ })
663
863
  );
864
+
865
+ await createCLIParser([
866
+ "publish",
867
+ "--dry-run",
868
+ "--outdir=dist",
869
+ ...(buildArgs.env ? ["--env", buildArgs.env] : []),
870
+ ]).parse();
664
871
  }
665
872
  );
666
873
 
@@ -672,7 +879,7 @@ export async function main(argv: string[]): Promise<void> {
672
879
  () => {
673
880
  // "🕵️ Authenticate Wrangler with a Cloudflare API Token",
674
881
  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"
882
+ "`wrangler config` has been deprecated, please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#config for alternatives"
676
883
  );
677
884
  }
678
885
  );
@@ -754,6 +961,13 @@ export async function main(argv: string[]): Promise<void> {
754
961
  describe: "Static assets to be served",
755
962
  type: "string",
756
963
  requiresArg: true,
964
+ deprecated: true,
965
+ hidden: true,
966
+ })
967
+ .option("assets", {
968
+ describe: "Static assets to be served",
969
+ type: "string",
970
+ requiresArg: true,
757
971
  })
758
972
  .option("site", {
759
973
  describe: "Root folder of static assets for Workers Sites",
@@ -816,181 +1030,116 @@ export async function main(argv: string[]): Promise<void> {
816
1030
  describe: "Enable dev tools",
817
1031
  type: "boolean",
818
1032
  deprecated: true,
1033
+ })
1034
+ .option("legacy-env", {
1035
+ type: "boolean",
1036
+ describe: "Use legacy environments",
1037
+ hidden: true,
819
1038
  });
820
1039
  },
821
1040
  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
- }
1041
+ let watcher: ReturnType<typeof watch> | undefined;
1042
+ try {
1043
+ await printWranglerBanner();
1044
+ const configPath =
1045
+ (args.config as ConfigPath) ||
1046
+ ((args.script &&
1047
+ findWranglerToml(path.dirname(args.script))) as ConfigPath);
1048
+ let config = readConfig(configPath, args);
1049
+
1050
+ if (config.configPath) {
1051
+ watcher = watch(config.configPath, {
1052
+ persistent: true,
1053
+ }).on("change", async (_event) => {
1054
+ // TODO: Do we need to handle different `_event` types differently?
1055
+ // e.g. what if the file is deleted, or added?
1056
+ config = readConfig(configPath, args);
1057
+ if (config.configPath) {
1058
+ logger.log(`${path.basename(config.configPath)} changed...`);
1059
+ rerender(await getDevReactElement(config));
1060
+ }
1061
+ });
1062
+ }
846
1063
 
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
- }
1064
+ const entry = await getEntry(args, config, "dev");
856
1065
 
857
- const accountId = !args.local ? await requireAuth(config) : undefined;
1066
+ if (config.services && config.services.length > 0) {
1067
+ logger.warn(
1068
+ `This worker is bound to live services: ${config.services
1069
+ .map(
1070
+ (service) =>
1071
+ `${service.binding} (${service.service}${
1072
+ service.environment ? `@${service.environment}` : ""
1073
+ })`
1074
+ )
1075
+ .join(", ")}`
1076
+ );
1077
+ }
858
1078
 
859
- // TODO: if worker_dev = false and no routes, then error (only for dev)
1079
+ if (args.inspect) {
1080
+ logger.warn(
1081
+ "Passing --inspect is unnecessary, now you can always connect to devtools."
1082
+ );
1083
+ }
860
1084
 
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;
1085
+ if (args["experimental-public"]) {
1086
+ throw new Error(
1087
+ "The --experimental-public field has been renamed to --assets"
1088
+ );
870
1089
  }
871
- return new URL(urlLike).host;
872
- }
873
1090
 
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
- }
1091
+ if (args.public) {
1092
+ throw new Error("The --public field has been renamed to --assets");
1093
+ }
886
1094
 
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();
1095
+ if ((args.assets || config.assets) && (args.site || config.site)) {
1096
+ throw new Error(
1097
+ "Cannot use Assets and Workers Sites in the same Worker."
1098
+ );
927
1099
  }
928
1100
 
929
- if (host && !zoneId) {
930
- throw new Error(`Could not find zone for ${hostLike}`);
1101
+ const upstreamProtocol =
1102
+ args["upstream-protocol"] || config.dev.upstream_protocol;
1103
+ if (upstreamProtocol === "http") {
1104
+ logger.warn(
1105
+ "Setting upstream-protocol to http is not currently implemented.\n" +
1106
+ "If this is required in your project, please add your use case to the following issue:\n" +
1107
+ "https://github.com/cloudflare/wrangler2/issues/583."
1108
+ );
931
1109
  }
932
1110
 
933
- zone =
934
- typeof zoneId === "string" && typeof host === "string"
935
- ? {
936
- host,
937
- id: zoneId,
938
- }
939
- : undefined;
940
- }
1111
+ // TODO: if worker_dev = false and no routes, then error (only for dev)
941
1112
 
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
- }
1113
+ // Compute zone info from the `host` and `route` args and config;
1114
+ let host = args.host || config.dev.host;
1115
+ let zoneId: string | undefined;
948
1116
 
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 }))
1117
+ if (!args.local) {
1118
+ if (host) {
1119
+ zoneId = await getZoneIdFromHost(host);
978
1120
  }
979
- ip={args.ip || config.dev.ip}
980
- inspectorPort={
981
- args["inspector-port"] ?? (await getPort({ port: 9229 }))
982
- }
983
- public={args["experimental-public"]}
984
- compatibilityDate={getDevCompatibilityDate(
985
- config,
986
- args["compatibility-date"]
987
- )}
988
- compatibilityFlags={
989
- args["compatibility-flags"] || config.compatibility_flags
1121
+ const routes = args.routes || config.route || config.routes;
1122
+ if (!zoneId && routes) {
1123
+ const firstRoute = Array.isArray(routes) ? routes[0] : routes;
1124
+ const zone = await getZoneForRoute(firstRoute);
1125
+ if (zone) {
1126
+ zoneId = zone.id;
1127
+ host = zone.host;
1128
+ }
990
1129
  }
991
- usageModel={config.usage_model}
992
- bindings={{
993
- kv_namespaces: config.kv_namespaces?.map(
1130
+ }
1131
+
1132
+ const nodeCompat = args.nodeCompat ?? config.node_compat;
1133
+ if (nodeCompat) {
1134
+ logger.warn(
1135
+ "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."
1136
+ );
1137
+ }
1138
+
1139
+ // eslint-disable-next-line no-inner-declarations
1140
+ async function getBindings(configParam: Config) {
1141
+ return {
1142
+ kv_namespaces: configParam.kv_namespaces?.map(
994
1143
  ({ binding, preview_id, id: _id }) => {
995
1144
  // In `dev`, we make folks use a separate kv namespace called
996
1145
  // `preview_id` instead of `id` so that they don't
@@ -1011,12 +1160,13 @@ export async function main(argv: string[]): Promise<void> {
1011
1160
  };
1012
1161
  }
1013
1162
  ),
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(
1163
+ // Use a copy of combinedVars since we're modifying it later
1164
+ vars: getVarsForDev(configParam),
1165
+ wasm_modules: configParam.wasm_modules,
1166
+ text_blobs: configParam.text_blobs,
1167
+ data_blobs: configParam.data_blobs,
1168
+ durable_objects: configParam.durable_objects,
1169
+ r2_buckets: configParam.r2_buckets?.map(
1020
1170
  ({ binding, preview_bucket_name, bucket_name: _bucket_name }) => {
1021
1171
  // same idea as kv namespace preview id,
1022
1172
  // same copy-on-write TODO
@@ -1031,12 +1181,95 @@ export async function main(argv: string[]): Promise<void> {
1031
1181
  };
1032
1182
  }
1033
1183
  ),
1034
- unsafe: config.unsafe?.bindings,
1035
- }}
1036
- crons={config.triggers.crons}
1037
- />
1038
- );
1039
- await waitUntilExit();
1184
+ services: configParam.services,
1185
+ unsafe: configParam.unsafe?.bindings,
1186
+ };
1187
+ }
1188
+
1189
+ const getLocalPort = memoizeGetPort(DEFAULT_LOCAL_PORT);
1190
+ const getInspectorPort = memoizeGetPort(9229);
1191
+
1192
+ // eslint-disable-next-line no-inner-declarations
1193
+ async function getDevReactElement(configParam: Config) {
1194
+ // now log all available bindings into the terminal
1195
+ const bindings = await getBindings(configParam);
1196
+ // mask anything that was overridden in .dev.vars
1197
+ // so that we don't log potential secrets into the terminal
1198
+ const maskedVars = { ...bindings.vars };
1199
+ for (const key of Object.keys(maskedVars)) {
1200
+ if (maskedVars[key] !== configParam.vars[key]) {
1201
+ // This means it was overridden in .dev.vars
1202
+ // so let's mask it
1203
+ maskedVars[key] = "(hidden)";
1204
+ }
1205
+ }
1206
+
1207
+ printBindings({
1208
+ ...bindings,
1209
+ vars: maskedVars,
1210
+ });
1211
+
1212
+ const assetPaths =
1213
+ args.assets || config.assets
1214
+ ? getAssetPaths(config, args.assets)
1215
+ : getSiteAssetPaths(
1216
+ config,
1217
+ args.site,
1218
+ args.siteInclude,
1219
+ args.siteExclude
1220
+ );
1221
+
1222
+ return (
1223
+ <Dev
1224
+ name={getScriptName(args, config)}
1225
+ entry={entry}
1226
+ env={args.env}
1227
+ zone={zoneId}
1228
+ host={host}
1229
+ rules={getRules(config)}
1230
+ legacyEnv={isLegacyEnv(config)}
1231
+ minify={args.minify ?? config.minify}
1232
+ nodeCompat={nodeCompat}
1233
+ build={config.build || {}}
1234
+ initialMode={args.local ? "local" : "remote"}
1235
+ jsxFactory={args["jsx-factory"] || config.jsx_factory}
1236
+ jsxFragment={args["jsx-fragment"] || config.jsx_fragment}
1237
+ tsconfig={args.tsconfig ?? config.tsconfig}
1238
+ upstreamProtocol={upstreamProtocol}
1239
+ localProtocol={
1240
+ args["local-protocol"] || config.dev.local_protocol
1241
+ }
1242
+ enableLocalPersistence={
1243
+ args["experimental-enable-local-persistence"] || false
1244
+ }
1245
+ accountId={config.account_id}
1246
+ assetPaths={assetPaths}
1247
+ port={args.port || config.dev.port || (await getLocalPort())}
1248
+ ip={args.ip || config.dev.ip}
1249
+ inspectorPort={
1250
+ args["inspector-port"] ?? (await getInspectorPort())
1251
+ }
1252
+ isWorkersSite={Boolean(args.site || config.site)}
1253
+ compatibilityDate={getDevCompatibilityDate(
1254
+ config,
1255
+ args["compatibility-date"]
1256
+ )}
1257
+ compatibilityFlags={
1258
+ args["compatibility-flags"] || config.compatibility_flags
1259
+ }
1260
+ usageModel={config.usage_model}
1261
+ bindings={bindings}
1262
+ crons={config.triggers.crons}
1263
+ />
1264
+ );
1265
+ }
1266
+ const { waitUntilExit, rerender } = render(
1267
+ await getDevReactElement(config)
1268
+ );
1269
+ await waitUntilExit();
1270
+ } finally {
1271
+ await watcher?.close();
1272
+ }
1040
1273
  }
1041
1274
  );
1042
1275
 
@@ -1093,6 +1326,13 @@ export async function main(argv: string[]): Promise<void> {
1093
1326
  describe: "Static assets to be served",
1094
1327
  type: "string",
1095
1328
  requiresArg: true,
1329
+ deprecated: true,
1330
+ hidden: true,
1331
+ })
1332
+ .option("assets", {
1333
+ describe: "Static assets to be served",
1334
+ type: "string",
1335
+ requiresArg: true,
1096
1336
  })
1097
1337
  .option("site", {
1098
1338
  describe: "Root folder of static assets for Workers Sites",
@@ -1153,20 +1393,15 @@ export async function main(argv: string[]): Promise<void> {
1153
1393
  .option("dry-run", {
1154
1394
  describe: "Don't actually publish",
1155
1395
  type: "boolean",
1396
+ })
1397
+ .option("legacy-env", {
1398
+ type: "boolean",
1399
+ describe: "Use legacy environments",
1400
+ hidden: true,
1156
1401
  });
1157
1402
  },
1158
1403
  async (args) => {
1159
1404
  await printWranglerBanner();
1160
- if (args["experimental-public"]) {
1161
- logger.warn(
1162
- "The --experimental-public field is experimental and will change in the future."
1163
- );
1164
- }
1165
- if (args.public) {
1166
- throw new Error(
1167
- "The --public field has been renamed to --experimental-public, and will change behaviour in the future."
1168
- );
1169
- }
1170
1405
 
1171
1406
  const configPath =
1172
1407
  (args.config as ConfigPath) ||
@@ -1174,20 +1409,39 @@ export async function main(argv: string[]): Promise<void> {
1174
1409
  const config = readConfig(configPath, args);
1175
1410
  const entry = await getEntry(args, config, "publish");
1176
1411
 
1412
+ if (args.public) {
1413
+ throw new Error("The --public field has been renamed to --assets");
1414
+ }
1415
+ if (args["experimental-public"]) {
1416
+ throw new Error(
1417
+ "The --experimental-public field has been renamed to --assets"
1418
+ );
1419
+ }
1420
+
1421
+ if ((args.assets || config.assets) && (args.site || config.site)) {
1422
+ throw new Error(
1423
+ "Cannot use Assets and Workers Sites in the same Worker."
1424
+ );
1425
+ }
1426
+
1177
1427
  if (args.latest) {
1178
1428
  logger.warn(
1179
1429
  "Using the latest version of the Workers runtime. To silence this warning, please choose a specific version of the runtime with --compatibility-date, or add a compatibility_date to your wrangler.toml.\n"
1180
1430
  );
1181
1431
  }
1182
1432
 
1183
- const accountId = await requireAuth(config);
1433
+ const accountId = args.dryRun ? undefined : await requireAuth(config);
1434
+
1435
+ const assetPaths =
1436
+ args.assets || config.assets
1437
+ ? getAssetPaths(config, args.assets)
1438
+ : getSiteAssetPaths(
1439
+ config,
1440
+ args.site,
1441
+ args.siteInclude,
1442
+ args.siteExclude
1443
+ );
1184
1444
 
1185
- const assetPaths = getAssetPaths(
1186
- config,
1187
- args["experimental-public"] || args.site,
1188
- args.siteInclude,
1189
- args.siteExclude
1190
- );
1191
1445
  await publish({
1192
1446
  config,
1193
1447
  accountId,
@@ -1208,7 +1462,7 @@ export async function main(argv: string[]): Promise<void> {
1208
1462
  legacyEnv: isLegacyEnv(config),
1209
1463
  minify: args.minify,
1210
1464
  nodeCompat: args.nodeCompat,
1211
- experimentalPublic: args["experimental-public"] !== undefined,
1465
+ isWorkersSite: Boolean(args.site || config.site),
1212
1466
  outDir: args.outdir,
1213
1467
  dryRun: args.dryRun,
1214
1468
  });
@@ -1274,6 +1528,11 @@ export async function main(argv: string[]): Promise<void> {
1274
1528
  default: false,
1275
1529
  describe:
1276
1530
  "If a log would have been filtered out, send it through anyway alongside the filter which would have blocked it.",
1531
+ })
1532
+ .option("legacy-env", {
1533
+ type: "boolean",
1534
+ describe: "Use legacy environments",
1535
+ hidden: true,
1277
1536
  });
1278
1537
  },
1279
1538
  async (args) => {
@@ -1410,6 +1669,7 @@ export async function main(argv: string[]): Promise<void> {
1410
1669
  rules={getRules(config)}
1411
1670
  env={args.env}
1412
1671
  zone={undefined}
1672
+ host={undefined}
1413
1673
  legacyEnv={isLegacyEnv(config)}
1414
1674
  build={config.build || {}}
1415
1675
  minify={undefined}
@@ -1423,9 +1683,11 @@ export async function main(argv: string[]): Promise<void> {
1423
1683
  enableLocalPersistence={false}
1424
1684
  accountId={accountId}
1425
1685
  assetPaths={undefined}
1426
- port={config.dev?.port}
1686
+ port={
1687
+ config.dev.port || (await getPort({ port: DEFAULT_LOCAL_PORT }))
1688
+ }
1427
1689
  ip={config.dev.ip}
1428
- public={undefined}
1690
+ isWorkersSite={false}
1429
1691
  compatibilityDate={getDevCompatibilityDate(config)}
1430
1692
  compatibilityFlags={config.compatibility_flags}
1431
1693
  usageModel={config.usage_model}
@@ -1471,6 +1733,7 @@ export async function main(argv: string[]): Promise<void> {
1471
1733
  };
1472
1734
  }
1473
1735
  ),
1736
+ services: config.services,
1474
1737
  unsafe: config.unsafe?.bindings,
1475
1738
  }}
1476
1739
  crons={config.triggers.crons}
@@ -1570,7 +1833,7 @@ export async function main(argv: string[]): Promise<void> {
1570
1833
  },
1571
1834
  () => {
1572
1835
  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"
1836
+ "`wrangler subdomain` has been deprecated, please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#subdomain for alternatives"
1574
1837
  );
1575
1838
  }
1576
1839
  );
@@ -1582,6 +1845,11 @@ export async function main(argv: string[]): Promise<void> {
1582
1845
  (secretYargs) => {
1583
1846
  return secretYargs
1584
1847
  .command(subHelp)
1848
+ .option("legacy-env", {
1849
+ type: "boolean",
1850
+ describe: "Use legacy environments",
1851
+ hidden: true,
1852
+ })
1585
1853
  .command(
1586
1854
  "put <key>",
1587
1855
  "Create or update a secret variable for a script",
@@ -1616,9 +1884,11 @@ export async function main(argv: string[]): Promise<void> {
1616
1884
  const accountId = await requireAuth(config);
1617
1885
 
1618
1886
  const isInteractive = process.stdin.isTTY;
1619
- const secretValue = isInteractive
1620
- ? await prompt("Enter a secret value:", "password")
1621
- : await readFromStdin();
1887
+ const secretValue = trimTrailingWhitespace(
1888
+ isInteractive
1889
+ ? await prompt("Enter a secret value:", "password")
1890
+ : await readFromStdin()
1891
+ );
1622
1892
 
1623
1893
  logger.log(
1624
1894
  `🌀 Creating the secret for script ${scriptName} ${
@@ -1663,6 +1933,7 @@ export async function main(argv: string[]): Promise<void> {
1663
1933
  vars: {},
1664
1934
  durable_objects: { bindings: [] },
1665
1935
  r2_buckets: [],
1936
+ services: [],
1666
1937
  wasm_modules: {},
1667
1938
  text_blobs: {},
1668
1939
  data_blobs: {},
@@ -1831,7 +2102,7 @@ export async function main(argv: string[]): Promise<void> {
1831
2102
  async (args) => {
1832
2103
  await printWranglerBanner();
1833
2104
 
1834
- if (!isValidNamespaceBinding(args.namespace)) {
2105
+ if (!isValidKVNamespaceBinding(args.namespace)) {
1835
2106
  throw new CommandLineArgsError(
1836
2107
  `The namespace binding name "${args.namespace}" is invalid. It can only have alphanumeric and _ characters, and cannot begin with a number.`
1837
2108
  );
@@ -1854,7 +2125,7 @@ export async function main(argv: string[]): Promise<void> {
1854
2125
  // TODO: generate a binding name stripping non alphanumeric chars
1855
2126
 
1856
2127
  logger.log(`🌀 Creating namespace with title "${title}"`);
1857
- const namespaceId = await createNamespace(accountId, title);
2128
+ const namespaceId = await createKVNamespace(accountId, title);
1858
2129
 
1859
2130
  logger.log("✨ Success!");
1860
2131
  const envString = args.env ? ` under [env.${args.env}]` : "";
@@ -1881,7 +2152,7 @@ export async function main(argv: string[]): Promise<void> {
1881
2152
  // TODO: we should show bindings if they exist for given ids
1882
2153
 
1883
2154
  logger.log(
1884
- JSON.stringify(await listNamespaces(accountId), null, " ")
2155
+ JSON.stringify(await listKVNamespaces(accountId), null, " ")
1885
2156
  );
1886
2157
  }
1887
2158
  )
@@ -1918,7 +2189,7 @@ export async function main(argv: string[]): Promise<void> {
1918
2189
 
1919
2190
  let id;
1920
2191
  try {
1921
- id = getNamespaceId(args, config);
2192
+ id = getKVNamespaceId(args, config);
1922
2193
  } catch (e) {
1923
2194
  throw new CommandLineArgsError(
1924
2195
  "Not able to delete namespace.\n" + ((e as Error).message ?? e)
@@ -1927,10 +2198,7 @@ export async function main(argv: string[]): Promise<void> {
1927
2198
 
1928
2199
  const accountId = await requireAuth(config);
1929
2200
 
1930
- await fetchResult<{ id: string }>(
1931
- `/accounts/${accountId}/storage/kv/namespaces/${id}`,
1932
- { method: "DELETE" }
1933
- );
2201
+ await deleteKVNamespace(accountId, id);
1934
2202
 
1935
2203
  // TODO: recommend they remove it from wrangler.toml
1936
2204
 
@@ -2015,10 +2283,10 @@ export async function main(argv: string[]): Promise<void> {
2015
2283
  async ({ key, ttl, expiration, ...args }) => {
2016
2284
  await printWranglerBanner();
2017
2285
  const config = readConfig(args.config as ConfigPath, args);
2018
- const namespaceId = getNamespaceId(args, config);
2286
+ const namespaceId = getKVNamespaceId(args, config);
2019
2287
  // One of `args.path` and `args.value` must be defined
2020
2288
  const value = args.path
2021
- ? readFileSync(args.path)
2289
+ ? readFileSyncToBuffer(args.path)
2022
2290
  : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2023
2291
  args.value!;
2024
2292
 
@@ -2034,7 +2302,7 @@ export async function main(argv: string[]): Promise<void> {
2034
2302
 
2035
2303
  const accountId = await requireAuth(config);
2036
2304
 
2037
- await putKeyValue(accountId, namespaceId, {
2305
+ await putKVKeyValue(accountId, namespaceId, {
2038
2306
  key,
2039
2307
  value,
2040
2308
  expiration,
@@ -2079,11 +2347,11 @@ export async function main(argv: string[]): Promise<void> {
2079
2347
  async ({ prefix, ...args }) => {
2080
2348
  // TODO: support for limit+cursor (pagination)
2081
2349
  const config = readConfig(args.config as ConfigPath, args);
2082
- const namespaceId = getNamespaceId(args, config);
2350
+ const namespaceId = getKVNamespaceId(args, config);
2083
2351
 
2084
2352
  const accountId = await requireAuth(config);
2085
2353
 
2086
- const results = await listNamespaceKeys(
2354
+ const results = await listKVNamespaceKeys(
2087
2355
  accountId,
2088
2356
  namespaceId,
2089
2357
  prefix
@@ -2131,11 +2399,13 @@ export async function main(argv: string[]): Promise<void> {
2131
2399
  },
2132
2400
  async ({ key, ...args }) => {
2133
2401
  const config = readConfig(args.config as ConfigPath, args);
2134
- const namespaceId = getNamespaceId(args, config);
2402
+ const namespaceId = getKVNamespaceId(args, config);
2135
2403
 
2136
2404
  const accountId = await requireAuth(config);
2137
2405
 
2138
- logger.log(await getKeyValue(accountId, namespaceId, key));
2406
+ process.stdout.write(
2407
+ Buffer.from(await getKVKeyValue(accountId, namespaceId, key))
2408
+ );
2139
2409
  }
2140
2410
  )
2141
2411
  .command(
@@ -2173,7 +2443,7 @@ export async function main(argv: string[]): Promise<void> {
2173
2443
  async ({ key, ...args }) => {
2174
2444
  await printWranglerBanner();
2175
2445
  const config = readConfig(args.config as ConfigPath, args);
2176
- const namespaceId = getNamespaceId(args, config);
2446
+ const namespaceId = getKVNamespaceId(args, config);
2177
2447
 
2178
2448
  logger.log(
2179
2449
  `Deleting the key "${key}" on namespace ${namespaceId}.`
@@ -2181,10 +2451,7 @@ export async function main(argv: string[]): Promise<void> {
2181
2451
 
2182
2452
  const accountId = await requireAuth(config);
2183
2453
 
2184
- await fetchResult(
2185
- `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`,
2186
- { method: "DELETE" }
2187
- );
2454
+ await deleteKVKeyValue(accountId, namespaceId, key);
2188
2455
  }
2189
2456
  );
2190
2457
  }
@@ -2236,7 +2503,7 @@ export async function main(argv: string[]): Promise<void> {
2236
2503
  // but we'll do that in the future if needed.
2237
2504
 
2238
2505
  const config = readConfig(args.config as ConfigPath, args);
2239
- const namespaceId = getNamespaceId(args, config);
2506
+ const namespaceId = getKVNamespaceId(args, config);
2240
2507
  const content = parseJSON(readFileSync(filename), filename);
2241
2508
 
2242
2509
  if (!Array.isArray(content)) {
@@ -2250,18 +2517,12 @@ export async function main(argv: string[]): Promise<void> {
2250
2517
  const warnings: string[] = [];
2251
2518
  for (let i = 0; i < content.length; i++) {
2252
2519
  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)) {
2520
+ if (!isKVKeyValue(keyValue)) {
2260
2521
  errors.push(
2261
2522
  `The item at index ${i} is ${JSON.stringify(keyValue)}`
2262
2523
  );
2263
2524
  } else {
2264
- const props = unexpectedKeyValueProps(keyValue);
2525
+ const props = unexpectedKVKeyValueProps(keyValue);
2265
2526
  if (props.length > 0) {
2266
2527
  warnings.push(
2267
2528
  `The item at index ${i} contains unexpected properties: ${JSON.stringify(
@@ -2294,14 +2555,7 @@ export async function main(argv: string[]): Promise<void> {
2294
2555
  }
2295
2556
 
2296
2557
  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
- );
2558
+ await putKVBulkKeyValue(accountId, namespaceId, content);
2305
2559
 
2306
2560
  logger.log("Success!");
2307
2561
  }
@@ -2346,7 +2600,7 @@ export async function main(argv: string[]): Promise<void> {
2346
2600
  async ({ filename, ...args }) => {
2347
2601
  await printWranglerBanner();
2348
2602
  const config = readConfig(args.config as ConfigPath, args);
2349
- const namespaceId = getNamespaceId(args, config);
2603
+ const namespaceId = getKVNamespaceId(args, config);
2350
2604
 
2351
2605
  if (!args.force) {
2352
2606
  const result = await confirm(
@@ -2391,14 +2645,7 @@ export async function main(argv: string[]): Promise<void> {
2391
2645
 
2392
2646
  const accountId = await requireAuth(config);
2393
2647
 
2394
- await deleteBulkKeyValue(
2395
- accountId,
2396
- namespaceId,
2397
- content,
2398
- (index, total) => {
2399
- logger.log(`Deleted ${index} of ${total}.`);
2400
- }
2401
- );
2648
+ await deleteKVBulkKeyValue(accountId, namespaceId, content);
2402
2649
 
2403
2650
  logger.log("Success!");
2404
2651
  }
@@ -2551,41 +2798,43 @@ export async function main(argv: string[]): Promise<void> {
2551
2798
  }
2552
2799
  );
2553
2800
 
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
- });
2801
+ wrangler.option("config", {
2802
+ alias: "c",
2803
+ describe: "Path to .toml configuration file",
2804
+ type: "string",
2805
+ requiresArg: true,
2806
+ });
2565
2807
 
2566
- wrangler.group(["config", "help", "version", "legacy-env"], "Flags:");
2808
+ wrangler.group(["config", "help", "version"], "Flags:");
2567
2809
  wrangler.help().alias("h", "help");
2568
2810
  wrangler.version(wranglerVersion).alias("v", "version");
2569
2811
  wrangler.exitProcess(false);
2570
2812
 
2813
+ return wrangler;
2814
+ }
2815
+
2816
+ export async function main(argv: string[]): Promise<void> {
2817
+ const wrangler = createCLIParser(argv);
2571
2818
  try {
2572
2819
  await wrangler.parse();
2573
2820
  } catch (e) {
2574
2821
  logger.log(""); // Just adds a bit of space
2575
2822
  if (e instanceof CommandLineArgsError) {
2576
- wrangler.showHelp("error");
2577
- logger.log(""); // Add a bit of space.
2578
2823
  logger.error(e.message);
2824
+ // We are not able to ask the `wrangler` CLI parser to show help for a subcommand programmatically.
2825
+ // The workaround is to re-run the parsing with an additional `--help` flag, which will result in the correct help message being displayed.
2826
+ // 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.
2827
+ await createCLIParser([...argv, "--help"]).parse();
2579
2828
  } else if (e instanceof ParseError) {
2580
2829
  e.notes.push({
2581
- text: "\nIf you think this is a bug, please open an issue at: https://github.com/cloudflare/wrangler2/issues/new",
2830
+ text: "\nIf you think this is a bug, please open an issue at: https://github.com/cloudflare/wrangler2/issues/new/choose",
2582
2831
  });
2583
- logger.error(formatMessage(e));
2832
+ logger.log(formatMessage(e));
2584
2833
  } else {
2585
2834
  logger.error(e instanceof Error ? e.message : e);
2586
2835
  logger.log(
2587
2836
  `${fgGreenColor}%s${resetColor}`,
2588
- "If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
2837
+ "If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
2589
2838
  );
2590
2839
  }
2591
2840
  throw e;
@@ -2613,3 +2862,35 @@ function getDevCompatibilityDate(
2613
2862
  }
2614
2863
  return compatibilityDate ?? currentDate;
2615
2864
  }
2865
+
2866
+ /**
2867
+ * Avoiding calling `getPort()` multiple times by memoizing the first result.
2868
+ */
2869
+ function memoizeGetPort(defaultPort: number) {
2870
+ let portValue: number;
2871
+ return async () => {
2872
+ return portValue || (portValue = await getPort({ port: defaultPort }));
2873
+ };
2874
+ }
2875
+
2876
+ /**
2877
+ * Find the path to the given `basename` file from the `cwd`.
2878
+ *
2879
+ * If `isolatedInit` is true then we only look in the `cwd` directory for the file.
2880
+ * Otherwise we also search up the tree.
2881
+ */
2882
+ async function findPath(
2883
+ isolatedInit: boolean,
2884
+ cwd: string,
2885
+ basename: string
2886
+ ): Promise<string | undefined> {
2887
+ if (isolatedInit) {
2888
+ return fs.existsSync(path.resolve(cwd, basename))
2889
+ ? path.resolve(cwd, basename)
2890
+ : undefined;
2891
+ } else {
2892
+ return await findUp(basename, {
2893
+ cwd: cwd,
2894
+ });
2895
+ }
2896
+ }