wrangler 0.0.0-e3cab74 → 0.0.0-e452a35

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 (102) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +27 -14
  3. package/bin/wrangler.js +49 -5
  4. package/miniflare-dist/index.mjs +4886 -0
  5. package/package.json +42 -28
  6. package/pages/functions/buildPlugin.ts +105 -0
  7. package/pages/functions/buildWorker.ts +93 -4
  8. package/pages/functions/filepath-routing.test.ts +136 -25
  9. package/pages/functions/filepath-routing.ts +14 -3
  10. package/pages/functions/routes.ts +6 -1
  11. package/pages/functions/template-plugin.ts +147 -0
  12. package/pages/functions/template-worker.ts +21 -8
  13. package/src/__tests__/config-cache.test.ts +36 -0
  14. package/src/__tests__/configuration.test.ts +1514 -367
  15. package/src/__tests__/dev.test.tsx +870 -84
  16. package/src/__tests__/guess-worker-format.test.ts +37 -18
  17. package/src/__tests__/helpers/mock-account-id.ts +22 -4
  18. package/src/__tests__/helpers/mock-cfetch.ts +8 -4
  19. package/src/__tests__/helpers/mock-console.ts +35 -10
  20. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  21. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  22. package/src/__tests__/helpers/mock-istty.ts +27 -0
  23. package/src/__tests__/helpers/mock-kv.ts +9 -16
  24. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  25. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  26. package/src/__tests__/helpers/run-in-tmp.ts +10 -0
  27. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  28. package/src/__tests__/https-options.test.ts +130 -0
  29. package/src/__tests__/index.test.ts +137 -747
  30. package/src/__tests__/init.test.ts +1557 -0
  31. package/src/__tests__/jest.setup.ts +28 -8
  32. package/src/__tests__/kv.test.ts +598 -258
  33. package/src/__tests__/logger.test.ts +115 -0
  34. package/src/__tests__/pages.test.ts +657 -0
  35. package/src/__tests__/parse.test.ts +8 -0
  36. package/src/__tests__/publish.test.ts +4003 -941
  37. package/src/__tests__/r2.test.ts +69 -34
  38. package/src/__tests__/route.test.ts +45 -0
  39. package/src/__tests__/secret.test.ts +228 -73
  40. package/src/__tests__/tail.test.ts +146 -20
  41. package/src/__tests__/user.test.ts +131 -0
  42. package/src/__tests__/whoami.test.tsx +26 -7
  43. package/src/abort.d.ts +3 -0
  44. package/src/bundle.ts +74 -7
  45. package/src/cfetch/index.ts +21 -4
  46. package/src/cfetch/internal.ts +20 -14
  47. package/src/cli.ts +5 -11
  48. package/src/config/README.md +7 -7
  49. package/src/config/config.ts +69 -100
  50. package/src/config/environment.ts +119 -4
  51. package/src/config/index.ts +177 -4
  52. package/src/config/validation-helpers.ts +228 -23
  53. package/src/config/validation.ts +687 -174
  54. package/src/config-cache.ts +45 -0
  55. package/src/{api/preview.ts → create-worker-preview.ts} +74 -36
  56. package/src/{api/form_data.ts → create-worker-upload-form.ts} +55 -18
  57. package/src/dev/dev-vars.ts +36 -0
  58. package/src/dev/dev.tsx +422 -0
  59. package/src/dev/local.tsx +318 -0
  60. package/src/dev/remote.tsx +227 -0
  61. package/src/dev/use-esbuild.ts +118 -0
  62. package/src/dialogs.tsx +16 -6
  63. package/src/durable.ts +102 -0
  64. package/src/entry.ts +279 -14
  65. package/src/environment-variables.ts +3 -1
  66. package/src/errors.ts +11 -0
  67. package/src/https-options.ts +123 -0
  68. package/src/index.tsx +1268 -806
  69. package/src/inspect.ts +64 -11
  70. package/src/intl-polyfill.d.ts +139 -0
  71. package/src/{kv.tsx → kv.ts} +195 -62
  72. package/src/logger.ts +66 -0
  73. package/src/miniflare-cli/README.md +30 -0
  74. package/src/miniflare-cli/enum-keys.ts +17 -0
  75. package/src/miniflare-cli/index.ts +41 -0
  76. package/src/miniflare-cli/request-context.ts +40 -0
  77. package/src/module-collection.ts +94 -6
  78. package/src/open-in-browser.ts +9 -3
  79. package/src/package-manager.ts +9 -8
  80. package/src/pages.tsx +1057 -75
  81. package/src/parse.ts +24 -5
  82. package/src/proxy.ts +143 -54
  83. package/src/publish.ts +444 -229
  84. package/src/selfsigned.d.ts +29 -0
  85. package/src/sites.tsx +98 -44
  86. package/src/tail/index.ts +2 -2
  87. package/src/tail/printing.ts +13 -8
  88. package/src/update-check.ts +19 -0
  89. package/src/user.tsx +263 -109
  90. package/src/whoami.tsx +2 -1
  91. package/src/{api/worker.ts → worker.ts} +24 -30
  92. package/templates/gitignore +171 -0
  93. package/templates/new-worker.ts +16 -1
  94. package/templates/static-asset-facade.js +1 -1
  95. package/wrangler-dist/cli.js +55345 -69246
  96. package/src/__tests__/helpers/mock-user.ts +0 -27
  97. package/src/__tests__/logout.test.ts +0 -50
  98. package/src/__tests__/sentry.test.ts +0 -146
  99. package/src/dev.tsx +0 -961
  100. package/src/guess-worker-format.ts +0 -68
  101. package/src/reporting.ts +0 -115
  102. package/wrangler-dist/cli.js.map +0 -7
