wrangler 2.0.7 → 2.0.8

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.
@@ -94,22 +94,6 @@ export function normalizeAndValidateConfig(
94
94
  "boolean"
95
95
  );
96
96
 
97
- validateOptionalProperty(
98
- diagnostics,
99
- "",
100
- "minify",
101
- rawConfig.minify,
102
- "boolean"
103
- );
104
-
105
- validateOptionalProperty(
106
- diagnostics,
107
- "",
108
- "node_compat",
109
- rawConfig.node_compat,
110
- "boolean"
111
- );
112
-
113
97
  // TODO: set the default to false to turn on service environments as the default
114
98
  const isLegacyEnv =
115
99
  (args as { "legacy-env": boolean | undefined })["legacy-env"] ??
@@ -400,29 +384,38 @@ function normalizeAndValidateMigrations(
400
384
  return [];
401
385
  } else {
402
386
  for (let i = 0; i < rawMigrations.length; i++) {
403
- const migration = rawMigrations[i];
387
+ const { tag, new_classes, renamed_classes, deleted_classes, ...rest } =
388
+ rawMigrations[i];
389
+
390
+ validateAdditionalProperties(
391
+ diagnostics,
392
+ "migrations",
393
+ Object.keys(rest),
394
+ []
395
+ );
396
+
404
397
  validateRequiredProperty(
405
398
  diagnostics,
406
399
  `migrations[${i}]`,
407
400
  `tag`,
408
- migration.tag,
401
+ tag,
409
402
  "string"
410
403
  );
411
404
  validateOptionalTypedArray(
412
405
  diagnostics,
413
406
  `migrations[${i}].new_classes`,
414
- migration.new_classes,
407
+ new_classes,
415
408
  "string"
416
409
  );
417
- if (migration.renamed_classes !== undefined) {
418
- if (!Array.isArray(migration.renamed_classes)) {
410
+ if (renamed_classes !== undefined) {
411
+ if (!Array.isArray(renamed_classes)) {
419
412
  diagnostics.errors.push(
420
413
  `Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
421
- migration.renamed_classes
414
+ renamed_classes
422
415
  )}.`
423
416
  );
424
417
  } else if (
425
- migration.renamed_classes.some(
418
+ renamed_classes.some(
426
419
  (c) =>
427
420
  typeof c !== "object" ||
428
421
  !isRequiredProperty(c, "from", "string") ||
@@ -431,7 +424,7 @@ function normalizeAndValidateMigrations(
431
424
  ) {
432
425
  diagnostics.errors.push(
433
426
  `Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
434
- migration.renamed_classes
427
+ renamed_classes
435
428
  )}.`
436
429
  );
437
430
  }
@@ -439,7 +432,7 @@ function normalizeAndValidateMigrations(
439
432
  validateOptionalTypedArray(
440
433
  diagnostics,
441
434
  `migrations[${i}].deleted_classes`,
442
- migration.deleted_classes,
435
+ deleted_classes,
443
436
  "string"
444
437
  );
445
438
  }
@@ -453,13 +446,32 @@ function normalizeAndValidateMigrations(
453
446
  (binding) => !binding.script_name
454
447
  );
455
448
  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(
449
+ if (
450
+ !exportedDurableObjects.some(
451
+ (exportedDurableObject) =>
452
+ typeof exportedDurableObject.class_name !== "string"
453
+ )
454
+ ) {
455
+ const durableObjectClassnames = exportedDurableObjects.map(
456
+ (durable) => durable.class_name
457
+ );
458
+
459
+ diagnostics.warnings.push(
460
+ `In wrangler.toml, you have configured [durable_objects] exported by this Worker (${durableObjectClassnames.join(
460
461
  ", "
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
- );
462
+ )}), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml. Add this configuration to your wrangler.toml:
463
+
464
+ \`\`\`
465
+ [[migrations]]
466
+ tag = "v1" # Should be unique for each entry
467
+ new_classes = [${durableObjectClassnames
468
+ .map((name) => `"${name}"`)
469
+ .join(", ")}]
470
+ \`\`\`
471
+
472
+ Refer to https://developers.cloudflare.com/workers/learning/using-durable-objects/#durable-object-migrations-in-wranglertoml for more details.`
473
+ );
474
+ }
463
475
  }
464
476
  }
465
477
 
@@ -928,8 +940,22 @@ function normalizeAndValidateEnvironment(
928
940
  }
929
941
  ),
930
942
  zone_id: rawEnv.zone_id,
931
- minify: rawEnv.minify,
932
- node_compat: rawEnv.node_compat,
943
+ minify: inheritable(
944
+ diagnostics,
945
+ topLevelEnv,
946
+ rawEnv,
947
+ "minify",
948
+ isBoolean,
949
+ undefined
950
+ ),
951
+ node_compat: inheritable(
952
+ diagnostics,
953
+ topLevelEnv,
954
+ rawEnv,
955
+ "node_compat",
956
+ isBoolean,
957
+ undefined
958
+ ),
933
959
  };
934
960
 
935
961
  return environment;
package/src/dev/dev.tsx CHANGED
@@ -9,7 +9,6 @@ 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";
13
12
  import { runCustomBuild } from "../entry";
14
13
  import { openInspector } from "../inspect";
15
14
  import { logger } from "../logger";
@@ -106,8 +105,6 @@ export function DevImplementation(props: DevProps): JSX.Element {
106
105
  nodeCompat: props.nodeCompat,
107
106
  });
108
107
 
109
- printBindings(props.bindings);
110
-
111
108
  // only load the UI if we're running in a supported environment
112
109
  const { isRawModeSupported } = useStdin();
113
110
  return isRawModeSupported ? (
package/src/dialogs.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import { Box, Text, useInput, render } from "ink";
3
+ import SelectInput from "ink-select-input";
3
4
  import TextInput from "ink-text-input";
4
5
  import * as React from "react";
5
6
  import { useState } from "react";
@@ -85,3 +86,50 @@ export async function prompt(
85
86
  );
86
87
  });
87
88
  }
89
+
90
+ type SelectOption = {
91
+ value: string;
92
+ label: string;
93
+ };
94
+
95
+ type SelectProps = {
96
+ text: string;
97
+ options: SelectOption[];
98
+ initialIndex: number;
99
+ onSelect: (value: string) => void;
100
+ };
101
+
102
+ function Select(props: SelectProps) {
103
+ return (
104
+ <Box flexDirection="column">
105
+ <Text>{props.text}</Text>
106
+ <SelectInput
107
+ initialIndex={props.initialIndex}
108
+ items={props.options}
109
+ onSelect={async (selected) => {
110
+ props.onSelect(selected.value);
111
+ }}
112
+ />
113
+ </Box>
114
+ );
115
+ }
116
+
117
+ export function select(
118
+ text: string,
119
+ options: SelectOption[],
120
+ initialIndex: number
121
+ ): Promise<string> {
122
+ return new Promise((resolve) => {
123
+ const { unmount } = render(
124
+ <Select
125
+ text={text}
126
+ options={options}
127
+ initialIndex={initialIndex}
128
+ onSelect={(option: string) => {
129
+ unmount();
130
+ resolve(option);
131
+ }}
132
+ />
133
+ );
134
+ });
135
+ }
package/src/index.tsx CHANGED
@@ -15,11 +15,11 @@ import { setGlobalDispatcher, ProxyAgent } from "undici";
15
15
  import makeCLI from "yargs";
16
16
  import { version as wranglerVersion } from "../package.json";
17
17
  import { fetchResult } from "./cfetch";
18
- import { findWranglerToml, readConfig } from "./config";
18
+ import { findWranglerToml, printBindings, readConfig } from "./config";
19
19
  import { createWorkerUploadForm } from "./create-worker-upload-form";
20
20
  import Dev from "./dev/dev";
21
21
  import { getVarsForDev } from "./dev/dev-vars";
22
- import { confirm, prompt } from "./dialogs";
22
+ import { confirm, prompt, select } from "./dialogs";
23
23
  import { getEntry } from "./entry";
24
24
  import { DeprecationError } from "./errors";
25
25
  import {
@@ -628,21 +628,21 @@ function createCLIParser(argv: string[]) {
628
628
  isWritingScripts: boolean,
629
629
  isCreatingWranglerToml: boolean,
630
630
  packagePath: string,
631
- scriptPath: string
631
+ scriptPath: string,
632
+ extraToml: TOML.JsonMap
632
633
  ) {
633
634
  if (isCreatingWranglerToml) {
634
- // rewrite wrangler.toml with main = "path/to/script"
635
+ // rewrite wrangler.toml with main = "path/to/script" and any additional config specified in `extraToml`
635
636
  const parsedWranglerToml = parseTOML(
636
637
  readFileSync(wranglerTomlDestination)
637
638
  );
638
- fs.writeFileSync(
639
- wranglerTomlDestination,
640
- TOML.stringify({
641
- name: parsedWranglerToml.name,
642
- main: scriptPath,
643
- compatibility_date: parsedWranglerToml.compatibility_date,
644
- })
645
- );
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));
646
646
  }
647
647
  const isNamedWorker =
648
648
  isCreatingWranglerToml && path.dirname(packagePath) !== process.cwd();
@@ -658,7 +658,7 @@ function createCLIParser(argv: string[]) {
658
658
  start: isCreatingWranglerToml
659
659
  ? `wrangler dev`
660
660
  : `wrangler dev ${scriptPath}`,
661
- publish: isCreatingWranglerToml
661
+ deploy: isCreatingWranglerToml
662
662
  ? `wrangler publish`
663
663
  : `wrangler publish ${scriptPath}`,
664
664
  },
@@ -673,7 +673,7 @@ function createCLIParser(argv: string[]) {
673
673
  }npm start\``
674
674
  );
675
675
  instructions.push(
676
- `To publish your Worker to the Internet, run \`npm run publish\``
676
+ `To publish your Worker to the Internet, run \`npm run deploy\``
677
677
  );
678
678
  } else {
679
679
  instructions.push(
@@ -689,24 +689,75 @@ function createCLIParser(argv: string[]) {
689
689
  }
690
690
  }
691
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
+
692
741
  if (isTypescriptProject) {
693
742
  if (!fs.existsSync(path.join(creationDirectory, "./src/index.ts"))) {
694
- const shouldCreateSource =
695
- yesFlag ||
696
- (await confirm(
697
- `Would you like to create a Worker at ${path.relative(
698
- process.cwd(),
699
- path.join(creationDirectory, "./src/index.ts")
700
- )}?`
701
- ));
743
+ const newWorkerFilename = path.relative(
744
+ process.cwd(),
745
+ path.join(creationDirectory, "./src/index.ts")
746
+ );
747
+
748
+ const newWorkerType = yesFlag
749
+ ? "fetch"
750
+ : await getNewWorkerType(newWorkerFilename);
751
+
752
+ if (newWorkerType !== "none") {
753
+ const template = getNewWorkerTemplate("ts", newWorkerType);
702
754
 
703
- if (shouldCreateSource) {
704
755
  await mkdir(path.join(creationDirectory, "./src"), {
705
756
  recursive: true,
706
757
  });
707
758
  await writeFile(
708
759
  path.join(creationDirectory, "./src/index.ts"),
709
- readFileSync(path.join(__dirname, "../templates/new-worker.ts"))
760
+ readFileSync(path.join(__dirname, `../templates/${template}`))
710
761
  );
711
762
 
712
763
  logger.log(
@@ -720,28 +771,31 @@ function createCLIParser(argv: string[]) {
720
771
  shouldWritePackageJsonScripts,
721
772
  justCreatedWranglerToml,
722
773
  pathToPackageJson,
723
- "src/index.ts"
774
+ "src/index.ts",
775
+ getNewWorkerToml(newWorkerType)
724
776
  );
725
777
  }
726
778
  }
727
779
  } else {
728
780
  if (!fs.existsSync(path.join(creationDirectory, "./src/index.js"))) {
729
- const shouldCreateSource =
730
- yesFlag ||
731
- (await confirm(
732
- `Would you like to create a Worker at ${path.relative(
733
- process.cwd(),
734
- path.join(creationDirectory, "./src/index.js")
735
- )}?`
736
- ));
781
+ const newWorkerFilename = path.relative(
782
+ process.cwd(),
783
+ path.join(creationDirectory, "./src/index.js")
784
+ );
785
+
786
+ const newWorkerType = yesFlag
787
+ ? "fetch"
788
+ : await getNewWorkerType(newWorkerFilename);
789
+
790
+ if (newWorkerType !== "none") {
791
+ const template = getNewWorkerTemplate("js", newWorkerType);
737
792
 
738
- if (shouldCreateSource) {
739
793
  await mkdir(path.join(creationDirectory, "./src"), {
740
794
  recursive: true,
741
795
  });
742
796
  await writeFile(
743
797
  path.join(creationDirectory, "./src/index.js"),
744
- readFileSync(path.join(__dirname, "../templates/new-worker.js"))
798
+ readFileSync(path.join(__dirname, `../templates/${template}`))
745
799
  );
746
800
 
747
801
  logger.log(
@@ -755,7 +809,8 @@ function createCLIParser(argv: string[]) {
755
809
  shouldWritePackageJsonScripts,
756
810
  justCreatedWranglerToml,
757
811
  pathToPackageJson,
758
- "src/index.js"
812
+ "src/index.js",
813
+ getNewWorkerToml(newWorkerType)
759
814
  );
760
815
  }
761
816
  }
@@ -1119,6 +1174,70 @@ function createCLIParser(argv: string[]) {
1119
1174
  );
1120
1175
  }
1121
1176
 
1177
+ const bindings = {
1178
+ kv_namespaces: config.kv_namespaces?.map(
1179
+ ({ binding, preview_id, id: _id }) => {
1180
+ // In `dev`, we make folks use a separate kv namespace called
1181
+ // `preview_id` instead of `id` so that they don't
1182
+ // break production data. So here we check that a `preview_id`
1183
+ // has actually been configured.
1184
+ // This whole block of code will be obsoleted in the future
1185
+ // when we have copy-on-write for previews on edge workers.
1186
+ if (!preview_id) {
1187
+ // TODO: This error has to be a _lot_ better, ideally just asking
1188
+ // to create a preview namespace for the user automatically
1189
+ throw new Error(
1190
+ `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`
1191
+ ); // Ugh, I really don't like this message very much
1192
+ }
1193
+ return {
1194
+ binding,
1195
+ id: preview_id,
1196
+ };
1197
+ }
1198
+ ),
1199
+ // Use a copy of combinedVars since we're modifying it later
1200
+ vars: getVarsForDev(config),
1201
+ wasm_modules: config.wasm_modules,
1202
+ text_blobs: config.text_blobs,
1203
+ data_blobs: config.data_blobs,
1204
+ durable_objects: config.durable_objects,
1205
+ r2_buckets: config.r2_buckets?.map(
1206
+ ({ binding, preview_bucket_name, bucket_name: _bucket_name }) => {
1207
+ // same idea as kv namespace preview id,
1208
+ // same copy-on-write TODO
1209
+ if (!preview_bucket_name) {
1210
+ throw new Error(
1211
+ `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`
1212
+ );
1213
+ }
1214
+ return {
1215
+ binding,
1216
+ bucket_name: preview_bucket_name,
1217
+ };
1218
+ }
1219
+ ),
1220
+ services: config.services,
1221
+ unsafe: config.unsafe?.bindings,
1222
+ };
1223
+
1224
+ // mask anything that was overridden in .dev.vars
1225
+ // so that we don't log potential secrets into the terminal
1226
+ const maskedVars = { ...bindings.vars };
1227
+ for (const key of Object.keys(maskedVars)) {
1228
+ if (maskedVars[key] !== config.vars[key]) {
1229
+ // This means it was overridden in .dev.vars
1230
+ // so let's mask it
1231
+ maskedVars[key] = "(hidden)";
1232
+ }
1233
+ }
1234
+
1235
+ // now log all available bindings into the terminal
1236
+ printBindings({
1237
+ ...bindings,
1238
+ vars: maskedVars,
1239
+ });
1240
+
1122
1241
  const { waitUntilExit } = render(
1123
1242
  <Dev
1124
1243
  name={getScriptName(args, config)}
@@ -1164,51 +1283,7 @@ function createCLIParser(argv: string[]) {
1164
1283
  args["compatibility-flags"] || config.compatibility_flags
1165
1284
  }
1166
1285
  usageModel={config.usage_model}
1167
- bindings={{
1168
- kv_namespaces: config.kv_namespaces?.map(
1169
- ({ binding, preview_id, id: _id }) => {
1170
- // In `dev`, we make folks use a separate kv namespace called
1171
- // `preview_id` instead of `id` so that they don't
1172
- // break production data. So here we check that a `preview_id`
1173
- // has actually been configured.
1174
- // This whole block of code will be obsoleted in the future
1175
- // when we have copy-on-write for previews on edge workers.
1176
- if (!preview_id) {
1177
- // TODO: This error has to be a _lot_ better, ideally just asking
1178
- // to create a preview namespace for the user automatically
1179
- throw new Error(
1180
- `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`
1181
- ); // Ugh, I really don't like this message very much
1182
- }
1183
- return {
1184
- binding,
1185
- id: preview_id,
1186
- };
1187
- }
1188
- ),
1189
- vars: getVarsForDev(config),
1190
- wasm_modules: config.wasm_modules,
1191
- text_blobs: config.text_blobs,
1192
- data_blobs: config.data_blobs,
1193
- durable_objects: config.durable_objects,
1194
- r2_buckets: config.r2_buckets?.map(
1195
- ({ binding, preview_bucket_name, bucket_name: _bucket_name }) => {
1196
- // same idea as kv namespace preview id,
1197
- // same copy-on-write TODO
1198
- if (!preview_bucket_name) {
1199
- throw new Error(
1200
- `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`
1201
- );
1202
- }
1203
- return {
1204
- binding,
1205
- bucket_name: preview_bucket_name,
1206
- };
1207
- }
1208
- ),
1209
- services: config.services,
1210
- unsafe: config.unsafe?.bindings,
1211
- }}
1286
+ bindings={bindings}
1212
1287
  crons={config.triggers.crons}
1213
1288
  />
1214
1289
  );
@@ -2752,14 +2827,14 @@ export async function main(argv: string[]): Promise<void> {
2752
2827
  await createCLIParser([...argv, "--help"]).parse();
2753
2828
  } else if (e instanceof ParseError) {
2754
2829
  e.notes.push({
2755
- 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",
2756
2831
  });
2757
- logger.error(formatMessage(e));
2832
+ logger.log(formatMessage(e));
2758
2833
  } else {
2759
2834
  logger.error(e instanceof Error ? e.message : e);
2760
2835
  logger.log(
2761
2836
  `${fgGreenColor}%s${resetColor}`,
2762
- "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"
2763
2838
  );
2764
2839
  }
2765
2840
  throw e;
@@ -5,15 +5,20 @@ import { logger } from "./logger";
5
5
 
6
6
  export interface PackageManager {
7
7
  cwd: string;
8
- type: "npm" | "yarn";
8
+ type: "npm" | "yarn" | "pnpm";
9
9
  addDevDeps(...packages: string[]): Promise<void>;
10
10
  install(): Promise<void>;
11
11
  }
12
12
 
13
13
  export async function getPackageManager(cwd: string): Promise<PackageManager> {
14
- const [hasYarn, hasNpm] = await Promise.all([supportsYarn(), supportsNpm()]);
14
+ const [hasYarn, hasNpm, hasPnpm] = await Promise.all([
15
+ supportsYarn(),
16
+ supportsNpm(),
17
+ supportsPnpm(),
18
+ ]);
15
19
  const hasYarnLock = existsSync(join(cwd, "yarn.lock"));
16
20
  const hasNpmLock = existsSync(join(cwd, "package-lock.json"));
21
+ const hasPnpmLock = existsSync(join(cwd, "pnpm-lock.yaml"));
17
22
 
18
23
  if (hasNpmLock) {
19
24
  if (hasNpm) {
@@ -28,6 +33,18 @@ export async function getPackageManager(cwd: string): Promise<PackageManager> {
28
33
  );
29
34
  return { ...YarnPackageManager, cwd };
30
35
  }
36
+ } else if (hasPnpmLock) {
37
+ if (hasPnpm) {
38
+ logger.log(
39
+ "Using pnpm as package manager, as there is already a pnpm-lock.yaml file."
40
+ );
41
+ return { ...PnpmPackageManager, cwd };
42
+ } else {
43
+ logger.warn(
44
+ "There is already a pnpm-lock.yaml file but could not find pnpm on the PATH."
45
+ );
46
+ // will simply fallback to the first found of [npm, yaml, pnpm] in the next if round.
47
+ }
31
48
  } else if (hasYarnLock) {
32
49
  if (hasYarn) {
33
50
  logger.log(
@@ -49,9 +66,12 @@ export async function getPackageManager(cwd: string): Promise<PackageManager> {
49
66
  } else if (hasYarn) {
50
67
  logger.log("Using yarn as package manager.");
51
68
  return { ...YarnPackageManager, cwd };
69
+ } else if (hasPnpm) {
70
+ logger.log("Using pnpm as package manager.");
71
+ return { ...PnpmPackageManager, cwd };
52
72
  } else {
53
73
  throw new Error(
54
- "Unable to find a package manager. Supported managers are: npm and yarn."
74
+ "Unable to find a package manager. Supported managers are: npm, yarn, and pnpm."
55
75
  );
56
76
  }
57
77
  }
@@ -86,6 +106,29 @@ const NpmPackageManager: PackageManager = {
86
106
  },
87
107
  };
88
108
 
109
+ /**
110
+ * Manage packages using pnpm
111
+ */
112
+ const PnpmPackageManager: PackageManager = {
113
+ cwd: process.cwd(),
114
+ type: "pnpm",
115
+ /** Add and install a new devDependency into the local package.json. */
116
+ async addDevDeps(...packages: string[]): Promise<void> {
117
+ await execa("pnpm", ["install", ...packages, "--save-dev"], {
118
+ stdio: "inherit",
119
+ cwd: this.cwd,
120
+ });
121
+ },
122
+
123
+ /** Install all the dependencies in the local package.json. */
124
+ async install(): Promise<void> {
125
+ await execa("pnpm", ["install"], {
126
+ stdio: "inherit",
127
+ cwd: this.cwd,
128
+ });
129
+ },
130
+ };
131
+
89
132
  /**
90
133
  * Manage packages using yarn
91
134
  */
@@ -125,3 +168,7 @@ function supportsYarn(): Promise<boolean> {
125
168
  function supportsNpm(): Promise<boolean> {
126
169
  return supports("npm");
127
170
  }
171
+
172
+ function supportsPnpm(): Promise<boolean> {
173
+ return supports("pnpm");
174
+ }
package/src/pages.tsx CHANGED
@@ -1066,6 +1066,7 @@ const createDeployment: CommandModule<
1066
1066
  "_headers",
1067
1067
  ".DS_Store",
1068
1068
  "node_modules",
1069
+ ".git",
1069
1070
  ];
1070
1071
 
1071
1072
  const walk = async (
@@ -1522,6 +1523,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
1522
1523
  } else {
1523
1524
  logger.log("No functions. Shimming...");
1524
1525
  miniflareArgs = {
1526
+ // cfFetch sets the `cf` object that a function could expect
1527
+ // If there are no functions, there's no reason to set this up (and not make that network call)
1528
+ cfFetch: false,
1525
1529
  // TODO: The fact that these request/response hacks are necessary is ridiculous.
1526
1530
  // We need to eliminate them from env.ASSETS.fetch (not sure if just local or prod as well)
1527
1531
  script: `