wrangler 0.0.0-e1c615f → 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 (85) hide show
  1. package/bin/wrangler.js +2 -2
  2. package/package.json +28 -13
  3. package/pages/functions/buildWorker.ts +6 -1
  4. package/pages/functions/filepath-routing.test.ts +112 -28
  5. package/pages/functions/filepath-routing.ts +88 -131
  6. package/pages/functions/routes.ts +11 -18
  7. package/pages/functions/template-worker.ts +21 -28
  8. package/src/__tests__/configuration.test.ts +2185 -0
  9. package/src/__tests__/dev.test.tsx +56 -9
  10. package/src/__tests__/guess-worker-format.test.ts +66 -0
  11. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  12. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  13. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  14. package/src/__tests__/helpers/mock-account-id.ts +30 -0
  15. package/src/__tests__/helpers/mock-bin.ts +36 -0
  16. package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +53 -17
  17. package/src/__tests__/helpers/mock-console.ts +62 -0
  18. package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
  19. package/src/__tests__/helpers/mock-kv.ts +40 -0
  20. package/src/__tests__/helpers/mock-user.ts +27 -0
  21. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  22. package/src/__tests__/helpers/run-in-tmp.ts +29 -0
  23. package/src/__tests__/helpers/run-wrangler.ts +16 -0
  24. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  25. package/src/__tests__/index.test.ts +655 -76
  26. package/src/__tests__/jest.setup.ts +37 -2
  27. package/src/__tests__/kv.test.ts +441 -398
  28. package/src/__tests__/logout.test.ts +50 -0
  29. package/src/__tests__/package-manager.test.ts +206 -0
  30. package/src/__tests__/parse.test.ts +322 -0
  31. package/src/__tests__/publish.test.ts +2406 -0
  32. package/src/__tests__/r2.test.ts +198 -0
  33. package/src/__tests__/secret.test.ts +326 -0
  34. package/src/__tests__/sentry.test.ts +146 -0
  35. package/src/__tests__/tail.test.ts +564 -0
  36. package/src/__tests__/whoami.test.tsx +86 -0
  37. package/src/api/form_data.ts +112 -26
  38. package/src/api/preview.ts +34 -15
  39. package/src/api/worker.ts +58 -27
  40. package/src/bundle.ts +133 -0
  41. package/src/cfetch/index.ts +21 -19
  42. package/src/cfetch/internal.ts +85 -21
  43. package/src/cli.ts +10 -0
  44. package/src/config/README.md +107 -0
  45. package/src/config/config.ts +265 -0
  46. package/src/config/diagnostics.ts +80 -0
  47. package/src/config/environment.ts +305 -0
  48. package/src/config/index.ts +52 -0
  49. package/src/config/validation-helpers.ts +370 -0
  50. package/src/config/validation.ts +1017 -0
  51. package/src/dev.tsx +455 -368
  52. package/src/dialogs.tsx +2 -2
  53. package/src/entry.ts +57 -0
  54. package/src/environment-variables.ts +33 -0
  55. package/src/guess-worker-format.ts +68 -0
  56. package/src/index.tsx +1044 -646
  57. package/src/inspect.ts +609 -0
  58. package/src/kv.tsx +47 -39
  59. package/src/module-collection.ts +142 -36
  60. package/src/open-in-browser.ts +13 -0
  61. package/src/package-manager.ts +126 -0
  62. package/src/pages.tsx +139 -62
  63. package/src/parse.ts +183 -0
  64. package/src/paths.ts +26 -0
  65. package/src/proxy.ts +350 -77
  66. package/src/publish.ts +307 -289
  67. package/src/r2.ts +50 -0
  68. package/src/reporting.ts +115 -0
  69. package/src/sites.tsx +183 -56
  70. package/src/tail/filters.ts +279 -0
  71. package/src/tail/index.ts +299 -0
  72. package/src/tail/printing.ts +62 -0
  73. package/src/user.tsx +137 -110
  74. package/src/whoami.tsx +69 -0
  75. package/templates/new-worker.js +15 -0
  76. package/templates/new-worker.ts +15 -0
  77. package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
  78. package/templates/tsconfig.json +105 -0
  79. package/wrangler-dist/cli.js +127861 -111486
  80. package/wrangler-dist/cli.js.map +3 -3
  81. package/src/__tests__/run-in-tmp.ts +0 -19
  82. package/src/__tests__/run-wrangler.ts +0 -32
  83. package/src/api/inspect.ts +0 -430
  84. package/src/config.ts +0 -131
  85. package/src/tail.tsx +0 -73