@@ -0,0 +1,45 @@
1
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
+ import { dirname, join } from "path";
3
+
4
+ let cacheMessageShown = false;
5
+
6
+ const cacheFolder = "node_modules/.cache/wrangler";
7
+
8
+ const showCacheMessage = () => {
9
+ if (!cacheMessageShown) {
10
+ console.log(
11
+ `Using cached values in '${cacheFolder}'. This is used as a temporary store to improve the developer experience for some commands. It may be purged at any time. It doesn't contain any sensitive information, but it should not be commited into source control.`
12
+ );
13
+ cacheMessageShown = true;
14
+ }
15
+ };
16
+
17
+ export const getConfigCache = <T>(fileName: string): Partial<T> => {
18
+ try {
19
+ const configCacheLocation = join(cacheFolder, fileName);
20
+ const configCache = JSON.parse(readFileSync(configCacheLocation, "utf-8"));
21
+ showCacheMessage();
22
+ return configCache;
23
+ } catch {
24
+ return {};
25
+ }
26
+ };
27
+
28
+ export const saveToConfigCache = <T>(
29
+ fileName: string,
30
+ newValues: Partial<T>
31
+ ) => {
32
+ const configCacheLocation = join(cacheFolder, fileName);
33
+ const existingValues = getConfigCache(fileName);
34
+
35
+ mkdirSync(dirname(configCacheLocation), { recursive: true });
36
+ writeFileSync(
37
+ configCacheLocation,
38
+ JSON.stringify({ ...existingValues, ...newValues }, null, 2)
39
+ );
40
+ showCacheMessage();
41
+ };
42
+
43
+ export const purgeConfigCaches = () => {
44
+ rmSync(cacheFolder, { recursive: true, force: true });
45
+ };
@@ -1,7 +1,8 @@
1
1
  import { URL } from "node:url";
2
2
  import { fetch } from "undici";
3
- import { fetchResult } from "../cfetch";
4
- import { toFormData } from "./form_data";
3
+ import { fetchResult } from "./cfetch";
4
+ import { createWorkerUploadForm } from "./create-worker-upload-form";
5
+ import { logger } from "./logger";
5
6
  import type { CfAccount, CfWorkerContext, CfWorkerInit } from "./worker";
6
7
 
7
8
  /**
@@ -57,17 +58,23 @@ export interface CfPreviewToken {
57
58
  */
