wrangler 2.0.6 → 2.0.7

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.
@@ -193,7 +193,8 @@ export function normalizeAndValidateConfig(
193
193
  dev: normalizeAndValidateDev(diagnostics, rawConfig.dev ?? {}),
194
194
  migrations: normalizeAndValidateMigrations(
195
195
  diagnostics,
196
- rawConfig.migrations ?? []
196
+ rawConfig.migrations ?? [],
197
+ activeEnv.durable_objects
197
198
  ),
198
199
  site: normalizeAndValidateSite(
199
200
  diagnostics,
@@ -387,7 +388,8 @@ function normalizeAndValidateDev(
387
388
  */
388
389
  function normalizeAndValidateMigrations(
389
390
  diagnostics: Diagnostics,
390
- rawMigrations: Config["migrations"]
391
+ rawMigrations: Config["migrations"],
392
+ durableObjects: Config["durable_objects"]
391
393
  ): Config["migrations"] {
392
394
  if (!Array.isArray(rawMigrations)) {
393
395
  diagnostics.errors.push(
@@ -441,6 +443,26 @@ function normalizeAndValidateMigrations(
441
443
  "string"
442
444
  );
443
445
  }
446
+
447
+ if (
448
+ Array.isArray(durableObjects?.bindings) &&
449
+ durableObjects.bindings.length > 0
450
+ ) {
451
+ // intrinsic [durable_objects] implies [migrations]
452
+ const exportedDurableObjects = (durableObjects.bindings || []).filter(
453
+ (binding) => !binding.script_name
454
+ );
455
+ if (exportedDurableObjects.length > 0 && rawMigrations.length === 0) {
456
+ diagnostics.warnings.push(
457
+ `In wrangler.toml, you have configured [durable_objects] exported by this Worker (${exportedDurableObjects
458
+ .map((durable) => durable.class_name || "(unnamed)")
459
+ .join(
460
+ ", "
461
+ )}), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml. Refer to https://developers.cloudflare.com/workers/learning/using-durable-objects/#durable-object-migrations-in-wranglertoml for more details.`
462
+ );
463
+ }
464
+ }
465
+
444
466
  return rawMigrations;
445
467
  }
446
468
  }
@@ -66,7 +66,12 @@ async function sessionToken(
66
66
  ? `/zones/${ctx.zone.id}/workers/edge-preview`
67
67
  : `/accounts/${accountId}/workers/subdomain/edge-preview`;
68
68
 
69
- const { exchange_url } = await fetchResult<{ exchange_url: string }>(initUrl);
69
+ const { exchange_url } = await fetchResult<{ exchange_url: string }>(
70
+ initUrl,
71
+ undefined,
72
+ undefined,
73
+ abortSignal
74
+ );
70
75
  const { inspector_websocket, prewarm, token } = (await (
71
76
  await fetch(exchange_url, { signal: abortSignal })
72
77
  ).json()) as { inspector_websocket: string; token: string; prewarm: string };
@@ -119,13 +124,18 @@ async function createPreviewToken(
119
124
  const formData = createWorkerUploadForm(worker);
120
125
  formData.set("wrangler-session-config", JSON.stringify(mode));
121
126
 
122
- const { preview_token } = await fetchResult<{ preview_token: string }>(url, {
123
- method: "POST",
124
- body: formData,
125
- headers: {
126
- "cf-preview-upload-config-token": value,
127
+ const { preview_token } = await fetchResult<{ preview_token: string }>(
128
+ url,
129
+ {
130
+ method: "POST",
131
+ body: formData,
132
+ headers: {
133
+ "cf-preview-upload-config-token": value,
134
+ },
127
135
  },
128
- });
136
+ undefined,
137
+ abortSignal
138
+ );
129
139
 
130
140
  return {
131
141
  value: preview_token,
package/src/dev/dev.tsx CHANGED
@@ -9,7 +9,9 @@ import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
9
9
  import onExit from "signal-exit";
10
10
  import tmp from "tmp-promise";
11
11
  import { fetch } from "undici";
12
+ import { printBindings } from "../config";
12
13
  import { runCustomBuild } from "../entry";
14
+ import { openInspector } from "../inspect";
13
15
  import { logger } from "../logger";
14
16
  import openInBrowser from "../open-in-browser";
15
17
  import { getAPIToken } from "../user";
@@ -104,6 +106,8 @@ export function DevImplementation(props: DevProps): JSX.Element {
104
106
  nodeCompat: props.nodeCompat,
105
107
  });
106
108
 
109
+ printBindings(props.bindings);
110
+
107
111
  // only load the UI if we're running in a supported environment
108
112
  const { isRawModeSupported } = useStdin();
109
113
  return isRawModeSupported ? (
@@ -378,10 +382,7 @@ function useHotkeys(
378
382
  }
379
383
  // toggle inspector
380
384
  case "d": {
381
- await openInBrowser(
382
- `https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:${inspectorPort}/ws`,
383
- { forceChromium: true }
384
- );
385
+ await openInspector(inspectorPort);
385
386
  break;
386
387
  }
387
388
  // toggle local
@@ -185,7 +185,21 @@ export function useWorker(props: {
185
185
  // we want to log the error, but not end the process
186
186
  // since it could recover after the developer fixes whatever's wrong
187
187
  if ((err as { code: string }).code !== "ABORT_ERR") {
188
- logger.error("Error on remote worker:", err);
188
+ // instead of logging the raw API error to the user,
189
+ // give them friendly instructions
190
+ // for error 10063 (workers.dev subdomain required)
191
+ if (err.code === 10063) {
192
+ const errorMessage =
193
+ "Error: You need to register a workers.dev subdomain before running the dev command in remote mode";
194
+ const solutionMessage =
195
+ "You can either enable local mode by pressing l, or register a workers.dev subdomain here:";
196
+ const onboardingLink = `https://dash.cloudflare.com/${accountId}/workers/onboarding`;
197
+ logger.error(
198
+ `${errorMessage}\n${solutionMessage}\n${onboardingLink}`
199
+ );
200
+ } else {
201
+ logger.error("Error on remote worker:", err);
202
+ }
189
203
  }
190
204
  });
191
205
 
package/src/durable.ts ADDED
@@ -0,0 +1,102 @@
1
+ import assert from "node:assert";
2
+ import { fetchResult } from "./cfetch";
3
+ import { logger } from "./logger";
4
+ import type { Config } from "./config";
5
+ import type { CfWorkerInit } from "./worker";
6
+
7
+ /**
8
+ * For a given Worker + migrations config, figure out which migrations
9
+ * to upload based on the current migration tag of the deployed Worker.
10
+ */
11
+ export async function getMigrationsToUpload(
12
+ scriptName: string,
13
+ props: {
14
+ accountId: string | undefined;
15
+ config: Config;
16
+ legacyEnv: boolean | undefined;
17
+ env: string | undefined;
18
+ }
19
+ ): Promise<CfWorkerInit["migrations"]> {
20
+ const { config, accountId } = props;
21
+
22
+ assert(accountId, "Missing accountId");
23
+ // if config.migrations
24
+ let migrations;
25
+ if (config.migrations.length > 0) {
26
+ // get current migration tag
27
+ type ScriptData = { id: string; migration_tag?: string };
28
+ let script: ScriptData | undefined;
29
+ if (!props.legacyEnv) {
30
+ try {
31
+ if (props.env) {
32
+ const scriptData = await fetchResult<{
33
+ script: ScriptData;
34
+ }>(
35
+ `/accounts/${accountId}/workers/services/${scriptName}/environments/${props.env}`
36
+ );
37
+ script = scriptData.script;
38
+ } else {
39
+ const scriptData = await fetchResult<{
40
+ default_environment: {
41
+ script: ScriptData;
42
+ };
43
+ }>(`/accounts/${accountId}/workers/services/${scriptName}`);
44
+ script = scriptData.default_environment.script;
45
+ }
46
+ } catch (err) {
47
+ if (
48
+ ![
49
+ 10090, // corresponds to workers.api.error.service_not_found, so the script wasn't previously published at all
50
+ 10092, // workers.api.error.environment_not_found, so the script wasn't published to this environment yet
51
+ ].includes((err as { code: number }).code)
52
+ ) {
53
+ throw err;
54
+ }
55
+ // else it's a 404, no script found, and we can proceed
56
+ }
57
+ } else {
58
+ const scripts = await fetchResult<ScriptData[]>(
59
+ `/accounts/${accountId}/workers/scripts`
60
+ );
61
+ script = scripts.find(({ id }) => id === scriptName);
62
+ }
63
+
64
+ if (script?.migration_tag) {
65
+ // was already published once
66
+ const scriptMigrationTag = script.migration_tag;
67
+ const foundIndex = config.migrations.findIndex(
68
+ (migration) => migration.tag === scriptMigrationTag
69
+ );
70
+ if (foundIndex === -1) {
71
+ logger.warn(
72
+ `The published script ${scriptName} has a migration tag "${script.migration_tag}, which was not found in wrangler.toml. You may have already deleted it. Applying all available migrations to the script...`
73
+ );
74
+ migrations = {
75
+ old_tag: script.migration_tag,
76
+ new_tag: config.migrations[config.migrations.length - 1].tag,
77
+ steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
78
+ };
79
+ } else {
80
+ if (foundIndex !== config.migrations.length - 1) {
81
+ // there are new migrations to send up
82
+ migrations = {
83
+ old_tag: script.migration_tag,
84
+ new_tag: config.migrations[config.migrations.length - 1].tag,
85
+ steps: config.migrations
86
+ .slice(foundIndex + 1)
87
+ .map(({ tag: _tag, ...rest }) => rest),
88
+ };
89
+ }
90
+ // else, we're up to date, no migrations to send
91
+ }
92
+ } else {
93
+ // first time publishing durable objects to this script,
94
+ // so we send all the migrations
95
+ migrations = {
96
+ new_tag: config.migrations[config.migrations.length - 1].tag,
97
+ steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
98
+ };
99
+ }
100
+ }
101
+ return migrations;
102
+ }
package/src/index.tsx CHANGED
@@ -11,6 +11,7 @@ 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";
@@ -43,6 +44,7 @@ import {
43
44
  formatMessage,
44
45
  ParseError,
45
46
  parseJSON,
47
+ parsePackageJSON,
46
48
  parseTOML,
47
49
  readFileSync,
48
50
  } from "./parse";
@@ -77,6 +79,17 @@ const resetColor = "\x1b[0m";
77
79
  const fgGreenColor = "\x1b[32m";
78
80
  const DEFAULT_LOCAL_PORT = 8787;
79
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
+ }
92
+
80
93
  function getRules(config: Config): Config["rules"] {
81
94
  const rules = config.rules ?? config.build?.upload?.rules ?? [];
82
95
 
@@ -333,6 +346,9 @@ function createCLIParser(argv: string[]) {
333
346
  throw new CommandLineArgsError(message);
334
347
  }
335
348
 
349
+ const devDepsToInstall: string[] = [];
350
+ const instructions: string[] = [];
351
+ let shouldRunPackageManagerInstall = false;
336
352
  const creationDirectory = path.resolve(process.cwd(), args.name ?? "");
337
353
 
338
354
  if (args.site) {
@@ -428,7 +444,9 @@ function createCLIParser(argv: string[]) {
428
444
  yesFlag ||
429
445
  (await confirm("Would you like to use git to manage this Worker?"));
430
446
  if (shouldInitGit) {
431
- await execa("git", ["init"], { cwd: creationDirectory });
447
+ await execa("git", ["init", "--initial-branch=main"], {
448
+ cwd: creationDirectory,
449
+ });
432
450
  await writeFile(
433
451
  path.join(creationDirectory, ".gitignore"),
434
452
  readFileSync(path.join(__dirname, "../templates/gitignore"))
@@ -474,7 +492,7 @@ function createCLIParser(argv: string[]) {
474
492
  ) + "\n"
475
493
  );
476
494
 
477
- await packageManager.install();
495
+ shouldRunPackageManagerInstall = true;
478
496
  pathToPackageJson = path.join(creationDirectory, "package.json");
479
497
  logger.log(
480
498
  `✨ Created ${path.relative(process.cwd(), pathToPackageJson)}`
@@ -485,7 +503,7 @@ function createCLIParser(argv: string[]) {
485
503
  } else {
486
504
  // If package.json exists and wrangler isn't installed,
487
505
  // then ask to add wrangler to devDependencies
488
- const packageJson = parseJSON(
506
+ const packageJson = parsePackageJSON(
489
507
  readFileSync(pathToPackageJson),
490
508
  pathToPackageJson
491
509
  );
@@ -504,8 +522,7 @@ function createCLIParser(argv: string[]) {
504
522
  )}?`
505
523
  ));
506
524
  if (shouldInstall) {
507
- await packageManager.addDevDeps(`wrangler@${wranglerVersion}`);
508
- logger.log(`✨ Installed wrangler`);
525
+ devDepsToInstall.push(`wrangler@${wranglerVersion}`);
509
526
  }
510
527
  }
511
528
  }
@@ -523,23 +540,18 @@ function createCLIParser(argv: string[]) {
523
540
  path.join(creationDirectory, "./tsconfig.json"),
524
541
  readFileSync(path.join(__dirname, "../templates/tsconfig.json"))
525
542
  );
526
- await packageManager.addDevDeps(
527
- "@cloudflare/workers-types",
528
- "typescript"
529
- );
543
+ devDepsToInstall.push("@cloudflare/workers-types");
544
+ devDepsToInstall.push("typescript");
530
545
  pathToTSConfig = path.join(creationDirectory, "tsconfig.json");
531
546
  logger.log(
532
- `✨ Created ${path.relative(
533
- process.cwd(),
534
- pathToTSConfig
535
- )}, installed @cloudflare/workers-types into devDependencies`
547
+ `✨ Created ${path.relative(process.cwd(), pathToTSConfig)}`
536
548
  );
537
549
  }
538
550
  } else {
539
551
  isTypescriptProject = true;
540
552
  // If there's a tsconfig, check if @cloudflare/workers-types
541
553
  // is already installed, and offer to install it if not
542
- const packageJson = parseJSON(
554
+ const packageJson = parsePackageJSON(
543
555
  readFileSync(pathToPackageJson),
544
556
  pathToPackageJson
545
557
  );
@@ -553,13 +565,13 @@ function createCLIParser(argv: string[]) {
553
565
  "Would you like to install the type definitions for Workers into your package.json?"
554
566
  );
555
567
  if (shouldInstall) {
556
- await packageManager.addDevDeps("@cloudflare/workers-types");
568
+ devDepsToInstall.push("@cloudflare/workers-types");
557
569
  // We don't update the tsconfig.json because
558
570
  // it could be complicated in existing projects
559
571
  // and we don't want to break them. Instead, we simply
560
572
  // tell the user that they need to update their tsconfig.json
561
- logger.log(
562
- `✨ 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(
563
575
  process.cwd(),
564
576
  pathToTSConfig
565
577
  )}`
@@ -568,7 +580,7 @@ function createCLIParser(argv: string[]) {
568
580
  }
569
581
  }
570
582
 
571
- const packageJsonContent = parseJSON(
583
+ const packageJsonContent = parsePackageJSON(
572
584
  readFileSync(pathToPackageJson),
573
585
  pathToPackageJson
574
586
  );
@@ -577,6 +589,41 @@ function createCLIParser(argv: string[]) {
577
589
  !packageJsonContent.scripts?.publish &&
578
590
  shouldCreatePackageJson;
579
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
+
580
627
  async function writePackageJsonScriptsAndUpdateWranglerToml(
581
628
  isWritingScripts: boolean,
582
629
  isCreatingWranglerToml: boolean,
@@ -620,21 +667,21 @@ function createCLIParser(argv: string[]) {
620
667
  2
621
668
  ) + "\n"
622
669
  );
623
- logger.log(
670
+ instructions.push(
624
671
  `\nTo start developing your Worker, run \`${
625
672
  isNamedWorker ? `cd ${args.name} && ` : ""
626
673
  }npm start\``
627
674
  );
628
- logger.log(
675
+ instructions.push(
629
676
  `To publish your Worker to the Internet, run \`npm run publish\``
630
677
  );
631
678
  } else {
632
- logger.log(
679
+ instructions.push(
633
680
  `\nTo start developing your Worker, run \`npx wrangler dev\`${
634
681
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
635
682
  }`
636
683
  );
637
- logger.log(
684
+ instructions.push(
638
685
  `To publish your Worker to the Internet, run \`npx wrangler publish\`${
639
686
  isCreatingWranglerToml ? "" : ` ${scriptPath}`
640
687
  }`
@@ -713,6 +760,21 @@ function createCLIParser(argv: string[]) {
713
760
  }
714
761
  }
715
762
  }
763
+ // install packages as the final step of init
764
+ try {
765
+ await installPackages(shouldRunPackageManagerInstall, devDepsToInstall);
766
+ } catch (e) {
767
+ // fetching packages could fail due to loss of internet, etc
768
+ // we should let folks know we failed to fetch, but their
769
+ // workers project is still ready to go
770
+ logger.error(e instanceof Error ? e.message : e);
771
+ instructions.push(
772
+ "\n🚨 wrangler was unable to fetch your npm packages, but your project is ready to go"
773
+ );
774
+ }
775
+
776
+ // let users know what to do now
777
+ instructions.forEach((instruction) => logger.log(instruction));
716
778
  }
717
779
  );
718
780
 
@@ -723,13 +785,34 @@ function createCLIParser(argv: string[]) {
723
785
  (yargs) => {
724
786
  return yargs.option("env", {
725
787
  describe: "Perform on a specific environment",
788
+ type: "string",
726
789
  });
727
790
  },
728
- () => {
791
+ async (buildArgs) => {
729
792
  // "[DEPRECATED] šŸ¦€ Build your project (if applicable)",
730
- throw new DeprecationError(
731
- "`wrangler build` has been deprecated, please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#build for alternatives"
793
+
794
+ const envFlag = buildArgs.env ? ` --env=${buildArgs.env}` : "";
795
+ logger.log(
796
+ formatMessage({
797
+ kind: "warning",
798
+ text: "Deprecation: `wrangler build` has been deprecated.",
799
+ notes: [
800
+ {
801
+ text: "Please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#build for more information.",
802
+ },
803
+ {
804
+ text: `Attempting to run \`wrangler publish --dry-run --outdir=dist${envFlag}\` for you instead:`,
805
+ },
806
+ ],
807
+ })
732
808
  );
809
+
810
+ await createCLIParser([
811
+ "publish",
812
+ "--dry-run",
813
+ "--outdir=dist",
814
+ ...(buildArgs.env ? ["--env", buildArgs.env] : []),
815
+ ]).parse();
733
816
  }
734
817
  );
735
818
 
@@ -1286,6 +1369,7 @@ function createCLIParser(argv: string[]) {
1286
1369
  args.siteInclude,
1287
1370
  args.siteExclude
1288
1371
  );
1372
+
1289
1373
  await publish({
1290
1374
  config,
1291
1375
  accountId,
@@ -2396,14 +2480,7 @@ function createCLIParser(argv: string[]) {
2396
2480
  }
2397
2481
 
2398
2482
  const accountId = await requireAuth(config);
2399
- await putKVBulkKeyValue(
2400
- accountId,
2401
- namespaceId,
2402
- content,
2403
- (index, total) => {
2404
- logger.log(`Uploaded ${index} of ${total}.`);
2405
- }
2406
- );
2483
+ await putKVBulkKeyValue(accountId, namespaceId, content);
2407
2484
 
2408
2485
  logger.log("Success!");
2409
2486
  }
@@ -2493,14 +2570,7 @@ function createCLIParser(argv: string[]) {
2493
2570
 
2494
2571
  const accountId = await requireAuth(config);
2495
2572
 
2496
- await deleteKVBulkKeyValue(
2497
- accountId,
2498
- namespaceId,
2499
- content,
2500
- (index, total) => {
2501
- logger.log(`Deleted ${index} of ${total}.`);
2502
- }
2503
- );
2573
+ await deleteKVBulkKeyValue(accountId, namespaceId, content);
2504
2574
 
2505
2575
  logger.log("Success!");
2506
2576
  }
package/src/inspect.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import assert from "node:assert";
2
2
  import { createServer } from "node:http";
3
+ import os from "node:os";
3
4
  import { URL } from "node:url";
4
5
 
6
+ import open from "open";
5
7
  import { useEffect, useRef, useState } from "react";
6
8
  import WebSocket, { WebSocketServer } from "ws";
7
9
  import { version } from "../package.json";
@@ -621,3 +623,40 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
621
623
  logger.warn("console event:", evt);
622
624
  }
623
625
  }
626
+
627
+ /**
628
+ * Opens the chrome debugger
629
+ */
630
+ export const openInspector = async (inspectorPort: number) => {
631
+ const url = `https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:${inspectorPort}/ws`;
632
+ const errorMessage =
633
+ "Failed to open inspector.\nInspector depends on having a Chromium-based browser installed, maybe you need to install one?";
634
+
635
+ // see: https://github.com/sindresorhus/open/issues/177#issue-610016699
636
+ let braveBrowser: string;
637
+ switch (os.platform()) {
638
+ case "darwin":
639
+ case "win32":
640
+ braveBrowser = "Brave";
641
+ break;
642
+ default:
643
+ braveBrowser = "brave";
644
+ }
645
+
646
+ const childProcess = await open(url, {
647
+ app: [
648
+ {
649
+ name: open.apps.chrome,
650
+ },
651
+ {
652
+ name: braveBrowser,
653
+ },
654
+ {
655
+ name: open.apps.edge,
656
+ },
657
+ ],
658
+ });
659
+ childProcess.on("error", () => {
660
+ logger.warn(errorMessage);
661
+ });
662
+ };