wrangler 0.0.0-e88a54e → 0.0.0-e8e684c7

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 (222) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +51 -14
  3. package/bin/wrangler.js +161 -29
  4. package/kv-asset-handler.js +1 -0
  5. package/miniflare-dist/index.mjs +5484 -21
  6. package/package.json +173 -143
  7. package/src/__tests__/config-cache-without-cache-dir.test.ts +38 -0
  8. package/src/__tests__/config-cache.test.ts +42 -0
  9. package/src/__tests__/configuration.test.ts +4016 -2786
  10. package/src/__tests__/dev.test.tsx +1305 -483
  11. package/src/__tests__/generate.test.ts +91 -0
  12. package/src/__tests__/guess-worker-format.test.ts +75 -48
  13. package/src/__tests__/helpers/cmd-shim.d.ts +6 -6
  14. package/src/__tests__/helpers/mock-account-id.ts +24 -24
  15. package/src/__tests__/helpers/mock-bin.ts +20 -20
  16. package/src/__tests__/helpers/mock-cfetch.ts +182 -93
  17. package/src/__tests__/helpers/mock-console.ts +59 -32
  18. package/src/__tests__/helpers/mock-dialogs.ts +121 -33
  19. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  20. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  21. package/src/__tests__/helpers/mock-istty.ts +74 -0
  22. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  23. package/src/__tests__/helpers/mock-kv.ts +26 -26
  24. package/src/__tests__/helpers/mock-oauth-flow.ts +250 -0
  25. package/src/__tests__/helpers/mock-process.ts +39 -0
  26. package/src/__tests__/helpers/mock-stdin.ts +82 -77
  27. package/src/__tests__/helpers/mock-web-socket.ts +21 -21
  28. package/src/__tests__/helpers/run-in-tmp.ts +28 -22
  29. package/src/__tests__/helpers/run-wrangler.ts +8 -8
  30. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  31. package/src/__tests__/helpers/write-wrangler-toml.ts +9 -9
  32. package/src/__tests__/https-options.test.ts +144 -108
  33. package/src/__tests__/index.test.ts +229 -975
  34. package/src/__tests__/init.test.ts +2170 -0
  35. package/src/__tests__/jest.setup.ts +122 -34
  36. package/src/__tests__/kv.test.ts +1376 -1087
  37. package/src/__tests__/logger.test.ts +141 -0
  38. package/src/__tests__/metrics.test.ts +450 -0
  39. package/src/__tests__/package-manager.test.ts +312 -165
  40. package/src/__tests__/pages.test.ts +1270 -39
  41. package/src/__tests__/parse.test.ts +253 -245
  42. package/src/__tests__/publish.test.ts +6878 -2627
  43. package/src/__tests__/pubsub.test.ts +367 -0
  44. package/src/__tests__/r2.test.ts +328 -190
  45. package/src/__tests__/route.test.ts +22 -22
  46. package/src/__tests__/secret.test.ts +474 -407
  47. package/src/__tests__/tail.test.ts +679 -441
  48. package/src/__tests__/test-old-node-version.js +31 -0
  49. package/src/__tests__/user.test.ts +155 -0
  50. package/src/__tests__/validate-dev-props.test.ts +56 -0
  51. package/src/__tests__/version.test.ts +35 -0
  52. package/src/__tests__/whoami.test.tsx +276 -87
  53. package/src/__tests__/worker-namespace.test.ts +329 -0
  54. package/src/abort.d.ts +3 -0
  55. package/src/api/dev.ts +93 -0
  56. package/src/api/index.ts +1 -0
  57. package/src/bundle-reporter.tsx +29 -0
  58. package/src/bundle.ts +441 -110
  59. package/src/cfetch/index.ts +83 -66
  60. package/src/cfetch/internal.ts +251 -92
  61. package/src/cli.ts +22 -14
  62. package/src/config/README.md +1 -1
  63. package/src/config/config.ts +224 -179
  64. package/src/config/diagnostics.ts +61 -61
  65. package/src/config/environment.ts +430 -311
  66. package/src/config/index.ts +227 -38
  67. package/src/config/validation-helpers.ts +414 -303
  68. package/src/config/validation.ts +1745 -973
  69. package/src/config-cache.ts +85 -0
  70. package/src/create-worker-preview.ts +209 -118
  71. package/src/create-worker-upload-form.ts +275 -201
  72. package/src/dev/dev-vars.ts +36 -0
  73. package/src/dev/dev.tsx +488 -307
  74. package/src/dev/local.tsx +427 -210
  75. package/src/dev/remote.tsx +407 -188
  76. package/src/dev/use-esbuild.ts +156 -78
  77. package/src/dev/validate-dev-props.ts +31 -0
  78. package/src/dev-registry.tsx +160 -0
  79. package/src/dev.tsx +696 -0
  80. package/src/dialogs.tsx +115 -57
  81. package/src/dispatch-namespace.ts +206 -0
  82. package/src/durable.ts +102 -0
  83. package/src/entry.ts +267 -147
  84. package/src/environment-variables.ts +25 -23
  85. package/src/errors.ts +11 -0
  86. package/src/faye-websocket.d.ts +6 -0
  87. package/src/generate.ts +131 -0
  88. package/src/git-client.ts +42 -0
  89. package/src/global-wrangler-config-path.ts +26 -0
  90. package/src/https-options.ts +85 -80
  91. package/src/index.tsx +2191 -2304
  92. package/src/init.ts +622 -0
  93. package/src/inspect.ts +687 -557
  94. package/src/intl-polyfill.d.ts +139 -0
  95. package/src/is-ci.ts +14 -0
  96. package/src/is-interactive.ts +12 -0
  97. package/src/kv.ts +353 -242
  98. package/src/logger.ts +73 -0
  99. package/src/metrics/index.ts +5 -0
  100. package/src/metrics/metrics-config.ts +239 -0
  101. package/src/metrics/metrics-dispatcher.ts +96 -0
  102. package/src/metrics/metrics-usage-headers.ts +24 -0
  103. package/src/metrics/send-event.ts +92 -0
  104. package/src/miniflare-cli/assets.ts +554 -0
  105. package/src/miniflare-cli/enum-keys.ts +8 -8
  106. package/src/miniflare-cli/index.ts +199 -29
  107. package/src/miniflare-cli/request-context.ts +40 -0
  108. package/src/module-collection.ts +213 -213
  109. package/src/open-in-browser.ts +8 -16
  110. package/src/package-manager.ts +183 -90
  111. package/src/pages/build.tsx +238 -0
  112. package/src/pages/constants.ts +12 -0
  113. package/src/pages/deployments.tsx +106 -0
  114. package/src/pages/dev.tsx +528 -0
  115. package/src/pages/errors.ts +22 -0
  116. package/src/pages/functions/buildPlugin.ts +109 -0
  117. package/src/pages/functions/buildWorker.ts +155 -0
  118. package/src/pages/functions/filepath-routing.test.ts +234 -0
  119. package/src/pages/functions/filepath-routing.ts +189 -0
  120. package/src/pages/functions/identifiers.ts +78 -0
  121. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  122. package/src/pages/functions/routes-consolidation.ts +73 -0
  123. package/src/pages/functions/routes-transformation.test.ts +271 -0
  124. package/src/pages/functions/routes-transformation.ts +122 -0
  125. package/src/pages/functions/routes.ts +151 -0
  126. package/src/pages/functions.tsx +96 -0
  127. package/src/pages/index.tsx +94 -0
  128. package/src/pages/projects.tsx +169 -0
  129. package/src/pages/publish.tsx +391 -0
  130. package/src/pages/types.ts +49 -0
  131. package/src/pages/upload.tsx +413 -0
  132. package/src/pages/utils.ts +12 -0
  133. package/src/parse.ts +224 -121
  134. package/src/paths.ts +6 -6
  135. package/src/preview.ts +31 -0
  136. package/src/proxy.ts +416 -370
  137. package/src/publish.ts +796 -321
  138. package/src/pubsub/index.ts +286 -0
  139. package/src/pubsub/pubsub-commands.tsx +623 -0
  140. package/src/r2.ts +100 -19
  141. package/src/selfsigned.d.ts +23 -23
  142. package/src/sites.tsx +281 -189
  143. package/src/tail/filters.ts +111 -109
  144. package/src/tail/index.ts +230 -217
  145. package/src/tail/printing.ts +92 -47
  146. package/src/update-check.ts +19 -0
  147. package/src/user/choose-account.tsx +69 -0
  148. package/src/user/env-vars.ts +46 -0
  149. package/src/user/generate-auth-url.ts +33 -0
  150. package/src/user/generate-random-state.ts +16 -0
  151. package/src/user/index.ts +3 -0
  152. package/src/user/user.tsx +1191 -0
  153. package/src/whoami.tsx +142 -42
  154. package/src/worker.ts +135 -94
  155. package/src/zones.ts +167 -0
  156. package/templates/checked-fetch.js +17 -0
  157. package/templates/first-party-worker-module-facade.ts +18 -0
  158. package/templates/format-dev-errors.ts +32 -0
  159. package/templates/gitignore +171 -0
  160. package/templates/new-worker-scheduled.js +17 -0
  161. package/templates/new-worker-scheduled.ts +32 -0
  162. package/templates/new-worker.js +3 -3
  163. package/templates/new-worker.ts +18 -3
  164. package/templates/no-op-worker.js +10 -0
  165. package/templates/pages-shim.ts +9 -0
  166. package/templates/pages-template-plugin.ts +167 -0
  167. package/templates/pages-template-worker.ts +172 -0
  168. package/templates/serve-static-assets.ts +63 -0
  169. package/templates/service-bindings-module-facade.js +54 -0
  170. package/templates/service-bindings-sw-facade.js +42 -0
  171. package/templates/tsconfig.json +95 -95
  172. package/wrangler-dist/cli.d.ts +338 -0
  173. package/wrangler-dist/cli.js +124611 -112403
  174. package/miniflare-dist/index.mjs.map +0 -7
  175. package/pages/functions/buildWorker.ts +0 -62
  176. package/pages/functions/filepath-routing.test.ts +0 -223
  177. package/pages/functions/filepath-routing.ts +0 -181
  178. package/pages/functions/identifiers.ts +0 -78
  179. package/pages/functions/routes.ts +0 -151
  180. package/pages/functions/template-worker.ts +0 -130
  181. package/src/__tests__/dev2.test.tsx +0 -154
  182. package/src/__tests__/helpers/faye-websocket.d.ts +0 -6
  183. package/src/__tests__/logout.test.ts +0 -52
  184. package/src/__tests__/sentry.test.ts +0 -146
  185. package/src/pages.tsx +0 -1047
  186. package/src/reporting.ts +0 -131
  187. package/src/user.tsx +0 -1118
  188. package/templates/static-asset-facade.js +0 -47
  189. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  190. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  191. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  192. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  193. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  194. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  195. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  196. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  197. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  198. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  199. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  200. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  201. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  202. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  203. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  204. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  205. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  206. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  207. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  208. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  209. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  210. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  211. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  212. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  213. package/vendor/wrangler-mime/LICENSE +0 -21
  214. package/vendor/wrangler-mime/Mime.js +0 -97
  215. package/vendor/wrangler-mime/README.md +0 -187
  216. package/vendor/wrangler-mime/cli.js +0 -46
  217. package/vendor/wrangler-mime/index.js +0 -4
  218. package/vendor/wrangler-mime/lite.js +0 -4
  219. package/vendor/wrangler-mime/package.json +0 -52
  220. package/vendor/wrangler-mime/types/other.js +0 -1
  221. package/vendor/wrangler-mime/types/standard.js +0 -1
  222. package/wrangler-dist/cli.js.map +0 -7
