wrangler 0.0.0-e2a26a0 → 0.0.0-e3cab74

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 (68) hide show
  1. package/package.json +16 -7
  2. package/pages/functions/filepath-routing.test.ts +112 -36
  3. package/pages/functions/filepath-routing.ts +66 -119
  4. package/pages/functions/routes.ts +8 -17
  5. package/pages/functions/template-worker.ts +3 -9
  6. package/src/__tests__/configuration.test.ts +2185 -0
  7. package/src/__tests__/dev.test.tsx +48 -6
  8. package/src/__tests__/guess-worker-format.test.ts +66 -0
  9. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  10. package/src/__tests__/helpers/mock-account-id.ts +6 -6
  11. package/src/__tests__/helpers/mock-bin.ts +4 -1
  12. package/src/__tests__/helpers/mock-cfetch.ts +2 -2
  13. package/src/__tests__/helpers/mock-console.ts +11 -5
  14. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  15. package/src/__tests__/helpers/run-wrangler.ts +5 -3
  16. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  17. package/src/__tests__/index.test.ts +343 -20
  18. package/src/__tests__/jest.setup.ts +21 -1
  19. package/src/__tests__/kv.test.ts +82 -80
  20. package/src/__tests__/package-manager.test.ts +2 -2
  21. package/src/__tests__/parse.test.ts +322 -0
  22. package/src/__tests__/publish.test.ts +1428 -244
  23. package/src/__tests__/r2.test.ts +198 -0
  24. package/src/__tests__/secret.test.ts +176 -60
  25. package/src/__tests__/sentry.test.ts +146 -0
  26. package/src/__tests__/tail.test.ts +564 -0
  27. package/src/api/form_data.ts +106 -23
  28. package/src/api/preview.ts +32 -13
  29. package/src/api/worker.ts +49 -18
  30. package/src/bundle.ts +133 -0
  31. package/src/cfetch/index.ts +8 -6
  32. package/src/cfetch/internal.ts +49 -17
  33. package/src/cli.ts +10 -0
  34. package/src/config/README.md +107 -0
  35. package/src/config/config.ts +265 -0
  36. package/src/config/diagnostics.ts +80 -0
  37. package/src/config/environment.ts +305 -0
  38. package/src/config/index.ts +52 -0
  39. package/src/config/validation-helpers.ts +370 -0
  40. package/src/config/validation.ts +1017 -0
  41. package/src/dev.tsx +338 -211
  42. package/src/entry.ts +57 -0
  43. package/src/environment-variables.ts +33 -0
  44. package/src/guess-worker-format.ts +68 -0
  45. package/src/index.tsx +860 -634
  46. package/src/inspect.ts +16 -3
  47. package/src/kv.tsx +20 -29
  48. package/src/module-collection.ts +141 -34
  49. package/src/open-in-browser.ts +13 -0
  50. package/src/package-manager.ts +21 -15
  51. package/src/pages.tsx +9 -10
  52. package/src/parse.ts +183 -0
  53. package/src/proxy.ts +11 -5
  54. package/src/publish.ts +272 -314
  55. package/src/r2.ts +50 -0
  56. package/src/reporting.ts +115 -0
  57. package/src/sites.tsx +16 -15
  58. package/src/tail/filters.ts +279 -0
  59. package/src/tail/index.ts +299 -0
  60. package/src/tail/printing.ts +62 -0
  61. package/src/user.tsx +74 -44
  62. package/templates/new-worker.js +1 -1
  63. package/templates/new-worker.ts +1 -1
  64. package/templates/tsconfig.json +105 -0
  65. package/wrangler-dist/cli.js +51308 -36440
  66. package/wrangler-dist/cli.js.map +3 -3
  67. package/src/config.ts +0 -641
  68. package/src/tail.tsx +0 -73
package/src/inspect.ts CHANGED
@@ -367,7 +367,10 @@ export default function useInspector(props: InspectorProps) {
367
367
  );
368
368
  remoteWebSocket.send(event.data);
