wrangler 0.0.0-e2a26a0 → 0.0.0-e322475

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 (157) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +27 -14
  3. package/bin/wrangler.js +49 -5
  4. package/kv-asset-handler.js +1 -0
  5. package/miniflare-dist/index.mjs +4886 -0
  6. package/package.json +54 -27
  7. package/pages/functions/buildPlugin.ts +105 -0
  8. package/pages/functions/buildWorker.ts +93 -4
  9. package/pages/functions/filepath-routing.test.ts +223 -36
  10. package/pages/functions/filepath-routing.ts +78 -120
  11. package/pages/functions/routes.ts +13 -17
  12. package/pages/functions/template-plugin.ts +147 -0
  13. package/pages/functions/template-worker.ts +24 -17
  14. package/src/__tests__/config-cache.test.ts +36 -0
  15. package/src/__tests__/configuration.test.ts +3548 -0
  16. package/src/__tests__/dev.test.tsx +1049 -65
  17. package/src/__tests__/guess-worker-format.test.ts +85 -0
  18. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  19. package/src/__tests__/helpers/mock-account-id.ts +26 -8
  20. package/src/__tests__/helpers/mock-bin.ts +4 -1
  21. package/src/__tests__/helpers/mock-cfetch.ts +12 -8
  22. package/src/__tests__/helpers/mock-console.ts +49 -8
  23. package/src/__tests__/helpers/mock-dialogs.ts +99 -11
  24. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  25. package/src/__tests__/helpers/mock-istty.ts +27 -0
  26. package/src/__tests__/helpers/mock-kv.ts +9 -16
  27. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  28. package/src/__tests__/helpers/mock-process.ts +24 -0
  29. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  30. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  31. package/src/__tests__/helpers/run-in-tmp.ts +10 -0
  32. package/src/__tests__/helpers/run-wrangler.ts +5 -3
  33. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  34. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  35. package/src/__tests__/https-options.test.ts +130 -0
  36. package/src/__tests__/index.test.ts +150 -435
  37. package/src/__tests__/init.test.ts +1992 -0
  38. package/src/__tests__/jest.setup.ts +75 -5
  39. package/src/__tests__/kv.test.ts +634 -253
  40. package/src/__tests__/logger.test.ts +141 -0
  41. package/src/__tests__/package-manager.test.ts +156 -9
  42. package/src/__tests__/pages.test.ts +754 -0
  43. package/src/__tests__/parse.test.ts +330 -0
  44. package/src/__tests__/publish.test.ts +5895 -961
  45. package/src/__tests__/r2.test.ts +233 -0
  46. package/src/__tests__/route.test.ts +45 -0
  47. package/src/__tests__/secret.test.ts +362 -91
  48. package/src/__tests__/tail.test.ts +690 -0
  49. package/src/__tests__/user.test.ts +132 -0
  50. package/src/__tests__/whoami.test.tsx +60 -7
  51. package/src/abort.d.ts +3 -0
  52. package/src/bundle-reporter.tsx +29 -0
  53. package/src/bundle.ts +204 -0
  54. package/src/cfetch/index.ts +27 -8
  55. package/src/cfetch/internal.ts +75 -34
  56. package/src/cli.ts +6 -2
  57. package/src/config/README.md +107 -0
  58. package/src/config/config.ts +243 -0
  59. package/src/config/diagnostics.ts +80 -0
  60. package/src/config/environment.ts +420 -0
  61. package/src/config/index.ts +225 -0
  62. package/src/config/validation-helpers.ts +584 -0
  63. package/src/config/validation.ts +1580 -0
  64. package/src/config-cache.ts +45 -0
  65. package/src/create-worker-preview.ts +185 -0
  66. package/src/create-worker-upload-form.ts +254 -0
  67. package/src/dev/dev-vars.ts +36 -0
  68. package/src/dev/dev.tsx +399 -0
  69. package/src/dev/local.tsx +321 -0
  70. package/src/dev/remote.tsx +249 -0
  71. package/src/dev/use-esbuild.ts +112 -0
  72. package/src/dialogs.tsx +64 -6
  73. package/src/durable.ts +102 -0
  74. package/src/entry.ts +328 -0
  75. package/src/environment-variables.ts +35 -0
  76. package/src/errors.ts +11 -0
  77. package/src/generate-auth-url.ts +33 -0
  78. package/src/generate-random-state.ts +16 -0
  79. package/src/git-client.ts +42 -0
  80. package/src/https-options.ts +123 -0
  81. package/src/index.tsx +1804 -1009
  82. package/src/inspect.ts +80 -14
  83. package/src/intl-polyfill.d.ts +139 -0
  84. package/src/{kv.tsx → kv.ts} +193 -69
  85. package/src/logger.ts +73 -0
  86. package/src/miniflare-cli/README.md +30 -0
  87. package/src/miniflare-cli/enum-keys.ts +17 -0
  88. package/src/miniflare-cli/index.ts +41 -0
  89. package/src/miniflare-cli/request-context.ts +40 -0
  90. package/src/module-collection.ts +226 -31
  91. package/src/open-in-browser.ts +17 -0
  92. package/src/package-manager.ts +79 -25
  93. package/src/pages.tsx +1131 -84
  94. package/src/parse.ts +222 -0
  95. package/src/proxy.ts +176 -70
  96. package/src/publish.ts +637 -308
  97. package/src/r2.ts +50 -0
  98. package/src/selfsigned.d.ts +29 -0
  99. package/src/sites.tsx +185 -57
  100. package/src/tail/filters.ts +279 -0
  101. package/src/tail/index.ts +299 -0
  102. package/src/tail/printing.ts +67 -0
  103. package/src/update-check.ts +19 -0
  104. package/src/user.tsx +349 -166
  105. package/src/whoami.tsx +5 -3
  106. package/src/{api/worker.ts → worker.ts} +65 -39
  107. package/src/zones.ts +73 -0
  108. package/templates/gitignore +171 -0
  109. package/templates/new-worker-scheduled.js +17 -0
  110. package/templates/new-worker-scheduled.ts +32 -0
  111. package/templates/new-worker.js +1 -1
  112. package/templates/new-worker.ts +17 -2
  113. package/templates/no-op-worker.js +10 -0
  114. package/templates/static-asset-facade.js +3 -7
  115. package/templates/tsconfig.json +105 -0
  116. package/wrangler-dist/cli.js +77463 -75999
  117. package/src/__tests__/helpers/mock-user.ts +0 -27
  118. package/src/__tests__/logout.test.ts +0 -50
  119. package/src/api/form_data.ts +0 -134
  120. package/src/api/preview.ts +0 -128
  121. package/src/config.ts +0 -641
  122. package/src/dev.tsx +0 -834
  123. package/src/tail.tsx +0 -73
  124. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  125. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  126. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  127. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  128. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  129. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  130. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  131. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  132. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  133. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  134. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  135. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  136. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  137. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  138. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  139. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  140. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  141. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  142. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  143. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  144. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  145. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  146. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  147. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  148. package/vendor/wrangler-mime/LICENSE +0 -21
  149. package/vendor/wrangler-mime/Mime.js +0 -97
  150. package/vendor/wrangler-mime/README.md +0 -187
  151. package/vendor/wrangler-mime/cli.js +0 -46
  152. package/vendor/wrangler-mime/index.js +0 -4
  153. package/vendor/wrangler-mime/lite.js +0 -4
  154. package/vendor/wrangler-mime/package.json +0 -52
  155. package/vendor/wrangler-mime/types/other.js +0 -1
  156. package/vendor/wrangler-mime/types/standard.js +0 -1
  157. package/wrangler-dist/cli.js.map +0 -7
