wrangler 0.0.0-ece06ea → 0.0.0-eebdb6c

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 (39) hide show
  1. package/package.json +22 -15
  2. package/pages/functions/buildWorker.ts +57 -0
  3. package/pages/functions/filepath-routing.test.ts +32 -0
  4. package/pages/functions/filepath-routing.ts +225 -0
  5. package/pages/functions/identifiers.ts +78 -0
  6. package/pages/functions/routes.ts +158 -0
  7. package/pages/functions/template-worker.ts +135 -0
  8. package/src/__tests__/index.test.ts +254 -120
  9. package/src/__tests__/jest.setup.ts +22 -0
  10. package/src/__tests__/kv.test.ts +1098 -0
  11. package/src/__tests__/mock-cfetch.ts +171 -0
  12. package/src/__tests__/mock-dialogs.ts +65 -0
  13. package/src/__tests__/run-in-tmp.ts +19 -0
  14. package/src/__tests__/run-wrangler.ts +32 -0
  15. package/src/api/form_data.ts +70 -97
  16. package/src/api/inspect.ts +0 -11
  17. package/src/api/preview.ts +29 -24
  18. package/src/api/worker.ts +34 -40
  19. package/src/cfetch/index.ts +103 -0
  20. package/src/cfetch/internal.ts +69 -0
  21. package/src/cli.ts +6 -7
  22. package/src/config.ts +14 -7
  23. package/src/dev.tsx +274 -131
  24. package/src/dialogs.tsx +4 -12
  25. package/src/index.tsx +304 -245
  26. package/src/kv.tsx +101 -45
  27. package/src/module-collection.ts +64 -0
  28. package/src/pages.tsx +681 -104
  29. package/src/proxy.ts +103 -0
  30. package/src/publish.ts +54 -58
  31. package/src/sites.tsx +5 -6
  32. package/src/tail.tsx +5 -3
  33. package/src/user.tsx +9 -16
  34. package/wrangler-dist/cli.js +36633 -49770
  35. package/wrangler-dist/cli.js.map +3 -3
  36. package/src/__tests__/fixtures/init/.gitkeep +0 -0
  37. package/src/__tests__/mock-cfetch.js +0 -46
  38. package/src/cfetch.ts +0 -72
  39. package/src/util/fetch.ts +0 -74
package/src/proxy.ts ADDED
@@ -0,0 +1,103 @@
1
+ import { connect } from "node:http2";
2
+ import { createServer } from "node:http";
3
+ import type { Server, IncomingHttpHeaders, RequestListener } from "node:http";
4
+ import WebSocket from "faye-websocket";
5
+ import serveStatic from "serve-static";
6
+
7
+ export interface HttpProxyInit {
8
+ host: string;
9
+ assetPath?: string | null;
10
+ onRequest?: (headers: IncomingHttpHeaders) => void;
11
+ onResponse?: (headers: IncomingHttpHeaders) => void;
12
+ }
13
+
14
+ /**
15
+ * Creates a HTTP/1 proxy that sends requests over HTTP/2.
16
+ */
17
+ export function createHttpProxy(init: HttpProxyInit): Server {
18
+ const { host, assetPath, onRequest = () => {}, onResponse = () => {} } = init;
19
+ const remote = connect(`https://${host}`);
20
+ const local = createServer();
21
+ // HTTP/2 -> HTTP/2
22
+ local.on("stream", (stream, headers: IncomingHttpHeaders) => {
23
+ onRequest(headers);
24
+ headers[":authority"] = host;
25
+ const request = stream.pipe(remote.request(headers));
26
+ request.on("response", (responseHeaders: IncomingHttpHeaders) => {
27
+ onResponse(responseHeaders);
28
+ stream.respond(responseHeaders);
29
+ request.pipe(stream, { end: true });
30
+ });
31
+ });
32
+ // HTTP/1 -> HTTP/2
33
+ const handleRequest: RequestListener = (message, response) => {
34
+ const { httpVersionMajor, headers, method, url } = message;
35
+ if (httpVersionMajor >= 2) {
36
+ return; // Already handled by the "stream" event.
37
+ }
38
+ onRequest(headers);
39
+ headers[":method"] = method;
40
+ headers[":path"] = url;
41
+ headers[":authority"] = host;
42
+ headers[":scheme"] = "https";
43
+ for (const name of Object.keys(headers)) {
44
+ if (HTTP1_HEADERS.has(name.toLowerCase())) {
45
+ delete headers[name];
46
+ }
47
+ }
48
+ const request = message.pipe(remote.request(headers));
49
+ request.on("response", (responseHeaders) => {
50
+ const status = responseHeaders[":status"];
51
+ onResponse(responseHeaders);
52
+ for (const name of Object.keys(responseHeaders)) {
53
+ if (name.startsWith(":")) {
54
+ delete responseHeaders[name];
55
+ }
56
+ }
57
+ response.writeHead(status, responseHeaders);
58
+ request.pipe(response, { end: true });
59
+ });
60
+ };
61
+ // If an asset path is defined, check the file system
62
+ // for a file first and serve if it exists.
63
+ if (assetPath) {
64
+ const handleAsset = serveStatic(assetPath, {
65
+ cacheControl: false,
66
+ });
67
+ local.on("request", (request, response) => {
68
+ handleAsset(request, response, () => {
69
+ handleRequest(request, response);
70
+ });
71
+ });
72
+ } else {
73
+ local.on("request", handleRequest);
74
+ }
75
+ // HTTP/1 -> WebSocket (over HTTP/1)
76
+ local.on("upgrade", (message, socket, body) => {
77
+ const { headers, url } = message;
78
+ onRequest(headers);
79
+ headers["host"] = host;
80
+ const localWebsocket = new WebSocket(message, socket, body);
81
+ // TODO(soon): Custom WebSocket protocol is not working?
82
+ const remoteWebsocketClient = new WebSocket.Client(
83
+ `wss://${host}${url}`,
84
+ [],
85
+ { headers }
86
+ );
87
+ localWebsocket.pipe(remoteWebsocketClient).pipe(localWebsocket);
88
+ });
89
+ remote.on("close", () => {
90
+ local.close();
91
+ });
92
+ return local;
93
+ }
94
+
95
+ const HTTP1_HEADERS = new Set([
96
+ "host",
97
+ "connection",
98
+ "upgrade",
99
+ "keep-alive",
100
+ "proxy-connection",
101
+ "transfer-encoding",
102
+ "http2-settings",
103
+ ]);
package/src/publish.ts CHANGED
@@ -1,15 +1,15 @@
1
- import type { CfWorkerInit } from "./api/worker";
2
- import { toFormData } from "./api/form_data";
1
+ import assert from "node:assert";
2
+ import path from "node:path";
3
+ import { readFile } from "node:fs/promises";
3
4
  import esbuild from "esbuild";