package/src/dev/dev.tsx CHANGED
@@ -1,233 +1,392 @@
1
1
  import { spawn } from "node:child_process";
2
+ import * as path from "node:path";
3
+ import * as util from "node:util";
2
4
  import { watch } from "chokidar";
3
5
  import clipboardy from "clipboardy";
4
6
  import commandExists from "command-exists";
5
- import { Box, Text, useApp, useInput } from "ink";
7
+ import { Box, Text, useApp, useInput, useStdin } from "ink";
6
8
  import React, { useState, useEffect, useRef } from "react";
7
9
  import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
8
10
  import onExit from "signal-exit";
9
11
  import tmp from "tmp-promise";
10
12
  import { fetch } from "undici";
13
+ import {
14
+ getRegisteredWorkers,
15
+ startWorkerRegistry,
16
+ stopWorkerRegistry,
17
+ unregisterWorker,
18
+ } from "../dev-registry";
11
19
  import { runCustomBuild } from "../entry";
20
+ import { openInspector } from "../inspect";
21
+ import { logger } from "../logger";
12
22
  import openInBrowser from "../open-in-browser";
13
- import { reportError } from "../reporting";
14
- import { getAPIToken } from "../user";
15
23
  import { Local } from "./local";
16
24
  import { Remote } from "./remote";