@@ -0,0 +1,321 @@
1
+ import { spawn } from "node:child_process";
2
+ import { realpathSync } from "node:fs";
3
+ import { writeFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { useState, useEffect, useRef } from "react";
6
+ import onExit from "signal-exit";
7
+ import useInspector from "../inspect";
8
+ import { logger } from "../logger";
9
+ import { DEFAULT_MODULE_RULES } from "../module-collection";
10
+ import { waitForPortToBeAvailable } from "../proxy";
11
+ import type { Config } from "../config";
12
+ import type { AssetPaths } from "../sites";
13
+ import type { CfWorkerInit, CfScriptFormat } from "../worker";
14
+ import type { EsbuildBundle } from "./use-esbuild";
15
+ import type { MiniflareOptions } from "miniflare";
16
+
17
+ interface LocalProps {
18
+ name: string | undefined;
19
+ bundle: EsbuildBundle | undefined;
20
+ format: CfScriptFormat | undefined;
21
+ compatibilityDate: string;
22
+ compatibilityFlags: string[] | undefined;
23
+ bindings: CfWorkerInit["bindings"];
24
+ localProtocol: "http" | "https";
25
+ assetPaths: AssetPaths | undefined;
26
+ isWorkersSite: boolean;
27
+ port: number;
28
+ ip: string;
29
+ rules: Config["rules"];
30
+ inspectorPort: number;
31
+ enableLocalPersistence: boolean;
32
+ crons: Config["triggers"]["crons"];
33
+ }
34
+
35
+ export function Local(props: LocalProps) {
36
+ const { inspectorUrl } = useLocalWorker(props);
37
+ useInspector({
38
+ inspectorUrl,
39
+ port: props.inspectorPort,
40
+ logToTerminal: false,
41
+ });
42
+ return null;
43
+ }
44
+
45
+ function useLocalWorker({
46
+ name: workerName,
47
+ bundle,
48
+ format,
49
+ compatibilityDate,
50
+ compatibilityFlags,
51
+ bindings,
52
+ assetPaths,
53
+ isWorkersSite,
54
+ port,
55
+ rules,
56
+ enableLocalPersistence,
57
+ ip,
58
+ localProtocol,
59
+ crons,
60
+ }: LocalProps) {
61
+ // TODO: pass vars via command line
62
+ const local = useRef<ReturnType<typeof spawn>>();
63
+ const removeSignalExitListener = useRef<() => void>();
64
+ const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
65
+ // if we're using local persistence for data, we should use the cwd
66
+ // as an explicit path, or else it'll use the temp dir
67
+ // which disappears when dev ends
68
+ const localPersistencePath = enableLocalPersistence
69
+ ? // Maybe we could make the path configurable as well?
70
+ path.join(process.cwd(), "wrangler-local-state")
71
+ : // We otherwise choose null, but choose true later
72
+ // so that it's persisted in the temp dir across a dev session
73
+ // even when we change source and reload
74
+ null;
75
+ useEffect(() => {
76
+ const abortController = new AbortController();
77
+ async function startLocalWorker() {
78
+ if (!bundle || !format) return;
79
+
80
+ // port for the worker
81
+ await waitForPortToBeAvailable(port, {
82
+ retryPeriod: 200,
83
+ timeout: 2000,
84
+ abortSignal: abortController.signal,
85
+ });
86
+
87
+ if (bindings.services && bindings.services.length > 0) {
88
+ throw new Error(
89
+ "⎔ Service bindings are not yet supported in local mode."
90
+ );
91
+ }
92
+
93
+ // In local mode, we want to copy all referenced modules into
94
+ // the output bundle directory before starting up
95
+ for (const module of bundle.modules) {
96
+ await writeFile(
97
+ path.join(path.dirname(bundle.path), module.name),
98
+ module.content
99
+ );
100
+ }
101
+
102
+ const scriptPath = realpathSync(bundle.path);
103
+
104
+ // the wasm_modules/text_blobs/data_blobs bindings are
105
+ // relative to process.cwd(), but the actual worker bundle
106
+ // is in the temp output directory; so we rewrite the paths to be absolute,
107
+ // letting miniflare resolve them correctly
108
+
109
+ // wasm
110
+ const wasmBindings: Record<string, string> = {};
111
+ for (const [name, filePath] of Object.entries(
112
+ bindings.wasm_modules || {}
113
+ )) {
114
+ wasmBindings[name] = path.join(process.cwd(), filePath);
115
+ }
116
+
117
+ // text
118
+ const textBlobBindings: Record<string, string> = {};
119
+ for (const [name, filePath] of Object.entries(
120
+ bindings.text_blobs || {}
121
+ )) {
122
+ textBlobBindings[name] = path.join(process.cwd(), filePath);
123
+ }
124
+
125
+ // data
126
+ const dataBlobBindings: Record<string, string> = {};
127
+ for (const [name, filePath] of Object.entries(
128
+ bindings.data_blobs || {}
129
+ )) {
130
+ dataBlobBindings[name] = path.join(process.cwd(), filePath);
131
+ }
132
+
133
+ if (format === "service-worker") {
134
+ for (const { type, name } of bundle.modules) {
135
+ if (type === "compiled-wasm") {
136
+ // In service-worker format, .wasm modules are referenced by global identifiers,
137
+ // so we convert it here.
138
+ // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
139
+ // characters with an underscore.
140
+ const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
141
+ wasmBindings[identifier] = name;
142
+ } else if (type === "text") {
143
+ // In service-worker format, text modules are referenced by global identifiers,
144
+ // so we convert it here.
145
+ // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
146
+ // characters with an underscore.
147
+ const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
148
+ textBlobBindings[identifier] = name;
149
+ } else if (type === "buffer") {
150
+ // In service-worker format, data blobs are referenced by global identifiers,
151
+ // so we convert it here.
152
+ // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
153
+ // characters with an underscore.
154
+ const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
155
+ dataBlobBindings[identifier] = name;
156
+ }
157
+ }
158
+ }
159
+
160
+ const options: MiniflareOptions = {
161
+ name: workerName,
162
+ port,
163
+ scriptPath,
164
+ https: localProtocol === "https",
165
+ host: ip,
166
+ modules: format === "modules",
167
+ modulesRules: (rules || [])
168
+ .concat(DEFAULT_MODULE_RULES)
169
+ .map(({ type, globs: include, fallthrough }) => ({
170
+ type,
171
+ include,
172
+ fallthrough,
173
+ })),
174
+ compatibilityDate,
175
+ compatibilityFlags,
176
+ kvNamespaces: bindings.kv_namespaces?.map((kv) => kv.binding),
177
+ durableObjects: Object.fromEntries(
178
+ (bindings.durable_objects?.bindings ?? []).map<[string, string]>(
179
+ (value) => [value.name, value.class_name]
180
+ )
181
+ ),
182
+ ...(localPersistencePath
183
+ ? {
184
+ kvPersist: path.join(localPersistencePath, "kv"),
185
+ durableObjectsPersist: path.join(localPersistencePath, "do"),
186
+ cachePersist: path.join(localPersistencePath, "cache"),
187
+ }
188
+ : {
189
+ // We mark these as true, so that they'll
190
+ // persist in the temp directory.
191
+ // This means they'll persist across a dev session,
192
+ // even if we change source and reload,
193
+ // and be deleted when the dev session ends
194
+ durableObjectsPersist: true,
195
+ cachePersist: true,
196
+ kvPersist: true,
197
+ }),
198
+
199
+ sitePath: assetPaths?.assetDirectory
200
+ ? path.join(assetPaths.baseDirectory, assetPaths.assetDirectory)
201
+ : undefined,
202
+ siteInclude: assetPaths?.includePatterns.length
203
+ ? assetPaths?.includePatterns
204
+ : undefined,
205
+ siteExclude: assetPaths?.excludePatterns.length
206
+ ? assetPaths.excludePatterns
207
+ : undefined,
208
+ bindings: bindings.vars,
209
+ wasmBindings,
210
+ textBlobBindings,
211
+ dataBlobBindings,
212
+ sourceMap: true,
213
+ logUnhandledRejections: true,
214
+ crons,
215
+ };
216
+
217
+ // The path to the Miniflare CLI assumes that this file is being run from
218
+ // `wrangler-dist` and that the CLI is found in `miniflare-dist`.
219
+ // If either of those paths change this line needs updating.
220
+ const miniflareCLIPath = path.resolve(
221
+ __dirname,
222
+ "../miniflare-dist/index.mjs"
223
+ );
224
+ const optionsArg = JSON.stringify(options, null);
225
+
226
+ logger.log("⎔ Starting a local server...");
227
+ local.current = spawn(
228
+ "node",
229
+ [
230
+ "--experimental-vm-modules", // ensures that Miniflare can run ESM Workers
231
+ "--no-warnings", // hide annoying Node warnings
232
+ "--inspect", // start Miniflare listening for a debugger to attach
233
+ miniflareCLIPath,
234
+ optionsArg,
235
+ // "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
236
+ ],
237
+ {
238
+ cwd: path.dirname(scriptPath),
239
+ }
240
+ );
241
+
242
+ local.current.on("close", (code) => {
243
+ if (code) {
244
+ logger.log(`Miniflare process exited with code ${code}`);
245
+ }
246
+ });
247
+
248
+ local.current.stdout?.on("data", (data: Buffer) => {
249
+ process.stdout.write(data);
250
+ });
251
+
252
+ // parse the node inspector url (which may be received in chunks) from stderr
253
+ let stderrData = "";
254
+ local.current.stderr?.on("data", (data: Buffer) => {
255
+ stderrData += data.toString();
256
+ const matches =
257
+ /Debugger listening on (ws:\/\/127\.0\.0\.1:\d+\/[A-Za-z0-9-]+)[\r|\n]/.exec(
258
+ stderrData
259
+ );
260
+ if (matches) {
261
+ local.current?.stderr?.removeAllListeners("data");
262
+ setInspectorUrl(matches[1]);
263
+ }
264
+ });
265
+
266
+ local.current.on("exit", (code) => {
267
+ if (code) {
268
+ logger.error(`Miniflare process exited with code ${code}`);
269
+ }
270
+ });
271
+
272
+ local.current.on("error", (error: Error) => {
273
+ logger.error(`Miniflare process failed to spawn`);
274
+ logger.error(error);
275
+ });
276
+
277
+ removeSignalExitListener.current = onExit((_code, _signal) => {
278
+ logger.log("⎔ Shutting down local server.");
279
+ local.current?.kill();
280
+ local.current = undefined;
281
+ });
282
+ }
283
+
284
+ startLocalWorker().catch((err) => {
285
+ logger.error("local worker:", err);
286
+ });
287
+
288
+ return () => {
289
+ abortController.abort();
290
+ if (local.current) {
291
+ logger.log("⎔ Shutting down local server.");
292
+ local.current?.kill();
293
+ local.current = undefined;
294
+ removeSignalExitListener.current && removeSignalExitListener.current();
295
+ removeSignalExitListener.current = undefined;
296
+ }
297
+ };
298
+ }, [
299
+ bundle,
300
+ workerName,
301
+ format,
302
+ port,
303
+ localProtocol,
304
+ ip,
305
+ bindings.durable_objects?.bindings,
306
+ bindings.kv_namespaces,
307
+ bindings.vars,
308
+ bindings.services,
309
+ compatibilityDate,
310
+ compatibilityFlags,
311
+ localPersistencePath,
312
+ assetPaths,
313
+ isWorkersSite,
314
+ rules,
315
+ bindings.wasm_modules,
316
+ bindings.text_blobs,
317
+ bindings.data_blobs,
318
+ crons,
319
+ ]);
320
+ return { inspectorUrl };
321
+ }
@@ -0,0 +1,249 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { useState, useEffect, useRef } from "react";
4
+ import { printBundleSize } from "../bundle-reporter";
5
+ import { createWorkerPreview } from "../create-worker-preview";
6
+ import useInspector from "../inspect";
7
+ import { logger } from "../logger";
8
+ import { usePreviewServer } from "../proxy";
9
+ import { syncAssets } from "../sites";
10
+ import { requireApiToken, requireAuth } from "../user";
11
+ import type { CfPreviewToken } from "../create-worker-preview";
12
+ import type { AssetPaths } from "../sites";
13
+ import type { CfModule, CfWorkerInit, CfScriptFormat } from "../worker";
14
+ import type { EsbuildBundle } from "./use-esbuild";
15
+
16
+ export function Remote(props: {
17
+ name: string | undefined;
18
+ bundle: EsbuildBundle | undefined;
19
+ format: CfScriptFormat | undefined;
20
+ isWorkersSite: boolean;
21
+ assetPaths: AssetPaths | undefined;
22
+ port: number;
23
+ ip: string;
24
+ localProtocol: "https" | "http";
25
+ inspectorPort: number;
26
+ accountId: string | undefined;
27
+ bindings: CfWorkerInit["bindings"];
28
+ compatibilityDate: string;
29
+ compatibilityFlags: string[] | undefined;
30
+ usageModel: "bundled" | "unbound" | undefined;
31
+ env: string | undefined;
32
+ legacyEnv: boolean | undefined;
33
+ zone: string | undefined;
34
+ host: string | undefined;
35
+ }) {
36
+ const previewToken = useWorker({
37
+ name: props.name,
38
+ bundle: props.bundle,
39
+ format: props.format,
40
+ modules: props.bundle ? props.bundle.modules : [],
41
+ accountId: props.accountId,
42
+ bindings: props.bindings,
43
+ assetPaths: props.assetPaths,
44
+ isWorkersSite: props.isWorkersSite,
45
+ port: props.port,
46
+ compatibilityDate: props.compatibilityDate,
47
+ compatibilityFlags: props.compatibilityFlags,
48
+ usageModel: props.usageModel,
49
+ env: props.env,
50
+ legacyEnv: props.legacyEnv,
51
+ zone: props.zone,
52
+ host: props.host,
53
+ });
54
+
55
+ usePreviewServer({
56
+ previewToken,
57
+ assetDirectory: props.isWorkersSite
58
+ ? undefined
59
+ : props.assetPaths?.assetDirectory,
60
+ localProtocol: props.localProtocol,
61
+ localPort: props.port,
62
+ ip: props.ip,
63
+ });
64
+
65
+ useInspector({
66
+ inspectorUrl: previewToken ? previewToken.inspectorUrl.href : undefined,
67
+ port: props.inspectorPort,
68
+ logToTerminal: true,
69
+ });
70
+ return null;
71
+ }
72
+
73
+ export function useWorker(props: {
74
+ name: string | undefined;
75
+ bundle: EsbuildBundle | undefined;
76
+ format: CfScriptFormat | undefined;
77
+ modules: CfModule[];
78
+ accountId: string | undefined;
79
+ bindings: CfWorkerInit["bindings"];
80
+ assetPaths: AssetPaths | undefined;
81
+ isWorkersSite: boolean;
82
+ port: number;
83
+ compatibilityDate: string | undefined;
84
+ compatibilityFlags: string[] | undefined;
85
+ usageModel: "bundled" | "unbound" | undefined;
86
+ env: string | undefined;
87
+ legacyEnv: boolean | undefined;
88
+ zone: string | undefined;
89
+ host: string | undefined;
90
+ }): CfPreviewToken | undefined {
91
+ const {
92
+ name,
93
+ bundle,
94
+ format,
95
+ modules,
96
+ accountId,
97
+ bindings,
98
+ assetPaths,
99
+ compatibilityDate,
100
+ compatibilityFlags,
101
+ usageModel,
102
+ port,
103
+ } = props;
104
+ const [token, setToken] = useState<CfPreviewToken | undefined>();
105
+
106
+ // This is the most reliable way to detect whether
107
+ // something's "happened" in our system; We make a ref and
108
+ // mark it once we log our initial message. Refs are vars!
109
+ const startedRef = useRef(false);
110
+ // This ref holds the actual accountId being used, the `accountId` prop could be undefined
111
+ // as it is only what is retrieved from the wrangler.toml config.
112
+ const accountIdRef = useRef(accountId);
113
+
114
+ useEffect(() => {
115
+ const abortController = new AbortController();
116
+ async function start() {
117
+ setToken(undefined); // reset token in case we're re-running
118
+
119
+ if (!bundle || !format) return;
120
+
121
+ if (!startedRef.current) {
122
+ startedRef.current = true;
123
+ } else {
124
+ logger.log("⎔ Detected changes, restarted server.");
125
+ }
126
+
127
+ // Ensure we have an account id, even if it means logging in here.
128
+ accountIdRef.current = await requireAuth({
129
+ account_id: accountIdRef.current,
130
+ });
131
+
132
+ const content = await readFile(bundle.path, "utf-8");
133
+
134
+ // TODO: For Dev we could show the reporter message in the interactive box.
135
+ void printBundleSize(
136
+ { name: path.basename(bundle.path), content: content },
137
+ modules
138
+ );
139
+
140
+ const assets = await syncAssets(
141
+ accountIdRef.current,
142
+ // When we're using the newer service environments, we wouldn't
143
+ // have added the env name on to the script name. However, we must
144
+ // include it in the kv namespace name regardless (since there's no
145
+ // concept of service environments for kv namespaces yet).
146
+ name + (!props.legacyEnv && props.env ? `-${props.env}` : ""),
147
+ props.isWorkersSite ? assetPaths : undefined,
148
+ true,
149
+ false
150
+ ); // TODO: cancellable?
151
+
152
+ const init: CfWorkerInit = {
153
+ name,
154
+ main: {
155
+ name: path.basename(bundle.path),
156
+ type: format === "modules" ? "esm" : "commonjs",
157
+ content,
158
+ },
159
+ modules: modules.concat(
160
+ assets.manifest
161
+ ? {
162
+ name: "__STATIC_CONTENT_MANIFEST",
163
+ content: JSON.stringify(assets.manifest),
164
+ type: "text",
165
+ }
166
+ : []
167
+ ),
168
+ bindings: {
169
+ ...bindings,
170
+ kv_namespaces: (bindings.kv_namespaces || []).concat(
171
+ assets.namespace
172
+ ? { binding: "__STATIC_CONTENT", id: assets.namespace }
173
+ : []
174
+ ),
175
+ text_blobs: {
176
+ ...bindings.text_blobs,
177
+ ...(assets.manifest &&
178
+ format === "service-worker" && {
179
+ __STATIC_CONTENT_MANIFEST: "__STATIC_CONTENT_MANIFEST",
180
+ }),
181
+ },
182
+ },
183
+ migrations: undefined, // no migrations in dev
184
+ compatibility_date: compatibilityDate,
185
+ compatibility_flags: compatibilityFlags,
186
+ usage_model: usageModel,
187
+ };
188
+ setToken(
189
+ await createWorkerPreview(
190
+ init,
191
+ {
192
+ accountId: accountIdRef.current,
193
+ apiToken: requireApiToken(),
194
+ },
195
+ {
196
+ env: props.env,
197
+ legacyEnv: props.legacyEnv,
198
+ zone: props.zone,
199
+ host: props.host,
200
+ },
201
+ abortController.signal
202
+ )
203
+ );
204
+ }
205
+ start().catch((err) => {
206
+ // we want to log the error, but not end the process
207
+ // since it could recover after the developer fixes whatever's wrong
208
+ if ((err as { code: string }).code !== "ABORT_ERR") {
209
+ // instead of logging the raw API error to the user,
210
+ // give them friendly instructions
211
+ // for error 10063 (workers.dev subdomain required)
212
+ if (err.code === 10063) {
213
+ const errorMessage =
214
+ "Error: You need to register a workers.dev subdomain before running the dev command in remote mode";
215
+ const solutionMessage =
216
+ "You can either enable local mode by pressing l, or register a workers.dev subdomain here:";
217
+ const onboardingLink = `https://dash.cloudflare.com/${accountIdRef.current}/workers/onboarding`;
218
+ logger.error(
219
+ `${errorMessage}\n${solutionMessage}\n${onboardingLink}`
220
+ );
221
+ } else {
222
+ logger.error("Error on remote worker:", err);
223
+ }
224
+ }
225
+ });
226
+
227
+ return () => {
228
+ abortController.abort();
229
+ };
230
+ }, [
231
+ name,
232
+ bundle,
233
+ format,
234
+ accountId,
235
+ port,
236
+ assetPaths,
237
+ props.isWorkersSite,
238
+ compatibilityDate,
239
+ compatibilityFlags,
240
+ usageModel,
241
+ bindings,
242
+ modules,
243
+ props.env,
244
+ props.legacyEnv,
245
+ props.zone,
246
+ props.host,
247
+ ]);
248
+ return token;
249
+ }
@@ -0,0 +1,112 @@
1
+ import assert from "node:assert";
2
+ import { useApp } from "ink";
3
+ import { useState, useEffect } from "react";
4
+ import { bundleWorker } from "../bundle";
5
+ import { logger } from "../logger";
6
+ import type { Config } from "../config";
7
+ import type { Entry } from "../entry";
8
+ import type { CfModule } from "../worker";
9
+ import type { WatchMode } from "esbuild";
10
+
11
+ export type EsbuildBundle = {
12
+ id: number;
13
+ path: string;
14
+ entry: Entry;
15
+ type: "esm" | "commonjs";
16
+ modules: CfModule[];
17
+ };
18
+
19
+ export function useEsbuild({
20
+ entry,
21
+ destination,
22
+ jsxFactory,
23
+ jsxFragment,
24
+ rules,
25
+ serveAssetsFromWorker,
26
+ tsconfig,
27
+ minify,
28
+ nodeCompat,
29
+ }: {
30
+ entry: Entry;
31
+ destination: string | undefined;
32
+ jsxFactory: string | undefined;
33
+ jsxFragment: string | undefined;
34
+ rules: Config["rules"];
35
+ serveAssetsFromWorker: boolean;
36
+ tsconfig: string | undefined;
37
+ minify: boolean | undefined;
38
+ nodeCompat: boolean | undefined;
39
+ }): EsbuildBundle | undefined {
40
+ const [bundle, setBundle] = useState<EsbuildBundle>();
41
+ const { exit } = useApp();
42
+ useEffect(() => {
43
+ let stopWatching: (() => void) | undefined = undefined;
44
+
45
+ const watchMode: WatchMode = {
46
+ async onRebuild(error) {
47
+ if (error) logger.error("Watch build failed:", error);
48
+ else {
49
+ // nothing really changes here, so let's increment the id
50
+ // to change the return object's identity
51
+ setBundle((previousBundle) => {
52
+ assert(
53
+ previousBundle,
54
+ "Rebuild triggered with no previous build available"
55
+ );
56
+ return { ...previousBundle, id: previousBundle.id + 1 };
57
+ });
58
+ }
59
+ },
60
+ };
61
+
62
+ async function build() {
63
+ if (!destination) return;
64
+
65
+ const { resolvedEntryPointPath, bundleType, modules, stop } =
66
+ await bundleWorker(entry, destination, {
67
+ serveAssetsFromWorker,
68
+ jsxFactory,
69
+ jsxFragment,
70
+ rules,
71
+ watch: watchMode,
72
+ tsconfig,
73
+ minify,
74
+ nodeCompat,
75
+ });
76
+
77
+ // Capture the `stop()` method to use as the `useEffect()` destructor.
78
+ stopWatching = stop;
79
+
80
+ setBundle({
81
+ id: 0,
82
+ entry,
83
+ path: resolvedEntryPointPath,
84
+ type: bundleType,
85
+ modules,
86
+ });
87
+ }
88
+
89
+ build().catch((err) => {
90
+ // If esbuild fails on first run, we want to quit the process
91
+ // since we can't recover from here
92
+ // related: https://github.com/evanw/esbuild/issues/1037
93
+ exit(err);
94
+ });
95
+
96
+ return () => {
97
+ stopWatching?.();
98
+ };
99
+ }, [
100
+ entry,
101
+ destination,
102
+ jsxFactory,
103
+ jsxFragment,
104
+ serveAssetsFromWorker,
105
+ rules,
106
+ tsconfig,
107
+ exit,
108
+ minify,
109
+ nodeCompat,
110
+ ]);
111
+ return bundle;
112
+ }