package/src/kv.tsx CHANGED
@@ -1,13 +1,12 @@
1
1
  import { URLSearchParams } from "node:url";
2
- import type { Config } from "./config";
3
- import { fetchListResult, fetchResult } from "./cfetch";
2
+ import { fetchListResult, fetchResult, fetchKVGetValue } from "./cfetch";
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
  /**
@@ -82,7 +81,7 @@ export interface NamespaceKeyInfo {
82
81
  export async function listNamespaceKeys(
83
82
  accountId: string,
84
83
  namespaceId: string,
85
- prefix?: string
84
+ prefix = ""
86
85
  ) {
87
86
  return await fetchListResult<NamespaceKeyInfo>(
88
87
  `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/keys`,
@@ -98,18 +97,31 @@ export async function putKeyValue(
98
97
  value: string,
99
98
  args?: { expiration?: number; expiration_ttl?: number }
100
99
  ) {
100
+ let searchParams: URLSearchParams | undefined;
101
+ if (args) {
102
+ searchParams = new URLSearchParams();
103
+ if (args.expiration) {
104
+ searchParams.set("expiration", `${args.expiration}`);
105
+ }
106
+ if (args.expiration_ttl) {
107
+ searchParams.set("expiration_ttl", `${args.expiration_ttl}`);
108
+ }
109
+ }
101
110
  return await fetchResult(
102
111
  `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`,
103
112
  { method: "PUT", body: value },
104
- args
105
- ? new URLSearchParams({
106
- expiration: args.expiration?.toString(),
107
- expiration_ttl: args.expiration_ttl?.toString(),
108
- })
109
- : undefined
113
+ searchParams
110
114
  );
111
115
  }
112
116
 
117
+ export async function getKeyValue(
118
+ accountId: string,
119
+ namespaceId: string,
120
+ key: string
121
+ ): Promise<string> {
122
+ return await fetchKVGetValue(accountId, namespaceId, key);
123
+ }
124
+
113
125
  export async function putBulkKeyValue(
114
126
  accountId: string,
115
127
  namespaceId: string,
@@ -140,13 +152,10 @@ export async function deleteBulkKeyValue(
140
152
  );
141
153
  }
142
154
 
143
- export function getNamespaceId({
144
- preview,
145
- binding,
146
- config,
147
- "namespace-id": namespaceId,
148
- env,
149
- }: KvArgs): string {
155
+ export function getNamespaceId(
156
+ { preview, binding, "namespace-id": namespaceId, env }: KvArgs,
157
+ configOrEnv: Config | Environment | undefined
158
+ ): string {
150
159
  // nice
151
160
  if (namespaceId) {
152
161
  return namespaceId;
@@ -154,13 +163,13 @@ export function getNamespaceId({
154
163
 
155
164
  // begin pre-flight checks
156
165
 
157
- // `--binding` is only valid if there's a wrangler configuration file.
158
- if (binding && !config) {
166
+ // `--binding` is only valid if there's a wrangler configuration.
167
+ if (binding && !configOrEnv) {
159
168
  throw new Error("--binding specified, but no config file was found.");
160
169
  }
161
170
 
162
171
  // there's no config. abort here
163
- if (!config) {
172
+ if (!configOrEnv) {
164
173
  throw new Error(
165
174
  "Failed to find a config file.\n" +
166
175
  "Either use --namespace-id to upload directly or create a configuration file with a binding."
@@ -169,36 +178,31 @@ export function getNamespaceId({
169
178
 
170
179
  // they want to use an environment, actually
171
180
  if (env) {
172
- if (!config.env || !config.env[env]) {
181
+ if (!("env" in configOrEnv) || !configOrEnv.env[env]) {
173
182
  throw new Error(
174
183
  `Failed to find environment "${env}" in configuration file!`
175
184
  );
176
185
  }
177
-
178
- // TODO: either a bespoke arg type for this function to avoid `undefined`s or an `EnvOrConfig` type
179
- return getNamespaceId({
180
- binding,
181
- "namespace-id": namespaceId,
182
- env: undefined,
183
- preview,
184
- config: {
185
- env: undefined,
186
- build: undefined,
187
- name: undefined,
188
- account_id: undefined,
189
- ...config.env[env],
186
+ return getNamespaceId(
187
+ {
188
+ binding,
189
+ "namespace-id": namespaceId,
190
+ preview,
190
191
  },
191
- });
192
+ configOrEnv.env[env]
193
+ );
192
194
  }
193
195
 
194
196
  // there's no KV namespaces
195
- if (!config.kv_namespaces || config.kv_namespaces.length === 0) {
197
+ if (!configOrEnv.kv_namespaces || configOrEnv.kv_namespaces.length === 0) {
196
198
  throw new Error(
197
199
  "No KV Namespaces configured! Either use --namespace-id to upload directly or add a KV namespace to your wrangler config file."
198
200
  );
199
201
  }
200
202
 
201
- const namespace = config.kv_namespaces.find((ns) => ns.binding === binding);
203
+ const namespace = configOrEnv.kv_namespaces.find(
204
+ (ns) => ns.binding === binding
205
+ );
202
206
 
203
207
  // we couldn't find a namespace with that binding
204
208
  if (!namespace) {
@@ -262,6 +266,10 @@ export function getNamespaceId({
262
266
  /**
263
267
  * KV namespace binding names must be valid JS identifiers.
264
268
  */