17
25
  import { useEsbuild } from "./use-esbuild";
26
+ import { validateDevProps } from "./validate-dev-props";
18
27
  import type { Config } from "../config";
28
+ import type { Route } from "../config/environment";
29
+ import type { WorkerRegistry } from "../dev-registry";
19
30
  import type { Entry } from "../entry";
31
+ import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli";
20
32
  import type { AssetPaths } from "../sites";
21
33
  import type { CfWorkerInit } from "../worker";
22
34
 
35
+ /**
36
+ * This hooks establishes a connection with the dev registry,
37
+ * and periodically updates itself with details of workers currently
38
+ * running a dev session on this system.
39
+ */
40
+ function useDevRegistry(
41
+ name: string | undefined,
42
+ services: Config["services"] | undefined,
43
+ durableObjects: Config["durable_objects"] | undefined,
44
+ mode: "local" | "remote"
45
+ ): WorkerRegistry {
46
+ const [workers, setWorkers] = useState<WorkerRegistry>({});
47
+
48
+ useEffect(() => {
49
+ // Let's try to start registry
50
+ // TODO: we should probably call this in a loop
51
+ // in case the registry dies elsewhere
52
+ startWorkerRegistry().catch((err) => {
53
+ logger.error("failed to start worker registry", err);
54
+ });
55
+
56
+ const serviceNames = (services || []).map(
57
+ (serviceBinding) => serviceBinding.service
58
+ );
59
+ const durableObjectServices = (
60
+ durableObjects || { bindings: [] }
61
+ ).bindings.map((durableObjectBinding) => durableObjectBinding.script_name);
62
+
63
+ const interval =
64
+ // TODO: enable this for remote mode as well
65
+ // https://github.com/cloudflare/wrangler2/issues/1182
66
+ mode === "local"
67
+ ? setInterval(() => {
68
+ getRegisteredWorkers().then(
69
+ (workerDefinitions: WorkerRegistry | undefined) => {
70
+ // We only want the workers that we're bound to
71
+ // so let's filter out the others
72
+ const filteredWorkers = Object.fromEntries(
73
+ Object.entries(workerDefinitions || {}).filter(
74
+ ([key, _value]) =>
75
+ serviceNames.includes(key) ||
76
+ durableObjectServices.includes(key)
77
+ )
78
+ );
79
+ setWorkers((prevWorkers) => {
80
+ if (!util.isDeepStrictEqual(filteredWorkers, prevWorkers)) {
81
+ return filteredWorkers;
82
+ }
83
+ return prevWorkers;
84
+ });
85
+ },
86
+ (err) => {
87
+ logger.warn("Failed to get worker definitions", err);
88
+ }
89
+ );
90
+ }, 300)
91
+ : undefined;
92
+
93
+ return () => {
94
+ interval && clearInterval(interval);
95
+ Promise.allSettled([
96
+ name ? unregisterWorker(name) : Promise.resolve(),
97
+ stopWorkerRegistry(),
98
+ ]).then(
99
+ ([unregisterResult, stopRegistryResult]) => {
100
+ if (unregisterResult.status === "rejected") {
101
+ logger.error(
102
+ "Failed to unregister worker",
103
+ unregisterResult.reason
104
+ );
105
+ }
106
+ if (stopRegistryResult.status === "rejected") {
107
+ logger.error(
108
+ "Failed to stop worker registry",
109
+ stopRegistryResult.reason
110
+ );
111
+ }
112
+ },
113
+ (err) => {
114
+ logger.error("Failed to clear dev registry effect", err);
115
+ }
116
+ );
117
+ };
118
+ }, [name, services, durableObjects, mode]);
119
+
120
+ return workers;
121
+ }
122
+
23
123
  export type DevProps = {
24
- name?: string;
25
- entry: Entry;
26
- port?: number;
27
- ip: string;
28
- inspectorPort: number;
29
- rules: Config["rules"];
30
- accountId: undefined | string;
31
- initialMode: "local" | "remote";
32
- jsxFactory: undefined | string;
33
- jsxFragment: undefined | string;
34
- upstreamProtocol: "https" | "http";
35
- localProtocol: "https" | "http";
36
- enableLocalPersistence: boolean;
37
- bindings: CfWorkerInit["bindings"];
38
- public: undefined | string;
39
- assetPaths: undefined | AssetPaths;
40
- compatibilityDate: undefined | string;
41
- compatibilityFlags: undefined | string[];
42
- usageModel: undefined | "bundled" | "unbound";
43
- buildCommand: {
44
- command?: undefined | string;
45
- cwd?: undefined | string;
46
- watch_dir?: undefined | string;
47
- };
48
- env: string | undefined;
49
- legacyEnv: boolean;
50
- zone:
51
- | {
52
- id: string;
53
- host: string;
54
- }
55
- | undefined;
124
+ name: string | undefined;
125
+ noBundle: boolean;
126
+ entry: Entry;
127
+ port: number;
128
+ ip: string;
129
+ inspectorPort: number;
130
+ rules: Config["rules"];
131
+ accountId: string | undefined;
132
+ initialMode: "local" | "remote";
133
+ jsxFactory: string | undefined;
134
+ jsxFragment: string | undefined;
135
+ tsconfig: string | undefined;
136
+ upstreamProtocol: "https" | "http";
137
+ localProtocol: "https" | "http";
138
+ localUpstream: string | undefined;
139
+ enableLocalPersistence: boolean;
140
+ liveReload: boolean;
141
+ bindings: CfWorkerInit["bindings"];
142
+ define: Config["define"];
143
+ crons: Config["triggers"]["crons"];
144
+ isWorkersSite: boolean;
145
+ assetPaths: AssetPaths | undefined;
146
+ assetsConfig: Config["assets"];
147
+ compatibilityDate: string;
148
+ compatibilityFlags: string[] | undefined;
149
+ usageModel: "bundled" | "unbound" | undefined;
150
+ minify: boolean | undefined;
151
+ nodeCompat: boolean | undefined;
152
+ build: Config["build"];
153
+ env: string | undefined;
154
+ legacyEnv: boolean;
155
+ zone: string | undefined;
156
+ host: string | undefined;
157
+ routes: Route[] | undefined;
158
+ inspect: boolean;
159
+ logLevel: "none" | "error" | "log" | "warn" | "debug" | undefined;
160
+ logPrefix?: string;
161
+ onReady: (() => void) | undefined;
162
+ showInteractiveDevSession: boolean | undefined;
163
+ forceLocal: boolean | undefined;
164
+ enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
165
+ firstPartyWorker: boolean | undefined;
166
+ sendMetrics: boolean | undefined;
56
167
  };