5
+ import { execa } from "execa";
4
6
  import tmp from "tmp-promise";
7
+ import type { CfWorkerInit } from "./api/worker";
8
+ import { toFormData } from "./api/form_data";
9
+ import { fetchResult } from "./cfetch";
5
10
  import type { Config } from "./config";
6
- import path from "path";
7
- import { readFile } from "fs/promises";
8
- import cfetch from "./cfetch";
9
- import assert from "node:assert";
11
+ import makeModuleCollector from "./module-collection";
10
12
  import { syncAssets } from "./sites";
11
- import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";
12
- import NodeGlobalsPolyfills from "@esbuild-plugins/node-globals-polyfill";
13
13
 
14
14
  type CfScriptFormat = void | "modules" | "service-worker";
15
15
 
@@ -26,7 +26,6 @@ type Props = {
26
26
  legacyEnv?: boolean;
27
27
  jsxFactory: void | string;
28
28
  jsxFragment: void | string;
29
- polyfillNode: void | boolean;
30
29
  };
31
30
 
32
31
  function sleep(ms: number) {
@@ -78,6 +77,18 @@ export default async function publish(props: Props): Promise<void> {
78
77
 
79
78
  const destination = await tmp.dir({ unsafeCleanup: true });
80
79
 
80
+ if (props.config.build?.command) {
81
+ // TODO: add a deprecation message here?
82
+ console.log("running:", props.config.build.command);
83
+ const buildCommandPieces = props.config.build.command.split(" ");
84
+ await execa(buildCommandPieces[0], buildCommandPieces.slice(1), {
85
+ stdout: "inherit",
86
+ stderr: "inherit",
87
+ ...(props.config.build?.cwd && { cwd: props.config.build.cwd }),
88
+ });
89
+ }
90
+
91
+ const moduleCollector = makeModuleCollector();
81
92
  const result = await esbuild.build({
82
93
  ...(props.public
83
94
  ? {
@@ -94,24 +105,17 @@ export default async function publish(props: Props): Promise<void> {
94
105
  }
95
106
  : { entryPoints: [file] }),
96
107
  bundle: true,
97
- define: {
98
- ...((props.polyfillNode ?? config.polyfill_node) && {
99
- global: "globalThis",
100
- }),
101
- },
102
108
  nodePaths: props.public ? [path.join(__dirname, "../vendor")] : undefined,
103
109
  outdir: destination.path,
104
110
  external: ["__STATIC_CONTENT_MANIFEST"],
105
111
  format: "esm",
106
- plugins:
107
- props.polyfillNode ?? config.polyfill_node
108
- ? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
109
- : undefined,
110
112
  sourcemap: true,
111
113
  metafile: true,
114
+ conditions: ["worker", "browser"],
112
115
  loader: {
113
116
  ".js": "jsx",
114
117
  },
118
+ plugins: [moduleCollector.plugin],
115
119
  ...(jsxFactory && { jsxFactory }),
116
120
  ...(jsxFragment && { jsxFragment }),
117
121
  });
@@ -145,16 +149,16 @@ export default async function publish(props: Props): Promise<void> {
145
149
  }
146
150
 
147
151
  const content = await readFile(chunks[0], { encoding: "utf-8" });
148
- destination.cleanup();
152
+ await destination.cleanup();
149
153
 
150
154
  // if config.migrations
151
155
  // get current migration tag
152
156
  let migrations;
153
157
  if ("migrations" in config) {
154
- const scripts = await cfetch<{ id: string; migration_tag: string }[]>(
158
+ const scripts = await fetchResult<{ id: string; migration_tag: string }[]>(
155
159
  `/accounts/${accountId}/workers/scripts`
156
160
  );
157
- const script = scripts.find((script) => script.id === scriptName);
161
+ const script = scripts.find(({ id }) => id === scriptName);
158
162
  if (script?.migration_tag) {
159
163
  // was already published once
160
164
  const foundIndex = config.migrations.findIndex(
@@ -162,7 +166,7 @@ export default async function publish(props: Props): Promise<void> {
162
166
  );
163
167
  if (foundIndex === -1) {
164
168
  console.warn(
165
- `The published script ${scriptName} has a migration tag "${script.migration_tag}, which was not found in wrangler.toml. You may have already delated it. Applying all available migrations to the script...`
169
+ `The published script ${scriptName} has a migration tag "${script.migration_tag}, which was not found in wrangler.toml. You may have already deleted it. Applying all available migrations to the script...`
166
170
  );
167
171
  migrations = {
168
172
  old_tag: script.migration_tag,
@@ -197,6 +201,16 @@ export default async function publish(props: Props): Promise<void> {
197
201
  : { manifest: undefined, namespace: undefined };
198
202
 
199
203
  const envRootObj = props.env ? config.env[props.env] || {} : config;
204
+ const bindings: CfWorkerInit["bindings"] = {
205
+ kv_namespaces: envRootObj.kv_namespaces?.concat(
206
+ assets.namespace
207
+ ? { binding: "__STATIC_CONTENT", id: assets.namespace }
208
+ : []
209
+ ),
210
+ vars: envRootObj.vars,
211
+ durable_objects: envRootObj.durable_objects,
212
+ services: envRootObj.experimental_services,
213
+ };
200
214
 
201
215
  const worker: CfWorkerInit = {
202
216
  name: scriptName,
@@ -205,35 +219,17 @@ export default async function publish(props: Props): Promise<void> {
205
219
  content: content,
206
220
  type: bundle.type === "esm" ? "esm" : "commonjs",
207
221
  },
208
- variables: {
209
- ...(envRootObj?.vars || {}),
210
- ...(envRootObj?.kv_namespaces || []).reduce(
211
- (obj, { binding, preview_id: _preview_id, id }) => {
212
- return { ...obj, [binding]: { namespaceId: id } };
213
- },
214
- {}
215
- ),
216
- ...(envRootObj?.durable_objects?.bindings || []).reduce(
217
- (obj, { name, class_name, script_name }) => {
218
- return {
219
- ...obj,
220
- [name]: { class_name, ...(script_name && { script_name }) },
221
- };
222
- },
223
- {}
224
- ),
225
- ...(assets.namespace
226
- ? { __STATIC_CONTENT: { namespaceId: assets.namespace } }
227
- : {}),
228
- },
222
+ bindings,
229
223
  ...(migrations && { migrations }),
230
- modules: assets.manifest
231
- ? [].concat({
232
- name: "__STATIC_CONTENT_MANIFEST",
233
- content: JSON.stringify(assets.manifest),
234
- type: "text",
235
- })
236
- : [],
224
+ modules: moduleCollector.modules.concat(
225
+ assets.manifest
226
+ ? {
227
+ name: "__STATIC_CONTENT_MANIFEST",
228
+ content: JSON.stringify(assets.manifest),
229
+ type: "text",
230
+ }
231
+ : []
232
+ ),
237
233
  compatibility_date: config.compatibility_date,
238
234
  compatibility_flags: config.compatibility_flags,
239
235
  usage_model: config.usage_model,
@@ -250,8 +246,8 @@ export default async function publish(props: Props): Promise<void> {
250
246
  ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${envName}`
251
247
  : `/accounts/${accountId}/workers/scripts/${scriptName}`;
252
248
 
253
- // Upload the script so it has time to propogate.
254
- const { available_on_subdomain } = await cfetch(
249
+ // Upload the script so it has time to propagate.
250
+ const { available_on_subdomain } = await fetchResult(
255
251
  `${workerUrl}?available_on_subdomain=true`,
256
252
  {
257
253
  method: "PUT",
@@ -265,7 +261,7 @@ export default async function publish(props: Props): Promise<void> {
265
261
  const deployments: Promise<string[]>[] = [];
266
262
 
267
263
  const userSubdomain = (
268
- await cfetch<{ subdomain: string }>(
264
+ await fetchResult<{ subdomain: string }>(
269
265
  `/accounts/${accountId}/workers/subdomain`
270
266
  )
271
267
  ).subdomain;
@@ -279,7 +275,7 @@ export default async function publish(props: Props): Promise<void> {
279
275
  // TODO: Make this configurable.
280
276
  if (!available_on_subdomain) {
281
277
  deployments.push(
282
- cfetch(`${workerUrl}/subdomain`, {
278
+ fetchResult(`${workerUrl}/subdomain`, {
283
279
  method: "POST",
284
280
  body: JSON.stringify({ enabled: true }),
285
281
  headers: {
@@ -291,8 +287,8 @@ export default async function publish(props: Props): Promise<void> {
291
287
  // This is to prevent an issue where a negative cache-hit
292
288
  // causes the subdomain to be unavailable for 30 seconds.
293
289
  // This is a temporary measure until we fix this on the edge.
294
- .then((url) => {
295
- sleep(3000);
290
+ .then(async (url) => {
291
+ await sleep(3000);
296
292
  return url;
297
293
  })
298
294
  );
@@ -303,7 +299,7 @@ export default async function publish(props: Props): Promise<void> {
303
299
  // Update routing table for the script.
304
300
  if (routes && routes.length) {
305
301
  deployments.push(
306
- cfetch(`${workerUrl}/routes`, {
302
+ fetchResult(`${workerUrl}/routes`, {
307
303
  // TODO: PATCH will not delete previous routes on this script,
308
304
  // whereas PUT will. We need to decide on the default behaviour
309
305
  // and how to configure it.
@@ -328,7 +324,7 @@ export default async function publish(props: Props): Promise<void> {
328
324
  // TODO: rename this to `schedules`?
329
325
  if (triggers && triggers.length) {
330
326
  deployments.push(
331
- cfetch(`${workerUrl}/schedules`, {
327
+ fetchResult(`${workerUrl}/schedules`, {
332
328
  // TODO: Unlike routes, this endpoint does not support PATCH.
333
329
  // So technically, this will override any previous schedules.
334
330
  // We should change the endpoint to support PATCH.
package/src/sites.tsx CHANGED
@@ -1,11 +1,10 @@
1
- import { readdir, readFile } from "node:fs/promises";
1
+ import crypto from "node:crypto";
2
2
  import { createReadStream } from "node:fs";
3
- import cfetch from "./cfetch";
3
+ import * as path from "node:path";
4
+ import { readdir, readFile } from "node:fs/promises";
5
+ import { fetchResult } from "./cfetch";
4
6
  import { listNamespaceKeys, listNamespaces, putBulkKeyValue } from "./kv";
5
7
 
6
- import * as path from "path";
7
- import crypto from "node:crypto";
8
-
9
8
  async function* getFilesInFolder(dirPath: string): AsyncIterable<string> {
10
9
  const files = await readdir(dirPath, { withFileTypes: true });
11
10
  for (const file of files) {
@@ -54,7 +53,7 @@ async function createKVNamespaceIfNotAlreadyExisting(
54
53
 
55
54
  // else we make the namespace
56
55
  // TODO: use an export from ./kv
57
- const json = await cfetch<{ id: string }>(
56
+ const json = await fetchResult<{ id: string }>(
58
57
  `/accounts/${accountId}/storage/kv/namespaces`,
59
58
  {
60
59
  method: "POST",
package/src/tail.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import WebSocket from "ws";
2
- import cfetch from "./cfetch";
3
2
  import { version as packageVersion } from "../package.json";
3
+ import { fetchResult } from "./cfetch";
4
4
 
5
5
  export type TailApiResponse = {
6
6
  id: string;
@@ -27,7 +27,9 @@ async function createTailButDontConnect(
27
27
  ): Promise<TailApiResponse> {
28
28
  const createTailUrl = makeCreateTailUrl(accountId, workerName);
29
29
  /// https://api.cloudflare.com/#worker-tail-logs-start-tail
30
- return await cfetch<TailApiResponse>(createTailUrl, { method: "POST" });
30
+ return await fetchResult<TailApiResponse>(createTailUrl, {
31
+ method: "POST",
32
+ });
31
33
  }
32
34
 
33
35
  export async function createTail(
@@ -48,7 +50,7 @@ export async function createTail(
48
50
 
49
51
  // deletes the tail
50
52
  async function deleteTail() {
51
- await cfetch(deleteUrl, { method: "DELETE" });
53
+ await fetchResult(deleteUrl, { method: "DELETE" });
52
54
  }
53
55
 
54
56
  const tail = new WebSocket(websocketUrl, "trace-v1", {
package/src/user.tsx CHANGED
@@ -578,19 +578,16 @@ export async function getAuthURL(scopes?: string[]): Promise<string> {
578
578
  * Refresh an access token from the remote service.
579
579
  */
580
580
  async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
581
- const { refreshToken } = LocalState;
582
-
583
- if (!refreshToken) {
581
+ if (!LocalState.refreshToken) {
584
582
  console.warn("No refresh token is present.");
585
583
  }
586
584
 
587
- const url = TOKEN_URL;
588
585
  const body =
589
586
  `grant_type=refresh_token&` +
590
- `refresh_token=${refreshToken?.value}&` +
587
+ `refresh_token=${LocalState.refreshToken?.value}&` +
591
588
  `client_id=${CLIENT_ID}`;
592
589
 
593
- const response = await fetch(url, {
590
+ const response = await fetch(TOKEN_URL, {
594
591
  method: "POST",
595
592
  body,
596
593
  headers: {
@@ -617,10 +614,9 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
617
614
  LocalState.accessToken = accessToken;
618
615
 
619
616
  if (refresh_token) {
620
- const refreshToken: RefreshToken = {
617
+ LocalState.refreshToken = {
621
618
  value: refresh_token,
622
619
  };
623
- LocalState.refreshToken = refreshToken;
624
620
  }
625
621
 
626
622
  if (scope) {
@@ -666,7 +662,6 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
666
662
  console.warn("No authorization grant code is being passed.");
667
663
  }
668
664
 
669
- const url = TOKEN_URL;
670
665
  const body =
671
666
  `grant_type=authorization_code&` +
672
667
  `code=${encodeURIComponent(authorizationCode || "")}&` +
@@ -674,7 +669,7 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
674
669
  `client_id=${encodeURIComponent(CLIENT_ID)}&` +
675
670
  `code_verifier=${codeVerifier}`;
676
671
 
677
- const response = await fetch(url, {
672
+ const response = await fetch(TOKEN_URL, {
678
673
  method: "POST",
679
674
  body,
680
675
  headers: {
@@ -709,10 +704,9 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
709
704
  LocalState.accessToken = accessToken;
710
705
 
711
706
  if (refresh_token) {
712
- const refreshToken: RefreshToken = {
707
+ LocalState.refreshToken = {
713
708
  value: refresh_token,
714
709
  };
715
- LocalState.refreshToken = refreshToken;
716
710
  }
717
711
 
718
712
  if (scope) {
@@ -816,7 +810,7 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
816
810
 
817
811
  export async function login(props?: LoginProps): Promise<boolean> {
818
812
  const urlToOpen = await getAuthURL(props?.scopes);
819
- open(urlToOpen);
813
+ await open(urlToOpen);
820
814
  // TODO: log url only if on system where it's unreliable/unavailable
821
815
  // console.log(`💁 Opened ${urlToOpen}`);
822
816
  let server;
@@ -921,15 +915,14 @@ export async function refreshToken(): Promise<boolean> {
921
915
 
922
916
  export async function logout(): Promise<void> {
923
917
  throwIfNotInitialised();
924
- const { refreshToken } = LocalState;
925
- if (!refreshToken) {
918
+ if (!LocalState.refreshToken) {
926
919
  console.log("Not logged in, exiting...");
927
920
  return;
928
921
  }
929
922
  const body =
930
923
  `client_id=${encodeURIComponent(CLIENT_ID)}&` +
931
924
  `token_type_hint=refresh_token&` +
932
- `token=${encodeURIComponent(refreshToken?.value || "")}`;
925
+ `token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
933
926
 
934
927
  const response = await fetch(REVOKE_URL, {
935
928
  method: "POST",