265
- export function isValidNamespaceBinding(binding: string): boolean {
266
- return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(binding);
269
+ export function isValidNamespaceBinding(
270
+ binding: string | undefined
271
+ ): binding is string {
272
+ return (
273
+ typeof binding === "string" && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(binding)
274
+ );
267
275
  }
@@ -1,21 +1,86 @@
1
- import type { CfModule } from "./api/worker";
2
- import type esbuild from "esbuild";
3
- import path from "node:path";
4
- import { readFile } from "node:fs/promises";
5
1
  import crypto from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import globToRegExp from "glob-to-regexp";
5
+ import type { CfModule, CfModuleType, CfScriptFormat } from "./api/worker";
6
+ import type { Config, ConfigModuleRuleType } from "./config";
7
+ import type esbuild from "esbuild";
8
+
9
+ const RuleTypeToModuleType: Record<ConfigModuleRuleType, CfModuleType> = {
10
+ ESModule: "esm",
11
+ CommonJS: "commonjs",
12
+ CompiledWasm: "compiled-wasm",
13
+ Data: "buffer",
14
+ Text: "text",
15
+ };
6
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,41 +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: /.*\.(pem|txt|html|wasm)$/ },
34
- async (args: esbuild.OnResolveArgs) => {
35
- // take the file and massage it to a
36
- // transportable/manageable format
37
- const fileExt = path.extname(args.path);
38
- const filePath = path.join(args.resolveDir, args.path);
39
- const fileContent = await readFile(filePath);
40
- const fileHash = crypto
41
- .createHash("sha1")
42
- .update(fileContent)
43
- .digest("hex");
44
- const fileName = `${fileHash}-${path.basename(args.path)}`;
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)}`;
45
112
 
46
- // add the module to the array
47
- modules.push({
48
- name: fileName,
49
- content: fileContent,
50
- type: fileExt === ".wasm" ? "compiled-wasm" : "text",
51
- });
113
+ // add the module to the array
114
+ modules.push({
115
+ name: fileName,
116
+ content: fileContent,
117
+ type: RuleTypeToModuleType[rule.type],
118
+ });
52
119
 
53
- return {
54
- path: fileName, // change the reference to the changed module
55
- external: true, // mark it as external in the bundle
56
- namespace: "wrangler-module-collector-ns", // just a tag, this isn't strictly necessary
57
- watchFiles: [filePath], // we also add the file to esbuild's watch list
58
- };
59
- }
60
- );
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
+ });
61
167
  },
62
168
  },
63
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
+ }
@@ -0,0 +1,126 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { execa, execaCommandSync } from "execa";
4
+
5
+ export interface PackageManager {
6
+ cwd: string;
7
+ type: "npm" | "yarn";
8
+ addDevDeps(...packages: string[]): Promise<void>;
9
+ install(): Promise<void>;
10
+ }
11
+
12
+ export async function getPackageManager(cwd: string): Promise<PackageManager> {
13
+ const [hasYarn, hasNpm] = await Promise.all([supportsYarn(), supportsNpm()]);
14
+ const hasYarnLock = existsSync(join(cwd, "yarn.lock"));
15
+ const hasNpmLock = existsSync(join(cwd, "package-lock.json"));
16
+
17
+ if (hasNpmLock) {
18
+ if (hasNpm) {
19
+ console.log(
20
+ "Using npm as package manager, as there is already a package-lock.json file."
21
+ );
22
+ return { ...NpmPackageManager, cwd };
23
+ } else if (hasYarn) {
24
+ console.log("Using yarn as package manager.");
25
+ console.warn(
26
+ "There is already a package-lock.json file but could not find npm on the PATH."
27
+ );
28
+ return { ...YarnPackageManager, cwd };
29
+ }
30
+ } else if (hasYarnLock) {
31
+ if (hasYarn) {
32
+ console.log(
33
+ "Using yarn as package manager, as there is already a yarn.lock file."
34
+ );
35
+ return { ...YarnPackageManager, cwd };
36
+ } else if (hasNpm) {
37
+ console.log("Using npm as package manager.");
38
+ console.warn(
39
+ "There is already a yarn.lock file but could not find yarn on the PATH."
40
+ );
41
+ return { ...NpmPackageManager, cwd };
42
+ }
43
+ }
44
+
45
+ if (hasNpm) {
46
+ console.log("Using npm as package manager.");
47
+ return { ...NpmPackageManager, cwd };
48
+ } else if (hasYarn) {
49
+ console.log("Using yarn as package manager.");
50
+ return { ...YarnPackageManager, cwd };
51
+ } else {
52
+ throw new Error(
53
+ "Unable to find a package manager. Supported managers are: npm and yarn."
54
+ );
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Get the name of the given `packageManager`.
60
+ */
61
+ export function getPackageManagerName(packageManager: PackageManager): string {
62
+ return packageManager.type ?? "unknown";
63
+ }
64
+
65
+ /**
66
+ * Manage packages using npm
67
+ */
68
+ const NpmPackageManager: PackageManager = {
69
+ cwd: process.cwd(),
70
+ type: "npm",
71
+ /** Add and install a new devDependency into the local package.json. */
72
+ async addDevDeps(...packages: string[]): Promise<void> {
73
+ await execa("npm", ["install", ...packages, "--save-dev"], {
74
+ stdio: "inherit",
75
+ cwd: this.cwd,
76
+ });
77
+ },
78
+
79
+ /** Install all the dependencies in the local package.json. */
80
+ async install(): Promise<void> {
81
+ await execa("npm", ["install"], {
82
+ stdio: "inherit",
83
+ cwd: this.cwd,
84
+ });
85
+ },
86
+ };
87
+
88
+ /**
89
+ * Manage packages using yarn
90
+ */
91
+ const YarnPackageManager: PackageManager = {
92
+ cwd: process.cwd(),
93
+ type: "yarn",
94
+ /** Add and install a new devDependency into the local package.json. */
95
+ async addDevDeps(...packages: string[]): Promise<void> {
96
+ await execa("yarn", ["add", ...packages, "--dev"], {
97
+ stdio: "inherit",
98
+ cwd: this.cwd,
99
+ });
100
+ },
101
+
102
+ /** Install all the dependencies in the local package.json. */
103
+ async install(): Promise<void> {
104
+ await execa("yarn", ["install"], {
105
+ stdio: "inherit",
106
+ cwd: this.cwd,
107
+ });
108
+ },
109
+ };
110
+
111
+ async function supports(name: string): Promise<boolean> {
112
+ try {
113
+ execaCommandSync(`${name} --version`, { stdio: "ignore" });
114
+ return true;
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+
120
+ function supportsYarn(): Promise<boolean> {
121
+ return supports("yarn");
122
+ }
123
+
124
+ function supportsNpm(): Promise<boolean> {
125
+ return supports("npm");
126
+ }