57
168
 
58
169
  export function DevImplementation(props: DevProps): JSX.Element {
59
- const port = props.port ?? 8787;
60
- const apiToken = props.initialMode === "remote" ? getAPIToken() : undefined;
61
- const directory = useTmpDir();
62
-
63
- useCustomBuild(props.entry, props.buildCommand);
64
-
65
- if (props.public && props.entry.format === "service-worker") {
66
- throw new Error(
67
- "You cannot use the service worker format with a `public` directory."
68
- );
69
- }
70
-
71
- if (props.bindings.wasm_modules && props.entry.format === "modules") {
72
- throw new Error(
73
- "You cannot configure [wasm_modules] with an ES module worker. Instead, import the .wasm module directly in your code"
74
- );
75
- }
76
-
77
- if (props.bindings.text_blobs && props.entry.format === "modules") {
78
- throw new Error(
79
- "You cannot configure [text_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[build.upload.rules]` in your wrangler.toml"
80
- );
81
- }
82
-
83
- const bundle = useEsbuild({
84
- entry: props.entry,
85
- destination: directory,
86
- staticRoot: props.public,
87
- jsxFactory: props.jsxFactory,
88
- rules: props.rules,
89
- jsxFragment: props.jsxFragment,
90
- serveAssetsFromWorker: !!props.public,
91
- });
92
-
93
- const toggles = useHotkeys(
94
- {
95
- local: props.initialMode === "local",
96
- tunnel: false,
97
- },
98
- port,
99
- props.ip,
100
- props.inspectorPort,
101
- props.localProtocol
102
- );
103
-
104
- useTunnel(toggles.tunnel);
105
-
106
- return (
107
- <>
108
- {toggles.local ? (
109
- <Local
110
- name={props.name}
111
- bundle={bundle}
112
- format={props.entry.format}
113
- compatibilityDate={props.compatibilityDate}
114
- compatibilityFlags={props.compatibilityFlags}
115
- bindings={props.bindings}
116
- assetPaths={props.assetPaths}
117
- public={props.public}
118
- port={port}
119
- ip={props.ip}
120
- rules={props.rules}
121
- inspectorPort={props.inspectorPort}
122
- enableLocalPersistence={props.enableLocalPersistence}
123
- />
124
- ) : (
125
- <Remote
126
- name={props.name}
127
- bundle={bundle}
128
- format={props.entry.format}
129
- accountId={props.accountId}
130
- apiToken={apiToken}
131
- bindings={props.bindings}
132
- assetPaths={props.assetPaths}
133
- public={props.public}
134
- port={port}
135
- ip={props.ip}
136
- localProtocol={props.localProtocol}
137
- inspectorPort={props.inspectorPort}
138
- compatibilityDate={props.compatibilityDate}
139
- compatibilityFlags={props.compatibilityFlags}
140
- usageModel={props.usageModel}
141
- env={props.env}
142
- legacyEnv={props.legacyEnv}
143
- zone={props.zone}
144
- />
145
- )}
146
- <Box borderStyle="round" paddingLeft={1} paddingRight={1}>
147
- <Text>
148
- {`B to open a browser, D to open Devtools, L to ${
149
- toggles.local ? "turn off" : "turn on"
150
- } local mode, X to exit`}
151
- </Text>
152
- </Box>
153
- </>
154
- );
170
+ validateDevProps(props);
171
+
172
+ // only load the UI if we're running in a supported environment
173
+ const { isRawModeSupported } = useStdin();
174
+
175
+ return props.showInteractiveDevSession ?? isRawModeSupported ? (
176
+ <InteractiveDevSession {...props} />
177
+ ) : (
178
+ <DevSession {...props} local={props.initialMode === "local"} />
179
+ );
180
+ }
181
+
182
+ function InteractiveDevSession(props: DevProps) {
183
+ const toggles = useHotkeys({
184
+ initial: {
185
+ local: props.initialMode === "local",
186
+ tunnel: false,
187
+ },
188
+ port: props.port,
189
+ ip: props.ip,
190
+ inspectorPort: props.inspectorPort,
191
+ inspect: props.inspect,
192
+ localProtocol: props.localProtocol,
193
+ forceLocal: props.forceLocal,
194
+ });
195
+
196
+ useTunnel(toggles.tunnel);
197
+
198
+ return (
199
+ <>
200
+ <DevSession {...props} local={toggles.local} />
201
+ <Box borderStyle="round" paddingLeft={1} paddingRight={1}>
202
+ <Text bold={true}>[b]</Text>
203
+ <Text> open a browser, </Text>
204
+ {props.inspect ? (
205
+ <>
206
+ <Text bold={true}>[d]</Text>
207
+ <Text> open Devtools, </Text>
208
+ </>
209
+ ) : null}
210
+ {!props.forceLocal ? (
211
+ <>
212
+ <Text bold={true}>[l]</Text>
213
+ <Text> {toggles.local ? "turn off" : "turn on"} local mode, </Text>
214
+ </>
215
+ ) : null}
216
+ <Text bold={true}>[c]</Text>
217
+ <Text> clear console, </Text>
218
+ <Text bold={true}>[x]</Text>
219
+ <Text> to exit</Text>
220
+ </Box>
221
+ </>
222
+ );
223
+ }
224
+
225
+ type DevSessionProps = DevProps & {
226
+ local: boolean;
227
+ };
228
+
229
+ function DevSession(props: DevSessionProps) {
230
+ useCustomBuild(props.entry, props.build);
231
+
232
+ const directory = useTmpDir();
233
+
234
+ const workerDefinitions = useDevRegistry(
235
+ props.name,
236
+ props.bindings.services,
237
+ props.bindings.durable_objects,
238
+ props.local ? "local" : "remote"
239
+ );
240
+
241
+ const bundle = useEsbuild({
242
+ entry: props.entry,
243
+ destination: directory,
244
+ jsxFactory: props.jsxFactory,
245
+ rules: props.rules,
246
+ jsxFragment: props.jsxFragment,
247
+ serveAssetsFromWorker: Boolean(
248
+ props.assetPaths && !props.isWorkersSite && props.local
249
+ ),
250
+ tsconfig: props.tsconfig,
251
+ minify: props.minify,
252
+ nodeCompat: props.nodeCompat,
253
+ define: props.define,
254
+ noBundle: props.noBundle,
255
+ assets: props.assetsConfig,
256
+ workerDefinitions,
257
+ services: props.bindings.services,
258
+ durableObjects: props.bindings.durable_objects || { bindings: [] },
259
+ firstPartyWorkerDevFacade: props.firstPartyWorker,
260
+ });
261
+
262
+ return props.local ? (
263
+ <Local
264
+ name={props.name}
265
+ bundle={bundle}
266
+ format={props.entry.format}
267
+ compatibilityDate={props.compatibilityDate}
268
+ compatibilityFlags={props.compatibilityFlags}
269
+ usageModel={props.usageModel}
270
+ bindings={props.bindings}
271
+ workerDefinitions={workerDefinitions}
272
+ assetPaths={props.assetPaths}
273
+ port={props.port}
274
+ ip={props.ip}
275
+ rules={props.rules}
276
+ inspectorPort={props.inspectorPort}
277
+ enableLocalPersistence={props.enableLocalPersistence}
278
+ liveReload={props.liveReload}
279
+ crons={props.crons}
280
+ localProtocol={props.localProtocol}
281
+ localUpstream={props.localUpstream}
282
+ logLevel={props.logLevel}
283
+ logPrefix={props.logPrefix}
284
+ inspect={props.inspect}
285
+ onReady={props.onReady}
286
+ enablePagesAssetsServiceBinding={props.enablePagesAssetsServiceBinding}
287
+ />
288
+ ) : (
289
+ <Remote
290
+ name={props.name}
291
+ bundle={bundle}
292
+ format={props.entry.format}
293
+ accountId={props.accountId}
294
+ bindings={props.bindings}
295
+ assetPaths={props.assetPaths}
296
+ isWorkersSite={props.isWorkersSite}
297
+ port={props.port}
298
+ ip={props.ip}
299
+ localProtocol={props.localProtocol}
300
+ inspectorPort={props.inspectorPort}
301
+ // TODO: @threepointone #1167
302
+ // liveReload={props.liveReload}
303
+ inspect={props.inspect}
304
+ compatibilityDate={props.compatibilityDate}
305
+ compatibilityFlags={props.compatibilityFlags}
306
+ usageModel={props.usageModel}
307
+ env={props.env}
308
+ legacyEnv={props.legacyEnv}
309
+ zone={props.zone}
310
+ host={props.host}
311
+ routes={props.routes}
312
+ onReady={props.onReady}
313
+ sourceMapPath={bundle?.sourceMapPath}
314
+ sendMetrics={props.sendMetrics}
315
+ />
316
+ );
155
317
  }