58
59
  async function sessionToken(
59
60
  account: CfAccount,
60
- ctx: CfWorkerContext
61
+ ctx: CfWorkerContext,
62
+ abortSignal: AbortSignal
61
63
  ): Promise<CfPreviewToken> {
62
64
  const { accountId } = account;
63
65
  const initUrl = ctx.zone
64
- ? `/zones/${ctx.zone}/workers/edge-preview`
66
+ ? `/zones/${ctx.zone.id}/workers/edge-preview`
65
67
  : `/accounts/${accountId}/workers/subdomain/edge-preview`;
66
68
 
67
- const { exchange_url } = await fetchResult<{ exchange_url: string }>(initUrl);
68
- const { inspector_websocket, token } = (await (
69
- await fetch(exchange_url)
70
- ).json()) as { inspector_websocket: string; token: string };
69
+ const { exchange_url } = await fetchResult<{ exchange_url: string }>(
70
+ initUrl,
71
+ undefined,
72
+ undefined,
73
+ abortSignal
74
+ );
75
+ const { inspector_websocket, prewarm, token } = (await (
76
+ await fetch(exchange_url, { signal: abortSignal })
77
+ ).json()) as { inspector_websocket: string; token: string; prewarm: string };
71
78
  const { host } = new URL(inspector_websocket);
72
79
  const query = `cf_workers_preview_token=${token}`;
73
80
 
@@ -75,9 +82,7 @@ async function sessionToken(
75
82
  value: token,
76
83
  host,
77
84
  inspectorUrl: new URL(`${inspector_websocket}?${query}`),
78
- prewarmUrl: new URL(
79
- `https://${host}/cdn-cgi/workers/preview/prewarm?${query}`
80
- ),
85
+ prewarmUrl: new URL(prewarm),
81
86
  };
82
87
  }
83
88
 