369
369
  } catch (e) {
370
- if (e.message !== "WebSocket is not open: readyState 0 (CONNECTING)") {
370
+ if (
371
+ (e as Error).message !==
372
+ "WebSocket is not open: readyState 0 (CONNECTING)"
373
+ ) {
371
374
  /**
372
375
  * ^ this just means we haven't opened a websocket yet
373
376
  * usually happens until there's at least one request
@@ -587,8 +590,18 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
587
590
  const method = mapConsoleAPIMessageTypeToConsoleMethod[evt.type];
588
591
 
589
592
  if (method in console) {
590
- // eslint-disable-next-line prefer-spread
591
- console[method].apply(console, args);
593
+ switch (method) {
594
+ case "dir":
595
+ console.dir(args);
596
+ break;
597
+ case "table":
598
+ console.table(args);
599
+ break;
600
+ default:
601
+ // eslint-disable-next-line prefer-spread
602
+ console[method].apply(console, args);
603
+ break;
604
+ }
592
605
  } else {
593
606
  console.warn(`Unsupported console method: ${method}`);
594
607
  console.log("console event:", evt);
package/src/kv.tsx CHANGED
@@ -1,13 +1,12 @@
1
1
  import { URLSearchParams } from "node:url";
2
2
  import { fetchListResult, fetchResult, fetchKVGetValue } from "./cfetch";
3
- import type { Config } from "./config";
3
+ import type { Config, Environment } from "./config";
4
4
 
5
5
  type KvArgs = {
6
6
  binding?: string;
7
7
  "namespace-id"?: string;
8
8
  env?: string;
9
9
  preview?: boolean;
10
- config?: Config;
11
10
  };
12
11
 
13
12
  /**
@@ -153,13 +152,10 @@ export async function deleteBulkKeyValue(
153
152
  );
154
153
  }
155
154
 
156
- export function getNamespaceId({
157
- preview,
158
- binding,
159
- config,
160
- "namespace-id": namespaceId,
161
- env,
162
- }: KvArgs): string {
155
+ export function getNamespaceId(
156
+ { preview, binding, "namespace-id": namespaceId, env }: KvArgs,
157
+ configOrEnv: Config | Environment | undefined
158
+ ): string {
163
159
  // nice
164
160
  if (namespaceId) {
165
161
  return namespaceId;
@@ -167,13 +163,13 @@ export function getNamespaceId({
167
163
 
168
164
  // begin pre-flight checks
169
165
 
170
- // `--binding` is only valid if there's a wrangler configuration file.
171
- if (binding && !config) {
166
+ // `--binding` is only valid if there's a wrangler configuration.
167
+ if (binding && !configOrEnv) {
172
168
  throw new Error("--binding specified, but no config file was found.");
173
169
  }
174
170
 
175
171
  // there's no config. abort here
176
- if (!config) {
172
+ if (!configOrEnv) {
177
173
  throw new Error(
178
174
  "Failed to find a config file.\n" +
179
175
  "Either use --namespace-id to upload directly or create a configuration file with a binding."
@@ -182,36 +178,31 @@ export function getNamespaceId({
182
178
 
183
179
  // they want to use an environment, actually
184
180
  if (env) {
185
- if (!config.env || !config.env[env]) {
181
+ if (!("env" in configOrEnv) || !configOrEnv.env[env]) {
186
182
  throw new Error(
187
183
  `Failed to find environment "${env}" in configuration file!`
188
184
  );
189
185
  }
190
-
191
- // TODO: either a bespoke arg type for this function to avoid `undefined`s or an `EnvOrConfig` type
192
- return getNamespaceId({
193
- binding,
194
- "namespace-id": namespaceId,
195
- env: undefined,
196
- preview,
197
- config: {
198
- env: undefined,
199
- build: undefined,
200
- name: undefined,
201
- account_id: undefined,
202
- ...config.env[env],
186
+ return getNamespaceId(
187
+ {
188
+ binding,
189
+ "namespace-id": namespaceId,
190
+ preview,
203
191
  },
204
- });
192
+ configOrEnv.env[env]
193
+ );
205
194
  }
206
195
 
207
196
  // there's no KV namespaces
208
- if (!config.kv_namespaces || config.kv_namespaces.length === 0) {
197
+ if (!configOrEnv.kv_namespaces || configOrEnv.kv_namespaces.length === 0) {
209
198
  throw new Error(
210
199
  "No KV Namespaces configured! Either use --namespace-id to upload directly or add a KV namespace to your wrangler config file."
211
200
  );
212
201
  }
213
202
 
214
- const namespace = config.kv_namespaces.find((ns) => ns.binding === binding);
203
+ const namespace = configOrEnv.kv_namespaces.find(
204
+ (ns) => ns.binding === binding
205
+ );
215
206
 
216
207
  // we couldn't find a namespace with that binding
217
208
  if (!namespace) {
@@ -1,21 +1,86 @@
1
1
  import crypto from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
- import type { CfModule } from "./api/worker";
4
+ import globToRegExp from "glob-to-regexp";
5
+ import type { CfModule, CfModuleType, CfScriptFormat } from "./api/worker";
6
+ import type { Config, ConfigModuleRuleType } from "./config";
5
7
  import type esbuild from "esbuild";
6
8
 
9
+ const RuleTypeToModuleType: Record<ConfigModuleRuleType, CfModuleType> = {
10
+ ESModule: "esm",
11
+ CommonJS: "commonjs",
12
+ CompiledWasm: "compiled-wasm",
13
+ Data: "buffer",
14
+ Text: "text",
15
+ };
16
+
7
17
  // This is a combination of an esbuild plugin and a mutable array
8
18
  // that we use to collect module references from source code.
9
19
  // There will be modules that _shouldn't_ be inlined directly into
10
20
  // the bundle. (eg. wasm modules, some text files, etc). We can include
11
- // those files as modules in the multi part forker form upload. This
21
+ // those files as modules in the multi part worker form upload. This
12
22
  // plugin+array is used to collect references to these modules, reference
13
23
  // them correctly in the bundle, and add them to the form upload.
14
24
 
15
- export default function makeModuleCollector(): {
25
+ export const DEFAULT_MODULE_RULES: Config["rules"] = [
26
+ { type: "Text", globs: ["**/*.txt", "**/*.html"] },
27
+ { type: "Data", globs: ["**/*.bin"] },
28
+ { type: "CompiledWasm", globs: ["**/*.wasm"] },
29
+ ];
30
+
31
+ export default function makeModuleCollector(props: {
32
+ format: CfScriptFormat;
33
+ rules?: Config["rules"];
34
+ }): {
16
35
  modules: CfModule[];
17
36
  plugin: esbuild.Plugin;
18
37
  } {
38
+ const rules: Config["rules"] = [
39
+ ...(props.rules || []),
40
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
41
+ ...DEFAULT_MODULE_RULES!,
42
+ ];
43
+
44
+ const completedRuleLocations: Record<string, number> = {};
45
+ let index = 0;
46
+ const rulesToRemove: Config["rules"] = [];
47
+ for (const rule of rules) {
48
+ if (rule.type in completedRuleLocations) {
49
+ if (rules[completedRuleLocations[rule.type]].fallthrough !== false) {
50
+ if (index < (props.rules || []).length) {
51
+ console.warn(
52
+ `The module rule at position ${index} (${JSON.stringify(
53
+ rule
54
+ )}) has the same type as a previous rule (at position ${
55
+ completedRuleLocations[rule.type]
56
+ }, ${JSON.stringify(
57
+ rules[completedRuleLocations[rule.type]]
58
+ )}). This rule will be ignored. To the previous rule, add \`fallthrough = true\` to allow this one to also be used, or \`fallthrough = false\` to silence this warning.`
59
+ );
60
+ } else {
61
+ console.warn(
62
+ `The default module rule ${JSON.stringify(
63
+ rule
64
+ )} has the same type as a previous rule (at position ${
65
+ completedRuleLocations[rule.type]
66
+ }, ${JSON.stringify(
67
+ rules[completedRuleLocations[rule.type]]
68
+ )}). This rule will be ignored. To the previous rule, add \`fallthrough = true\` to allow the default one to also be used, or \`fallthrough = false\` to silence this warning.`
69
+ );
70
+ }
71
+ }
72
+
73
+ rulesToRemove.push(rule);
74
+ }
75
+ if (!(rule.type in completedRuleLocations) && rule.fallthrough !== true) {
76
+ completedRuleLocations[rule.type] = index;
77
+ }
78
+ index++;
79
+ }
80
+
81
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
82
+ rulesToRemove.forEach((rule) => rules!.splice(rules!.indexOf(rule), 1));
83
+
19
84
  const modules: CfModule[] = [];
20
85
  return {
21
86
  modules,
@@ -23,40 +88,82 @@ export default function makeModuleCollector(): {
23
88
  name: "wrangler-module-collector",
24
89
  setup(build) {
25
90
  build.onStart(() => {
26
- // reset the moduels collection
91
+ // reset the module collection array
27
92
  modules.splice(0);
28
93
  });
29
94
 
30
- build.onResolve(
31
- // filter on "known" file types,
32
- // we can expand this list later
33
- { filter: /.*\.(wasm)$/ },
34
- async (args: esbuild.OnResolveArgs) => {
35
- // take the file and massage it to a
36
- // transportable/manageable format
37
- const filePath = path.join(args.resolveDir, args.path);
38
- const fileContent = await readFile(filePath);
39
- const fileHash = crypto
40
- .createHash("sha1")
41
- .update(fileContent)
42
- .digest("hex");
43
- const fileName = `${fileHash}-${path.basename(args.path)}`;
44
-
45
- // add the module to the array
46
- modules.push({
47
- name: fileName,
48
- content: fileContent,
49
- type: "compiled-wasm",
50
- });
51
-
52
- return {
53
- path: fileName, // change the reference to the changed module
54
- external: true, // mark it as external in the bundle
55
- namespace: "wrangler-module-collector-ns", // just a tag, this isn't strictly necessary
56
- watchFiles: [filePath], // we also add the file to esbuild's watch list
57
- };
58
- }
59
- );
95
+ rules?.forEach((rule) => {
96
+ if (rule.type === "ESModule" || rule.type === "CommonJS") return; // TODO: we should treat these as js files, and use the jsx loader
97
+
98
+ rule.globs.forEach((glob) => {
99
+ build.onResolve(
100
+ { filter: globToRegExp(glob) },
101
+ async (args: esbuild.OnResolveArgs) => {
102
+ // take the file and massage it to a
103
+ // transportable/manageable format
104
+
105
+ const filePath = path.join(args.resolveDir, args.path);
106
+ const fileContent = await readFile(filePath);
107
+ const fileHash = crypto
108
+ .createHash("sha1")
109
+ .update(fileContent)
110
+ .digest("hex");
111
+ const fileName = `./${fileHash}-${path.basename(args.path)}`;
112
+
113
+ // add the module to the array
114
+ modules.push({
115
+ name: fileName,
116
+ content: fileContent,
117
+ type: RuleTypeToModuleType[rule.type],
118
+ });
119
+
120
+ return {
121
+ path: fileName, // change the reference to the changed module
122
+ external: props.format === "modules", // mark it as external in the bundle
123
+ namespace: `wrangler-module-${rule.type}`, // just a tag, this isn't strictly necessary
124
+ watchFiles: [filePath], // we also add the file to esbuild's watch list
125
+ };
126
+ }
127
+ );
128
+
129
+ if (props.format === "service-worker") {
130
+ build.onLoad(
131
+ { filter: globToRegExp(glob) },
132
+ async (args: esbuild.OnLoadArgs) => {
133
+ return {
134
+ // We replace the the module with an identifier
135
+ // that we'll separately add to the form upload
136
+ // as part of [wasm_modules]/[text_blobs]. This identifier has to be a valid
137
+ // JS identifier, so we replace all non alphanumeric characters
138
+ // with an underscore.
139
+ // TODO: what of "Data"?
140
+ contents: `export default ${args.path.replace(
141
+ /[^a-zA-Z0-9_$]/g,
142
+ "_"
143
+ )};`,
144
+ };
145
+ }
146
+ );
147
+ }
148
+ });
149
+ });
150
+
151
+ rulesToRemove.forEach((rule) => {
152
+ rule.globs.forEach((glob) => {
153
+ build.onResolve(
154
+ { filter: globToRegExp(glob) },
155
+ async (args: esbuild.OnResolveArgs) => {
156
+ throw new Error(
157
+ `The file ${
158
+ args.path
159
+ } matched a module rule in your configuration (${JSON.stringify(
160
+ rule
161
+ )}), but was ignored because a previous rule with the same type was not marked as \`fallthrough = true\`.`
162
+ );
163
+ }
164
+ );
165
+ });
166
+ });
60
167
  },
61
168
  },
62
169
  };
@@ -0,0 +1,13 @@
1
+ import open from "open";
2
+ /**
3
+ * An extremely simple wrapper around the open command.
4
+ * Specifically, it adds an 'error' event handler so that when this function
5
+ * is called in environments where we can't open the browser (e.g. github codespaces,
6
+ * stackblitz, remote servers), it doesn't just crash the process.
7
+ */
8
+ export default async function openInBrowser(url: string): Promise<void> {
9
+ const childProcess = await open(url);
10
+ childProcess.on("error", () => {
11
+ console.warn(`Failed to open ${url} in a browser`);
12
+ });
13
+ }
@@ -3,49 +3,51 @@ import { join } from "node:path";
3
3
  import { execa, execaCommandSync } from "execa";
4
4
 
5
5
  export interface PackageManager {
6
+ cwd: string;
7
+ type: "npm" | "yarn";
6
8
  addDevDeps(...packages: string[]): Promise<void>;
7
9
  install(): Promise<void>;
8
10
  }
9
11
 
10
- export async function getPackageManager(root: string): Promise<PackageManager> {
12
+ export async function getPackageManager(cwd: string): Promise<PackageManager> {
11
13
  const [hasYarn, hasNpm] = await Promise.all([supportsYarn(), supportsNpm()]);
12
- const hasYarnLock = existsSync(join(root, "yarn.lock"));
13
- const hasNpmLock = existsSync(join(root, "package-lock.json"));
14
+ const hasYarnLock = existsSync(join(cwd, "yarn.lock"));
15
+ const hasNpmLock = existsSync(join(cwd, "package-lock.json"));
14
16
 
15
17
  if (hasNpmLock) {
16
18
  if (hasNpm) {
17
19
  console.log(
18
20
  "Using npm as package manager, as there is already a package-lock.json file."
19
21
  );
20
- return NpmPackageManager;
22
+ return { ...NpmPackageManager, cwd };
21
23
  } else if (hasYarn) {
22
24
  console.log("Using yarn as package manager.");
23
25
  console.warn(
24
26
  "There is already a package-lock.json file but could not find npm on the PATH."
25
27
  );
26
- return YarnPackageManager;
28
+ return { ...YarnPackageManager, cwd };
27
29
  }
28
30
  } else if (hasYarnLock) {
29
31
  if (hasYarn) {
30
32
  console.log(
31
33
  "Using yarn as package manager, as there is already a yarn.lock file."
32
34
  );
33
- return YarnPackageManager;
35
+ return { ...YarnPackageManager, cwd };
34
36
  } else if (hasNpm) {
35
37
  console.log("Using npm as package manager.");
36
38
  console.warn(
37
39
  "There is already a yarn.lock file but could not find yarn on the PATH."
38
40
  );
39
- return NpmPackageManager;
41
+ return { ...NpmPackageManager, cwd };
40
42
  }
41
43
  }
42
44
 
43
45
  if (hasNpm) {
44
46
  console.log("Using npm as package manager.");
45
- return NpmPackageManager;
47
+ return { ...NpmPackageManager, cwd };
46
48
  } else if (hasYarn) {
47
49
  console.log("Using yarn as package manager.");
48
- return YarnPackageManager;
50
+ return { ...YarnPackageManager, cwd };
49
51
  } else {
50
52
  throw new Error(
51
53
  "Unable to find a package manager. Supported managers are: npm and yarn."
@@ -56,22 +58,21 @@ export async function getPackageManager(root: string): Promise<PackageManager> {
56
58
  /**
57
59
  * Get the name of the given `packageManager`.
58
60
  */
59
- export function getPackageManagerName(packageManager: unknown): string {
60
- return packageManager === NpmPackageManager
61
- ? "npm"
62
- : packageManager === YarnPackageManager
63
- ? "yarn"
64
- : "unknown";
61
+ export function getPackageManagerName(packageManager: PackageManager): string {
62
+ return packageManager.type ?? "unknown";
65
63
  }
66
64
 
67
65
  /**
68
66
  * Manage packages using npm
69
67
  */
70
68
  const NpmPackageManager: PackageManager = {
69
+ cwd: process.cwd(),
70
+ type: "npm",
71
71
  /** Add and install a new devDependency into the local package.json. */
72
72
  async addDevDeps(...packages: string[]): Promise<void> {
73
73
  await execa("npm", ["install", ...packages, "--save-dev"], {
74
74
  stdio: "inherit",
75
+ cwd: this.cwd,
75
76
  });
76
77
  },
77
78
 
@@ -79,6 +80,7 @@ const NpmPackageManager: PackageManager = {
79
80
  async install(): Promise<void> {
80
81
  await execa("npm", ["install"], {
81
82
  stdio: "inherit",
83
+ cwd: this.cwd,
82
84
  });
83
85
  },
84
86
  };
@@ -87,10 +89,13 @@ const NpmPackageManager: PackageManager = {
87
89
  * Manage packages using yarn
88
90
  */
89
91
  const YarnPackageManager: PackageManager = {
92
+ cwd: process.cwd(),
93
+ type: "yarn",
90
94
  /** Add and install a new devDependency into the local package.json. */
91
95
  async addDevDeps(...packages: string[]): Promise<void> {
92
96
  await execa("yarn", ["add", ...packages, "--dev"], {
93
97
  stdio: "inherit",
98
+ cwd: this.cwd,
94
99
  });
95
100
  },
96
101
 
@@ -98,6 +103,7 @@ const YarnPackageManager: PackageManager = {
98
103
  async install(): Promise<void> {
99
104
  await execa("yarn", ["install"], {
100
105
  stdio: "inherit",
106
+ cwd: this.cwd,
101
107
  });
102
108
  },
103
109
  };
package/src/pages.tsx CHANGED
@@ -7,10 +7,10 @@ import { join } from "node:path";
7
7
  import { URL } from "node:url";
8
8
  import { watch } from "chokidar";
9
9
  import { getType } from "mime";
10
- import open from "open";
11
10
  import { buildWorker } from "../pages/functions/buildWorker";
12
11
  import { generateConfigFromFileTree } from "../pages/functions/filepath-routing";
13
12
  import { writeRoutesModule } from "../pages/functions/routes";
13
+ import openInBrowser from "./open-in-browser";
14
14
  import { toUrlPath } from "./paths";
15
15
  import type { Config } from "../pages/functions/routes";
16
16
  import type { Headers, Request, fetch } from "@miniflare/core";
@@ -771,7 +771,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
771
771
  kv: kvs = [],
772
772
  do: durableObjects = [],
773
773
  "live-reload": liveReload,
774
- "--": remaining = [],
774
+ _: [_pages, _dev, ...remaining],
775
775
  }) => {
776
776
  if (!local) {
777
777
  console.error("Only local mode is supported at the moment.");
@@ -795,8 +795,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
795
795
 
796
796
  let miniflareArgs: MiniflareOptions = {};
797
797
 
798
- let scriptReadyResolve;
799
- const scriptReadyPromise = new Promise(
798
+ let scriptReadyResolve: () => void;
799
+ const scriptReadyPromise = new Promise<void>(
800
800
  (resolve) => (scriptReadyResolve = resolve)
801
801
  );
802
802
 
@@ -830,7 +830,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
830
830
  scriptPath,
831
831
  };
832
832
  } else {
833
- scriptReadyResolve();
833
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
834
+ scriptReadyResolve!();
834
835
 
835
836
  const scriptPath =
836
837
  directory !== undefined
@@ -900,7 +901,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
900
901
 
901
902
  // env.ASSETS.fetch
902
903
  serviceBindings: {
903
- async ASSETS(request) {
904
+ async ASSETS(request: Request) {
904
905
  if (proxyPort) {
905
906
  try {
906
907
  const url = new URL(request.url);
@@ -945,9 +946,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
945
946
  console.log(`Serving at http://localhost:${port}/`);
946
947
 
947
948
  if (process.env.BROWSER !== "none") {
948
- const childProcess = await open(`http://localhost:${port}/`);
949
- // fail silently if the open command doesn't work (e.g. in GitHub Codespaces)
950
- childProcess.on("error", (_err) => {});
949
+ await openInBrowser(`http://localhost:${port}/`);
951
950
  }
952
951
 
953
952
  if (directory !== undefined && liveReload) {
@@ -964,7 +963,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
964
963
  miniflare.dispose().catch((err) => miniflare.log.error(err));
965
964
  });
966
965
  } catch (e) {
967
- miniflare.log.error(e);
966
+ miniflare.log.error(e as Error);
968
967
  EXIT("Could not start Miniflare.", 1);
969
968
  }
970
969
  }