156
318
 
157
319
  export interface DirectorySyncResult {
158
- name: string;
159
- removeCallback: () => void;
320
+ name: string;
321
+ removeCallback: () => void;
160
322
  }
161
323
 
162
324
  function useTmpDir(): string | undefined {
163
- const [directory, setDirectory] = useState<DirectorySyncResult>();
164
- const handleError = useErrorHandler();
165
- useEffect(() => {
166
- let dir: DirectorySyncResult | undefined;
167
- try {
168
- dir = tmp.dirSync({ unsafeCleanup: true });
169
- setDirectory(dir);
170
- return;
171
- } catch (err) {
172
- console.error("failed to create tmp dir");
173
- handleError(err);
174
- }
175
- return () => {
176
- dir?.removeCallback();
177
- };
178
- }, [handleError]);
179
- return directory?.name;
325
+ const [directory, setDirectory] = useState<DirectorySyncResult>();
326
+ const handleError = useErrorHandler();
327
+ useEffect(() => {
328
+ let dir: DirectorySyncResult | undefined;
329
+ try {
330
+ dir = tmp.dirSync({ unsafeCleanup: true });
331
+ setDirectory(dir);
332
+ return;
333
+ } catch (err) {
334
+ logger.error(
335
+ "Failed to create temporary directory to store built files."
336
+ );
337
+ handleError(err);
338
+ }
339
+ return () => {
340
+ dir?.removeCallback();
341
+ };
342
+ }, [handleError]);
343
+ return directory?.name;
180
344
  }