@@ -93,14 +98,16 @@ function randomId(): string {
93
98
  /**
94
99
  * Creates a preview token.
95
100
  */
96
- export async function previewToken(
101
+ async function createPreviewToken(
97
102
  account: CfAccount,
98
103
  worker: CfWorkerInit,
99
- ctx: CfWorkerContext
104
+ ctx: CfWorkerContext,
105
+ abortSignal: AbortSignal
100
106
  ): Promise<CfPreviewToken> {
101
107
  const { value, host, inspectorUrl, prewarmUrl } = await sessionToken(
102
108
  account,
103
- ctx
109
+ ctx,
110
+ abortSignal
104
111
  );
105
112
 
106
113
  const { accountId } = account;
@@ -111,37 +118,68 @@ export async function previewToken(
111
118
  : `/accounts/${accountId}/workers/scripts/${scriptId}/edge-preview`;
112
119
 
113
120
  const mode: CfPreviewMode = ctx.zone
114
- ? { routes: ["*/*"] }
121
+ ? { routes: ["*/*"] } // TODO: should we support routes here? how?
115
122
  : { workers_dev: true };
116
123
 
117
- const formData = toFormData(worker);
124
+ const formData = createWorkerUploadForm(worker);
118
125
  formData.set("wrangler-session-config", JSON.stringify(mode));
119
126
 
120
- const { preview_token } = await fetchResult<{ preview_token: string }>(url, {
121
- method: "POST",
122
- body: formData,
123
- headers: {
124
- "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
+ },
125
135
  },
126
- });
136
+ undefined,
137
+ abortSignal
138
+ );
127
139
 
128
140
  return {
129
141
  value: preview_token,
130
- // TODO: verify this works with zoned workers
131
- host:
132
- worker.name && !ctx.zone
133
- ? // hrmm this might need change as well
134
- `${
135
- worker.name
136
- // TODO: this should also probably have the env prefix
137
- // but it doesn't appear to work yet, instead giving us the
138
- // "There is nothing here yet" screen
139
- // ctx.env && !ctx.legacyEnv
140
- // ? `${ctx.env}.${worker.name}`
141
- // : worker.name
142
- }.${host.split(".").slice(1).join(".")}`
143
- : host,
142
+ host: ctx.zone
143
+ ? ctx.zone.host
144
+ : worker.name
145
+ ? `${
146
+ worker.name
147
+ // TODO: this should also probably have the env prefix
148
+ // but it doesn't appear to work yet, instead giving us the
149
+ // "There is nothing here yet" screen
150
+ // ctx.env && !ctx.legacyEnv
151
+ // ? `${ctx.env}.${worker.name}`
152
+ // : worker.name
153
+ }.${host.split(".").slice(1).join(".")}`
154
+ : host,
155
+
144
156
  inspectorUrl,
145
157
  prewarmUrl,
146
158
  };
147
159
  }
160
+
161
+ /**
162
+ * A stub to create a Cloudflare Worker preview.
163
+ *
164
+ * @example
165
+ * const {value, host} = await createWorker(init, acct);
166
+ */
167
+ export async function createWorkerPreview(
168
+ init: CfWorkerInit,
169
+ account: CfAccount,
170
+ ctx: CfWorkerContext,
171
+ abortSignal: AbortSignal
172
+ ): Promise<CfPreviewToken> {
173
+ const token = await createPreviewToken(account, init, ctx, abortSignal);
174
+ const response = await fetch(token.prewarmUrl.href, {
175
+ method: "POST",
176
+ signal: abortSignal,
177
+ headers: {
178
+ "cf-workers-preview-token": token.value,
179
+ },
180
+ });
181
+ if (!response.ok) {
182
+ logger.warn("worker failed to prewarm: ", response.statusText);
183
+ }
184
+ return token;
185
+ }
@@ -26,32 +26,37 @@ export function toMimeType(type: CfModuleType): string {
26
26
  export interface WorkerMetadata {
27
27
  /** The name of the entry point module. Only exists when the worker is in the ES module format */
28
28
  main_module?: string;
29
- /** The name of the entry point module. Only exists when the worker is in the Service Worker format */
29
+ /** The name of the entry point module. Only exists when the worker is in the service-worker format */
30
30
  body_part?: string;
31
31
  compatibility_date?: string;
32
32
  compatibility_flags?: string[];
33
33
  usage_model?: "bundled" | "unbound";
34
34
  migrations?: CfDurableObjectMigrations;
35
+ // If you add any new binding types here, also add it to safeBindings
36
+ // under validateUnsafeBinding in config/validation.ts
35
37
  bindings: (
36
- | { type: "kv_namespace"; name: string; namespace_id: string }
37
38
  | { type: "plain_text"; name: string; text: string }
38
39
  | { type: "json"; name: string; json: unknown }
39
40
  | { type: "wasm_module"; name: string; part: string }
40
41
  | { type: "text_blob"; name: string; part: string }
42
+ | { type: "data_blob"; name: string; part: string }
43
+ | { type: "kv_namespace"; name: string; namespace_id: string }
41
44
  | {
42
45
  type: "durable_object_namespace";
43
46
  name: string;
44
47
  class_name: string;
45
48
  script_name?: string;
49
+ environment?: string;
46
50
  }
47
51
  | { type: "r2_bucket"; name: string; bucket_name: string }
52
+ | { type: "service"; name: string; service: string; environment?: string }
48
53
  )[];
49
54
  }
50
55
 
51
56
  /**
52
57
  * Creates a `FormData` upload from a `CfWorkerInit`.
53
58
  */
54
- export function toFormData(worker: CfWorkerInit): FormData {
59
+ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
55
60
  const formData = new FormData();
56
61
  const {
57
62
  main,
@@ -66,6 +71,14 @@ export function toFormData(worker: CfWorkerInit): FormData {
66
71
 
67
72
  const metadataBindings: WorkerMetadata["bindings"] = [];
68
73
 
74
+ Object.entries(bindings.vars || {})?.forEach(([key, value]) => {
75
+ if (typeof value === "string") {
76
+ metadataBindings.push({ name: key, type: "plain_text", text: value });
77
+ } else {
78
+ metadataBindings.push({ name: key, type: "json", json: value });
79
+ }
80
+ });
81
+
69
82
  bindings.kv_namespaces?.forEach(({ id, binding }) => {
70
83
  metadataBindings.push({
71
84
  name: binding,
@@ -75,12 +88,13 @@ export function toFormData(worker: CfWorkerInit): FormData {
75
88
  });
76
89
 
77
90
  bindings.durable_objects?.bindings.forEach(
78
- ({ name, class_name, script_name }) => {
91
+ ({ name, class_name, script_name, environment }) => {
79
92
  metadataBindings.push({
80
93
  name,
81
94
  type: "durable_object_namespace",
82
95
  class_name: class_name,
83
96
  ...(script_name && { script_name }),
97
+ ...(environment && { environment }),
84
98
  });
85
99
  }
86
100
  );
@@ -93,12 +107,13 @@ export function toFormData(worker: CfWorkerInit): FormData {
93
107
  });
94
108
  });
95
109
 
96
- Object.entries(bindings.vars || {})?.forEach(([key, value]) => {
97
- if (typeof value === "string") {
98
- metadataBindings.push({ name: key, type: "plain_text", text: value });
99
- } else {
100
- metadataBindings.push({ name: key, type: "json", json: value });
101
- }
110
+ bindings.services?.forEach(({ binding, service, environment }) => {
111
+ metadataBindings.push({
112
+ name: binding,
113
+ type: "service",
114
+ service,
115
+ ...(environment && { environment }),
116
+ });
102
117
  });
103
118
 
104
119
  for (const [name, filePath] of Object.entries(bindings.wasm_modules || {})) {
@@ -133,6 +148,21 @@ export function toFormData(worker: CfWorkerInit): FormData {
133
148
  }
134
149
  }
135
150
 
151
+ for (const [name, filePath] of Object.entries(bindings.data_blobs || {})) {
152
+ metadataBindings.push({
153
+ name,
154
+ type: "data_blob",
155
+ part: name,
156
+ });
157
+
158
+ formData.set(
159
+ name,
160
+ new File([readFileSync(filePath)], filePath, {
161
+ type: "application/octet-stream",
162
+ })
163
+ );
164
+ }
165
+
136
166
  if (main.type === "commonjs") {
137
167
  // This is a service-worker format worker.
138
168
  for (const module of Object.values([...(modules || [])])) {
@@ -146,8 +176,12 @@ export function toFormData(worker: CfWorkerInit): FormData {
146
176
  );
147
177
  // And then remove it from the modules collection
148
178
  modules = modules?.filter((m) => m !== module);
149
- } else if (module.type === "compiled-wasm" || module.type === "text") {
150
- // Convert all wasm/text modules into `wasm_module`/`text_blob` bindings.
179
+ } else if (
180
+ module.type === "compiled-wasm" ||
181
+ module.type === "text" ||
182
+ module.type === "buffer"
183
+ ) {
184
+ // Convert all wasm/text/data modules into `wasm_module`/`text_blob`/`data_blob` bindings.
151
185
  // The "name" of the module is a file path. We use it
152
186
  // to instead be a "part" of the body, and a reference
153
187
  // that we can use inside our source. This identifier has to be a valid
@@ -156,7 +190,12 @@ export function toFormData(worker: CfWorkerInit): FormData {
156
190
  const name = module.name.replace(/[^a-zA-Z0-9_$]/g, "_");
157
191
  metadataBindings.push({
158
192
  name,
159
- type: module.type === "compiled-wasm" ? "wasm_module" : "text_blob",
193
+ type:
194
+ module.type === "compiled-wasm"
195
+ ? "wasm_module"
196
+ : module.type === "text"
197
+ ? "text_blob"
198
+ : "data_blob",
160
199
  part: name,
161
200
  });
162
201
 
@@ -167,15 +206,13 @@ export function toFormData(worker: CfWorkerInit): FormData {
167
206
  type:
168
207
  module.type === "compiled-wasm"
169
208
  ? "application/wasm"
170
- : "text/plain",
209
+ : module.type === "text"
210
+ ? "text/plain"
211
+ : "application/octet-stream",
171
212
  })
172
213
  );
173
214
  // And then remove it from the modules collection
174
215
  modules = modules?.filter((m) => m !== module);
175
- } else if (module.type === "buffer") {
176
- throw new Error(
177
- 'The "buffer" module type is not yet supported in service worker format workers'
178
- );
179
216
  }
180
217
  }
181
218
  }
@@ -0,0 +1,36 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import dotenv from "dotenv";
4
+ import { logger } from "../logger";
5
+ import type { Config } from "../config";
6
+
7
+ /**
8
+ * Get the Worker `vars` bindings for a `wrangler dev` instance of a Worker.
9
+ *
10
+ * The `vars` bindings can be specified in the `wrangler.toml` configuration file.
11
+ * But "secret" `vars` are usually only provided at the server -
12
+ * either by creating them in the Dashboard UI, or using the `wrangler secret` command.
13
+ *
14
+ * It is useful during development, to provide these types of variable locally.
15
+ * When running `wrangler dev` we will look for a file called `.dev.vars`, situated
16
+ * next to the `wrangler.toml` file (or in the current working directory if there is no
17
+ * `wrangler.toml`).
18
+ *
19
+ * Any values in this file, formatted like a `dotenv` file, will add to or override `vars`
20
+ * bindings provided in the `wrangler.toml`.
21
+ */
22
+ export function getVarsForDev(config: Config): Config["vars"] {
23
+ const configDir = path.resolve(path.dirname(config.configPath ?? "."));
24
+ const devVarsPath = path.resolve(configDir, ".dev.vars");
25
+ if (fs.existsSync(devVarsPath)) {
26
+ const devVarsRelativePath = path.relative(process.cwd(), devVarsPath);
27
+ logger.log(`Using vars defined in ${devVarsRelativePath}`);
28
+ const devVars = dotenv.parse(fs.readFileSync(devVarsPath, "utf8"));
29
+ return {
30
+ ...config.vars,
31
+ ...devVars,
32
+ };
33
+ } else {
34
+ return config.vars;
35
+ }
36
+ }