181
345
 
182
- function useCustomBuild(
183
- expectedEntry: Entry,
184
- build: {
185
- command?: undefined | string;
186
- cwd?: undefined | string;
187
- watch_dir?: undefined | string;
188
- }
189
- ): void {
190
- useEffect(() => {
191
- if (!build.command) return;
192
- let watcher: ReturnType<typeof watch> | undefined;
193
- if (build.watch_dir) {
194
- watcher = watch(build.watch_dir, {
195
- persistent: true,
196
- ignoreInitial: true,
197
- }).on("all", (_event, filePath) => {
198
- //TODO: we should buffer requests to the proxy until this completes
199
- console.log(`The file ${filePath} changed, restarting build...`);
200
- runCustomBuild(expectedEntry.file, build).catch((err) => {
201
- console.error("Custom build failed:", err);
202
- });
203
- });
204
- }
205
-
206
- return () => {
207
- watcher?.close();
208
- };
209
- }, [build, expectedEntry.file]);
346
+ function useCustomBuild(expectedEntry: Entry, build: Config["build"]): void {
347
+ useEffect(() => {
348
+ if (!build.command) return;
349
+ let watcher: ReturnType<typeof watch> | undefined;
350
+ if (build.watch_dir) {
351
+ watcher = watch(build.watch_dir, {
352
+ persistent: true,
353
+ ignoreInitial: true,
354
+ }).on("all", (_event, filePath) => {
355
+ const relativeFile =
356
+ path.relative(expectedEntry.directory, expectedEntry.file) || ".";
357
+ //TODO: we should buffer requests to the proxy until this completes
358
+ logger.log(`The file ${filePath} changed, restarting build...`);
359
+ runCustomBuild(expectedEntry.file, relativeFile, build).catch((err) => {
360
+ logger.error("Custom build failed:", err);
361
+ });
362
+ });
363
+ }
364
+
365
+ return () => {
366
+ watcher?.close();
367
+ };
368
+ }, [build, expectedEntry]);
210
369
  }
211
370
 
212
371
  function sleep(period: number) {
213
- return new Promise((resolve) => setTimeout(resolve, period));
372
+ return new Promise((resolve) => setTimeout(resolve, period));
214
373
  }
215
374
  const SLEEP_DURATION = 2000;
216
375
  // really need a first class api for this
217
376
  const hostNameRegex = /userHostname="(.*)"/g;
218
377
  async function findTunnelHostname() {
219
- let hostName: string | undefined;
220
- while (!hostName) {
221
- try {
222
- const resp = await fetch("http://localhost:8789/metrics");
223
- const data = await resp.text();
224
- const matches = Array.from(data.matchAll(hostNameRegex));
225
- hostName = matches[0][1];
226
- } catch (err) {
227
- await sleep(SLEEP_DURATION);
228
- }
229
- }
230
- return hostName;
378
+ let hostName: string | undefined;
379
+ while (!hostName) {
380
+ try {
381
+ const resp = await fetch("http://localhost:8789/metrics");
382
+ const data = await resp.text();
383
+ const matches = Array.from(data.matchAll(hostNameRegex));
384
+ hostName = matches[0][1];
385
+ } catch (err) {
386
+ await sleep(SLEEP_DURATION);
387
+ }
388
+ }
389
+ return hostName;
231
390
  }
232
391
 
233
392
  /**
@@ -235,130 +394,152 @@ async function findTunnelHostname() {
235
394
  * We've disabled this for now until we figure out a better user experience.
236
395
  */
237
396
  function useTunnel(toggle: boolean) {
238
- const tunnel = useRef<ReturnType<typeof spawn>>();
239
- const removeSignalExitListener = useRef<() => void>();
240
- // TODO: test if cloudflared is available, if not
241
- // point them to a url where they can get docs to install it
242
- useEffect(() => {
243
- async function startTunnel() {
244
- if (toggle) {
245
- try {
246
- await commandExists("cloudflared");
247
- } catch (e) {
248
- console.error(
249
- "To share your worker on the Internet, please install `cloudflared` from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation"
250
- );
251
- return;
252
- }
253
- console.log("⎔ Starting a tunnel...");
254
- tunnel.current = spawn("cloudflared", [
255
- "tunnel",
256
- "--url",
257
- "http://localhost:8787",
258
- "--metrics",
259
- "localhost:8789",
260
- ]);
261
-
262
- tunnel.current.on("close", (code) => {
263
- if (code) {
264
- console.log(`Tunnel process exited with code ${code}`);
265
- }
266
- });
267
-
268
- removeSignalExitListener.current = onExit((_code, _signal) => {
269
- console.log("⎔ Shutting down local tunnel.");
270
- tunnel.current?.kill();
271
- tunnel.current = undefined;
272
- });
273
-
274
- const hostName = await findTunnelHostname();
275
- await clipboardy.write(hostName);
276
- console.log(`⬣ Sharing at ${hostName}, copied to clipboard.`);
277
- }
278
- }
279
-
280
- startTunnel().catch(async (err) => {
281
- console.error("tunnel:", err);
282
- await reportError(err);
283
- });
284
-
285
- return () => {
286
- if (tunnel.current) {
287
- console.log("⎔ Shutting down tunnel.");
288
- tunnel.current?.kill();
289
- tunnel.current = undefined;
290
- removeSignalExitListener.current && removeSignalExitListener.current();
291
- removeSignalExitListener.current = undefined;
292
- }
293
- };
294
- }, [toggle]);
397
+ const tunnel = useRef<ReturnType<typeof spawn>>();
398
+ const removeSignalExitListener = useRef<() => void>();
399
+ // TODO: test if cloudflared is available, if not
400
+ // point them to a url where they can get docs to install it
401
+ useEffect(() => {
402
+ async function startTunnel() {
403
+ if (toggle) {
404
+ try {
405
+ await commandExists("cloudflared");
406
+ } catch (e) {
407
+ logger.warn(
408
+ "To share your worker on the Internet, please install `cloudflared` from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation"
409
+ );
410
+ return;
411
+ }
412
+ logger.log("⎔ Starting a tunnel...");
413
+ tunnel.current = spawn("cloudflared", [
414
+ "tunnel",
415
+ "--url",
416
+ "http://localhost:8787",
417
+ "--metrics",
418
+ "localhost:8789",
419
+ ]);
420
+
421
+ tunnel.current.on("close", (code) => {
422
+ if (code) {
423
+ logger.log(`Tunnel process exited with code ${code}`);
424
+ }
425
+ });
426
+
427
+ removeSignalExitListener.current = onExit((_code, _signal) => {
428
+ logger.log("⎔ Shutting down local tunnel.");
429
+ tunnel.current?.kill();
430
+ tunnel.current = undefined;
431
+ });
432
+
433
+ const hostName = await findTunnelHostname();
434
+ await clipboardy.write(hostName);
435
+ logger.log(`⬣ Sharing at ${hostName}, copied to clipboard.`);
436
+ }
437
+ }
438
+
439
+ startTunnel().catch(async (err) => {
440
+ logger.error("tunnel:", err);
441
+ });
442
+
443
+ return () => {
444
+ if (tunnel.current) {
445
+ logger.log("⎔ Shutting down tunnel.");
446
+ tunnel.current?.kill();
447
+ tunnel.current = undefined;
448
+ removeSignalExitListener.current && removeSignalExitListener.current();
449
+ removeSignalExitListener.current = undefined;
450
+ }
451
+ };
452
+ }, [toggle]);
295
453
  }
296
454
 
297
455
  type useHotkeysInitialState = {
298
- local: boolean;
299
- tunnel: boolean;
456
+ local: boolean;
457
+ tunnel: boolean;
300
458
  };
301
- function useHotkeys(
302
- initial: useHotkeysInitialState,
303
- port: number,
304
- ip: string,
305
- inspectorPort: number,
306
- localProtocol: "http" | "https"
307
- ) {
308
- // UGH, we should put port in context instead
309
- const [toggles, setToggles] = useState(initial);
310
- useInput(
311
- async (
312
- input,
313
- // eslint-disable-next-line unused-imports/no-unused-vars
314
- key
315
- ) => {
316
- switch (input.toLowerCase()) {
317
- // open browser
318
- case "b": {
319
- await openInBrowser(`${localProtocol}://${ip}:${port}`);
320
- break;
321
- }
322
- // toggle inspector
323
- case "d": {
324
- await openInBrowser(
325
- `https://built-devtools.pages.dev/js_app?experiments=true&v8only=true&ws=localhost:${inspectorPort}/ws`,
326
- { forceChromium: true }
327
- );
328
- break;
329
- }
330
- // toggle local
331
- case "l":
332
- setToggles((previousToggles) => ({
333
- ...previousToggles,
334
- local: !previousToggles.local,
335
- }));
336
- break;
337
- // shut down
338
- case "q":
339
- case "x":
340
- process.exit(0);
341
- break;
342
- default:
343
- // nothing?
344
- break;
345
- }
346
- }
347
- );
348
- return toggles;
459
+ function useHotkeys(props: {
460
+ initial: useHotkeysInitialState;
461
+ port: number;
462
+ ip: string;
463
+ inspectorPort: number;
464
+ inspect: boolean;
465
+ localProtocol: "http" | "https";
466
+ forceLocal: boolean | undefined;
467
+ }) {
468
+ const {
469
+ initial,
470
+ port,
471
+ ip,
472
+ inspectorPort,
473
+ inspect,
474
+ localProtocol,
475
+ forceLocal,
476
+ } = props;
477
+ // UGH, we should put port in context instead
478
+ const [toggles, setToggles] = useState(initial);
479
+ const { exit } = useApp();
480
+ useInput(
481
+ async (
482
+ input,
483
+ // eslint-disable-next-line unused-imports/no-unused-vars
484
+ key
485
+ ) => {
486
+ switch (input.toLowerCase()) {
487
+ // clear console
488
+ case "c":
489
+ console.clear();
490
+ // This console.log causes Ink to re-render the `DevSession` component.
491
+ // Couldn't find a better way to tell it to do so...
492
+ console.log();
493
+ break;
494
+ // open browser
495
+ case "b": {
496
+ if (ip === "0.0.0.0") {
497
+ await openInBrowser(`${localProtocol}://127.0.0.1:${port}`);
498
+ return;
499
+ }
500
+ await openInBrowser(`${localProtocol}://${ip}:${port}`);
501
+ break;
502
+ }
503
+ // toggle inspector
504
+ case "d": {
505
+ if (inspect) {
506
+ await openInspector(inspectorPort);
507
+ }
508
+ break;
509
+ }
510
+ // toggle local
511
+ case "l":
512
+ if (forceLocal) return;
513
+ setToggles((previousToggles) => ({
514
+ ...previousToggles,
515
+ local: !previousToggles.local,
516
+ }));
517
+ break;
518
+ // shut down
519
+ case "q":
520
+ case "x":
521
+ exit();
522
+ break;
523
+ default:
524
+ // nothing?
525
+ break;
526
+ }
527
+ }
528
+ );
529
+ return toggles;
349
530
  }
350
531
 
351
532
  function ErrorFallback(props: { error: Error }) {
352
- const { exit } = useApp();
353
- useEffect(() => exit(props.error));
354
- return (
355
- <>
356
- <Text>Something went wrong:</Text>
357
- <Text>{props.error.stack}</Text>
358
- </>
359
- );
533
+ const { exit } = useApp();
534
+ useEffect(() => exit(props.error));
535
+ return (
536
+ <>
537
+ <Text>Something went wrong:</Text>
538
+ <Text>{props.error.stack}</Text>
539
+ </>
540
+ );
360
541
  }
361
542
 
362
543
  export default withErrorBoundary(DevImplementation, {
363
- FallbackComponent: ErrorFallback,
544
+ FallbackComponent: ErrorFallback,
364
545
  });