wrangler 0.0.13 → 0.0.17
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.
- package/bin/wrangler.js +2 -2
- package/package.json +20 -11
- package/pages/functions/buildWorker.ts +1 -1
- package/pages/functions/filepath-routing.test.ts +112 -28
- package/pages/functions/filepath-routing.ts +44 -51
- package/pages/functions/routes.ts +11 -18
- package/pages/functions/template-worker.ts +3 -9
- package/src/__tests__/dev.test.tsx +42 -5
- package/src/__tests__/guess-worker-format.test.ts +66 -0
- package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
- package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
- package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
- package/src/__tests__/helpers/mock-account-id.ts +30 -0
- package/src/__tests__/helpers/mock-bin.ts +36 -0
- package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +43 -9
- package/src/__tests__/helpers/mock-console.ts +62 -0
- package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
- package/src/__tests__/helpers/mock-kv.ts +40 -0
- package/src/__tests__/helpers/mock-user.ts +27 -0
- package/src/__tests__/helpers/mock-web-socket.ts +37 -0
- package/src/__tests__/{run-in-tmp.ts → helpers/run-in-tmp.ts} +1 -1
- package/src/__tests__/helpers/run-wrangler.ts +16 -0
- package/src/__tests__/helpers/write-wrangler-toml.ts +20 -0
- package/src/__tests__/index.test.ts +418 -71
- package/src/__tests__/jest.setup.ts +30 -2
- package/src/__tests__/kv.test.ts +147 -252
- package/src/__tests__/logout.test.ts +50 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/publish.test.ts +1136 -291
- package/src/__tests__/r2.test.ts +206 -0
- package/src/__tests__/secret.test.ts +210 -0
- package/src/__tests__/sentry.test.ts +146 -0
- package/src/__tests__/tail.test.ts +246 -0
- package/src/__tests__/whoami.test.tsx +6 -47
- package/src/api/form_data.ts +75 -25
- package/src/api/preview.ts +2 -2
- package/src/api/worker.ts +34 -15
- package/src/bundle.ts +127 -0
- package/src/cfetch/index.ts +7 -15
- package/src/cfetch/internal.ts +41 -6
- package/src/cli.ts +10 -0
- package/src/config.ts +125 -95
- package/src/dev.tsx +300 -193
- package/src/dialogs.tsx +2 -2
- package/src/guess-worker-format.ts +68 -0
- package/src/index.tsx +578 -192
- package/src/inspect.ts +29 -10
- package/src/kv.tsx +23 -17
- package/src/module-collection.ts +32 -12
- package/src/open-in-browser.ts +13 -0
- package/src/package-manager.ts +120 -0
- package/src/pages.tsx +28 -23
- package/src/paths.ts +26 -0
- package/src/proxy.ts +88 -14
- package/src/publish.ts +260 -297
- package/src/r2.ts +50 -0
- package/src/reporting.ts +115 -0
- package/src/sites.tsx +28 -27
- package/src/tail.tsx +178 -9
- package/src/user.tsx +58 -44
- package/templates/new-worker.js +15 -0
- package/templates/new-worker.ts +15 -0
- package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
- package/wrangler-dist/cli.js +124315 -104677
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/mock-console.ts +0 -34
- package/src/__tests__/run-wrangler.ts +0 -8
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
|
|
2
|
+
import { setMockResponse } from "./helpers/mock-cfetch";
|
|
3
|
+
import { mockConsoleMethods } from "./helpers/mock-console";
|
|
4
|
+
import { WS } from "./helpers/mock-web-socket";
|
|
5
|
+
import { runInTempDir } from "./helpers/run-in-tmp";
|
|
6
|
+
import { runWrangler } from "./helpers/run-wrangler";
|
|
7
|
+
import type Websocket from "ws";
|
|
8
|
+
|
|
9
|
+
// change this if you're testing using debug mode
|
|
10
|
+
// (sends all logs regardless of filters)
|
|
11
|
+
const DEBUG = false;
|
|
12
|
+
|
|
13
|
+
describe("tail", () => {
|
|
14
|
+
runInTempDir();
|
|
15
|
+
mockAccountId();
|
|
16
|
+
mockApiToken();
|
|
17
|
+
|
|
18
|
+
const std = mockConsoleMethods();
|
|
19
|
+
const api = mockWebsocketApis();
|
|
20
|
+
|
|
21
|
+
/* API related functionality */
|
|
22
|
+
|
|
23
|
+
it("creates and then delete tails", async () => {
|
|
24
|
+
expect(api.requests.creation.count).toStrictEqual(0);
|
|
25
|
+
|
|
26
|
+
await runWrangler("tail test-worker");
|
|
27
|
+
|
|
28
|
+
await expect(api.ws.connected).resolves.toBeTruthy();
|
|
29
|
+
expect(api.requests.creation.count).toStrictEqual(1);
|
|
30
|
+
expect(api.requests.deletion.count).toStrictEqual(0);
|
|
31
|
+
|
|
32
|
+
api.ws.close();
|
|
33
|
+
expect(api.requests.deletion.count).toStrictEqual(1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/* filtering */
|
|
37
|
+
|
|
38
|
+
it("sends sampling rate filters", async () => {
|
|
39
|
+
const tooHigh = runWrangler("tail test-worker --sampling-rate 10");
|
|
40
|
+
await expect(tooHigh).rejects.toThrow();
|
|
41
|
+
|
|
42
|
+
const tooLow = runWrangler("tail test-worker --sampling-rate -5");
|
|
43
|
+
await expect(tooLow).rejects.toThrow();
|
|
44
|
+
|
|
45
|
+
await runWrangler("tail test-worker --sampling-rate 0.25");
|
|
46
|
+
await expect(api.ws.nextMessageJson()).resolves.toHaveProperty("filters", [
|
|
47
|
+
{ sampling_rate: 0.25 },
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("sends single status filters", async () => {
|
|
52
|
+
await runWrangler("tail test-worker --status error");
|
|
53
|
+
await expect(api.ws.nextMessageJson()).resolves.toHaveProperty("filters", [
|
|
54
|
+
{ outcome: ["exception", "exceededCpu", "unknown"] },
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("sends multiple status filters", async () => {
|
|
59
|
+
await runWrangler("tail test-worker --status error --status canceled");
|
|
60
|
+
await expect(api.ws.nextMessageJson()).resolves.toHaveProperty("filters", [
|
|
61
|
+
{ outcome: ["exception", "exceededCpu", "unknown", "canceled"] },
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("sends single HTTP method filters", async () => {
|
|
66
|
+
await runWrangler("tail test-worker --method POST");
|
|
67
|
+
await expect(api.ws.nextMessageJson()).resolves.toHaveProperty("filters", [
|
|
68
|
+
{ method: ["POST"] },
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("sends multiple HTTP method filters", async () => {
|
|
73
|
+
await runWrangler("tail test-worker --method POST --method GET");
|
|
74
|
+
await expect(api.ws.nextMessageJson()).resolves.toHaveProperty("filters", [
|
|
75
|
+
{ method: ["POST", "GET"] },
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("sends header filters without a query", async () => {
|
|
80
|
+
await runWrangler("tail test-worker --header X-CUSTOM-HEADER ");
|
|
81
|
+
await expect(api.ws.nextMessageJson()).resolves.toHaveProperty("filters", [
|
|
82
|
+
{ header: { key: "X-CUSTOM-HEADER" } },
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("sends header filters with a query", async () => {
|
|
87
|
+
await runWrangler("tail test-worker --header X-CUSTOM-HEADER:some-value ");
|
|
88
|
+
await expect(api.ws.nextMessageJson()).resolves.toHaveProperty("filters", [
|
|
89
|
+
{ header: { key: "X-CUSTOM-HEADER", query: "some-value" } },
|
|
90
|
+
]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("sends single IP filters", async () => {
|
|
94
|
+
const fakeIp = "192.0.2.1";
|
|
95
|
+
|
|
96
|
+
await runWrangler(`tail test-worker --ip ${fakeIp}`);
|
|
97
|
+
await expect(api.ws.nextMessageJson()).resolves.toHaveProperty("filters", [
|
|
98
|
+
{ client_ip: [fakeIp] },
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("sends multiple IP filters", async () => {
|
|
103
|
+
const fakeIp = "192.0.2.1";
|
|
104
|
+
|
|
105
|
+
await runWrangler(`tail test-worker --ip ${fakeIp} --ip self`);
|
|
106
|
+
await expect(api.ws.nextMessageJson()).resolves.toHaveProperty("filters", [
|
|
107
|
+
{ client_ip: [fakeIp, "self"] },
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("sends search filters", async () => {
|
|
112
|
+
const search = "filterMe";
|
|
113
|
+
|
|
114
|
+
await runWrangler(`tail test-worker --search ${search}`);
|
|
115
|
+
await expect(api.ws.nextMessageJson()).resolves.toHaveProperty("filters", [
|
|
116
|
+
{ query: search },
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("sends everything but the kitchen sink", async () => {
|
|
121
|
+
const sampling_rate = 0.69;
|
|
122
|
+
const status = ["ok", "error"];
|
|
123
|
+
const method = ["GET", "POST", "PUT"];
|
|
124
|
+
const header = "X-HELLO:world";
|
|
125
|
+
const client_ip = ["192.0.2.1", "self"];
|
|
126
|
+
const query = "onlyTheseMessagesPlease";
|
|
127
|
+
|
|
128
|
+
const cliFilters =
|
|
129
|
+
`--sampling-rate ${sampling_rate} ` +
|
|
130
|
+
status.map((s) => `--status ${s} `).join("") +
|
|
131
|
+
method.map((m) => `--method ${m} `).join("") +
|
|
132
|
+
`--header ${header} ` +
|
|
133
|
+
client_ip.map((c) => `--ip ${c} `).join("") +
|
|
134
|
+
`--search ${query}`;
|
|
135
|
+
|
|
136
|
+
const expectedWebsocketMessage = {
|
|
137
|
+
filters: [
|
|
138
|
+
{ sampling_rate },
|
|
139
|
+
{ outcome: ["ok", "exception", "exceededCpu", "unknown"] },
|
|
140
|
+
{ method },
|
|
141
|
+
{ header: { key: "X-HELLO", query: "world" } },
|
|
142
|
+
{ client_ip },
|
|
143
|
+
{ query },
|
|
144
|
+
],
|
|
145
|
+
debug: DEBUG,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
await runWrangler(`tail test-worker ${cliFilters}`);
|
|
149
|
+
await expect(api.ws.nextMessageJson()).resolves.toEqual(
|
|
150
|
+
expectedWebsocketMessage
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/* Basic logging */
|
|
155
|
+
|
|
156
|
+
it("logs incoming messages", async () => {
|
|
157
|
+
await runWrangler("tail test-worker");
|
|
158
|
+
const greeting = serialize({ hello: "world!" });
|
|
159
|
+
api.ws.send(greeting);
|
|
160
|
+
expect(std.out).toMatch(deserializeToJson(greeting));
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/* helpers */
|
|
165
|
+
|
|
166
|
+
function serialize(message: unknown): Websocket.RawData {
|
|
167
|
+
return Buffer.from(JSON.stringify(message), "utf-8");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function deserializeToJson(message: Websocket.RawData): string {
|
|
171
|
+
return JSON.stringify(JSON.parse(message.toString()), null, 2);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
type RequestCounter = {
|
|
175
|
+
count: number;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
function mockCreateTailRequest(websocketURL: string): RequestCounter {
|
|
179
|
+
const requests = { count: 0 };
|
|
180
|
+
setMockResponse(
|
|
181
|
+
"/accounts/:accountId/workers/scripts/:worker/tails",
|
|
182
|
+
"POST",
|
|
183
|
+
() => {
|
|
184
|
+
requests.count++;
|
|
185
|
+
return {
|
|
186
|
+
id: "tail-id",
|
|
187
|
+
url: websocketURL,
|
|
188
|
+
expires_at: new Date(3005, 0, 0),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return requests;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function mockDeleteTailRequest(): RequestCounter {
|
|
197
|
+
const requests = { count: 0 };
|
|
198
|
+
setMockResponse(
|
|
199
|
+
"/accounts/:accountId/workers/scripts/:worker/tails/:tailId",
|
|
200
|
+
"DELETE",
|
|
201
|
+
() => {
|
|
202
|
+
requests.count++;
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
return requests;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
type MockAPI = {
|
|
211
|
+
requests: {
|
|
212
|
+
creation: RequestCounter;
|
|
213
|
+
deletion: RequestCounter;
|
|
214
|
+
};
|
|
215
|
+
ws: WS;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
function mockWebsocketApis(websocketURL = "ws://localhost:1234"): MockAPI {
|
|
219
|
+
const api: MockAPI = {
|
|
220
|
+
requests: {
|
|
221
|
+
deletion: { count: 0 },
|
|
222
|
+
creation: { count: 0 },
|
|
223
|
+
},
|
|
224
|
+
ws: new WS(websocketURL),
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// don't delete this line or else it breaks.
|
|
228
|
+
// we need to have api.ws be an instasnce of WS for
|
|
229
|
+
// the type checker to be happy, but if we have
|
|
230
|
+
// an actual open websocket then it causes problems
|
|
231
|
+
// since we create a new websocket beforeEach test.
|
|
232
|
+
// so we need to close it before we ever use it.
|
|
233
|
+
api.ws.close();
|
|
234
|
+
|
|
235
|
+
beforeEach(() => {
|
|
236
|
+
api.requests.creation = mockCreateTailRequest(websocketURL);
|
|
237
|
+
api.requests.deletion = mockDeleteTailRequest();
|
|
238
|
+
api.ws = new WS(websocketURL);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
afterEach(() => {
|
|
242
|
+
api.ws.close();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return api;
|
|
246
|
+
}
|
|
@@ -1,32 +1,15 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
1
|
import { render } from "ink-testing-library";
|
|
5
|
-
import
|
|
6
|
-
import { getUserInfo, WhoAmI } from "../whoami";
|
|
7
|
-
import { runInTempDir } from "./run-in-tmp";
|
|
8
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
9
|
-
import { setMockResponse } from "./mock-cfetch";
|
|
2
|
+
import React from "react";
|
|
10
3
|
import { initialise } from "../user";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
import { getUserInfo, WhoAmI } from "../whoami";
|
|
5
|
+
import { setMockResponse } from "./helpers/mock-cfetch";
|
|
6
|
+
import { writeUserConfig } from "./helpers/mock-user";
|
|
7
|
+
import { runInTempDir } from "./helpers/run-in-tmp";
|
|
8
|
+
import type { UserInfo } from "../whoami";
|
|
14
9
|
|
|
15
10
|
describe("getUserInfo()", () => {
|
|
16
11
|
runInTempDir({ homedir: "./home" });
|
|
17
12
|
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
// Clear the environment variables, so we can control them in the tests
|
|
20
|
-
delete process.env.CF_API_TOKEN;
|
|
21
|
-
delete process.env.CF_ACCOUNT_ID;
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterEach(() => {
|
|
25
|
-
// Reset any changes to the environment variables
|
|
26
|
-
process.env.CF_API_TOKEN = ORIGINAL_CF_API_TOKEN;
|
|
27
|
-
process.env.CF_ACCOUNT_ID = ORIGINAL_CF_ACCOUNT_ID;
|
|
28
|
-
});
|
|
29
|
-
|
|
30
13
|
it("should return undefined if there is no config file", async () => {
|
|
31
14
|
await initialise();
|
|
32
15
|
const userInfo = await getUserInfo();
|
|
@@ -101,27 +84,3 @@ describe("WhoAmI component", () => {
|
|
|
101
84
|
expect(lastFrame()).toMatch(/Account Three .+ account-3/);
|
|
102
85
|
});
|
|
103
86
|
});
|
|
104
|
-
|
|
105
|
-
function writeUserConfig(
|
|
106
|
-
oauth_token?: string,
|
|
107
|
-
refresh_token?: string,
|
|
108
|
-
expiration_time?: string
|
|
109
|
-
) {
|
|
110
|
-
const lines: string[] = [];
|
|
111
|
-
if (oauth_token) {
|
|
112
|
-
lines.push(`oauth_token = "${oauth_token}"`);
|
|
113
|
-
}
|
|
114
|
-
if (refresh_token) {
|
|
115
|
-
lines.push(`refresh_token = "${refresh_token}"`);
|
|
116
|
-
}
|
|
117
|
-
if (expiration_time) {
|
|
118
|
-
lines.push(`expiration_time = "${expiration_time}"`);
|
|
119
|
-
}
|
|
120
|
-
const configPath = path.join(os.homedir(), ".wrangler/config");
|
|
121
|
-
mkdirSync(configPath, { recursive: true });
|
|
122
|
-
writeFileSync(
|
|
123
|
-
path.join(configPath, "default.toml"),
|
|
124
|
-
lines.join("\n"),
|
|
125
|
-
"utf-8"
|
|
126
|
-
);
|
|
127
|
-
}
|
package/src/api/form_data.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { FormData, File } from "undici";
|
|
1
3
|
import type {
|
|
2
4
|
CfWorkerInit,
|
|
3
5
|
CfModuleType,
|
|
4
|
-
CfModule,
|
|
5
6
|
CfDurableObjectMigrations,
|
|
6
7
|
} from "./worker.js";
|
|
7
|
-
import { FormData, Blob } from "formdata-node";
|
|
8
8
|
|
|
9
9
|
export function toMimeType(type: CfModuleType): string {
|
|
10
10
|
switch (type) {
|
|
@@ -23,14 +23,11 @@ export function toMimeType(type: CfModuleType): string {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface WorkerMetadata {
|
|
26
|
+
export interface WorkerMetadata {
|
|
27
|
+
/** The name of the entry point module. Only exists when the worker is in the ES module format */
|
|
28
|
+
main_module?: string;
|
|
29
|
+
/** The name of the entry point module. Only exists when the worker is in the Service Worker format */
|
|
30
|
+
body_part?: string;
|
|
34
31
|
compatibility_date?: string;
|
|
35
32
|
compatibility_flags?: string[];
|
|
36
33
|
usage_model?: "bundled" | "unbound";
|
|
@@ -38,18 +35,15 @@ interface WorkerMetadata {
|
|
|
38
35
|
bindings: (
|
|
39
36
|
| { type: "kv_namespace"; name: string; namespace_id: string }
|
|
40
37
|
| { type: "plain_text"; name: string; text: string }
|
|
38
|
+
| { type: "json"; name: string; json: unknown }
|
|
39
|
+
| { type: "wasm_module"; name: string; part: string }
|
|
41
40
|
| {
|
|
42
41
|
type: "durable_object_namespace";
|
|
43
42
|
name: string;
|
|
44
43
|
class_name: string;
|
|
45
44
|
script_name?: string;
|
|
46
45
|
}
|
|
47
|
-
| {
|
|
48
|
-
type: "service";
|
|
49
|
-
name: string;
|
|
50
|
-
service: string;
|
|
51
|
-
environment: string;
|
|
52
|
-
}
|
|
46
|
+
| { type: "r2_bucket"; name: string; bucket_name: string }
|
|
53
47
|
)[];
|
|
54
48
|
}
|
|
55
49
|
|
|
@@ -89,18 +83,71 @@ export function toFormData(worker: CfWorkerInit): FormData {
|
|
|
89
83
|
}
|
|
90
84
|
);
|
|
91
85
|
|
|
86
|
+
bindings.r2_buckets?.forEach(({ binding, bucket_name }) => {
|
|
87
|
+
metadataBindings.push({
|
|
88
|
+
name: binding,
|
|
89
|
+
type: "r2_bucket",
|
|
90
|
+
bucket_name,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
92
94
|
Object.entries(bindings.vars || {})?.forEach(([key, value]) => {
|
|
93
|
-
|
|
95
|
+
if (typeof value === "string") {
|
|
96
|
+
metadataBindings.push({ name: key, type: "plain_text", text: value });
|
|
97
|
+
} else {
|
|
98
|
+
metadataBindings.push({ name: key, type: "json", json: value });
|
|
99
|
+
}
|
|
94
100
|
});
|
|
95
101
|
|
|
96
|
-
|
|
102
|
+
for (const [name, filePath] of Object.entries(bindings.wasm_modules || {})) {
|
|
97
103
|
metadataBindings.push({
|
|
98
104
|
name,
|
|
99
|
-
type: "
|
|
100
|
-
|
|
101
|
-
environment,
|
|
105
|
+
type: "wasm_module",
|
|
106
|
+
part: name,
|
|
102
107
|
});
|
|
103
|
-
|
|
108
|
+
|
|
109
|
+
formData.set(
|
|
110
|
+
name,
|
|
111
|
+
new File([readFileSync(filePath)], filePath, {
|
|
112
|
+
type: "application/wasm",
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (main.type === "commonjs") {
|
|
118
|
+
// This is a service-worker format worker.
|
|
119
|
+
// So we convert all `.wasm` modules into `wasm_module` bindings.
|
|
120
|
+
for (const [index, module] of Object.entries(modules || [])) {
|
|
121
|
+
if (module.type === "compiled-wasm") {
|
|
122
|
+
// The "name" of the module is a file path. We use it
|
|
123
|
+
// to instead be a "part" of the body, and a reference
|
|
124
|
+
// that we can use inside our source. This identifier has to be a valid
|
|
125
|
+
// JS identifier, so we replace all non alphanumeric characters
|
|
126
|
+
// with an underscore.
|
|
127
|
+
const name = module.name.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
128
|
+
metadataBindings.push({
|
|
129
|
+
name,
|
|
130
|
+
type: "wasm_module",
|
|
131
|
+
part: name,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Add the module to the form data.
|
|
135
|
+
formData.set(
|
|
136
|
+
name,
|
|
137
|
+
new File([module.content], module.name, {
|
|
138
|
+
type: "application/wasm",
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
// And then remove it from the modules collection
|
|
142
|
+
modules?.splice(parseInt(index, 10), 1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (bindings.unsafe) {
|
|
148
|
+
// @ts-expect-error unsafe bindings don't need to match a specific type here
|
|
149
|
+
metadataBindings.push(...bindings.unsafe);
|
|
150
|
+
}
|
|
104
151
|
|
|
105
152
|
const metadata: WorkerMetadata = {
|
|
106
153
|
...(main.type !== "commonjs"
|
|
@@ -122,9 +169,12 @@ export function toFormData(worker: CfWorkerInit): FormData {
|
|
|
122
169
|
}
|
|
123
170
|
|
|
124
171
|
for (const module of [main].concat(modules || [])) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
172
|
+
formData.set(
|
|
173
|
+
module.name,
|
|
174
|
+
new File([module.content], module.name, {
|
|
175
|
+
type: toMimeType(module.type ?? main.type ?? "esm"),
|
|
176
|
+
})
|
|
177
|
+
);
|
|
128
178
|
}
|
|
129
179
|
|
|
130
180
|
return formData;
|
package/src/api/preview.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { URL } from "node:url";
|
|
2
|
+
import { fetch } from "undici";
|
|
2
3
|
import { fetchResult } from "../cfetch";
|
|
3
4
|
import { toFormData } from "./form_data";
|
|
4
5
|
import type { CfAccount, CfWorkerInit } from "./worker";
|
|
@@ -108,7 +109,6 @@ export async function previewToken(
|
|
|
108
109
|
|
|
109
110
|
const { preview_token } = await fetchResult<{ preview_token: string }>(url, {
|
|
110
111
|
method: "POST",
|
|
111
|
-
// @ts-expect-error TODO: fix this
|
|
112
112
|
body: formData,
|
|
113
113
|
headers: {
|
|
114
114
|
"cf-preview-upload-config-token": value,
|
package/src/api/worker.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { fetch } from "undici";
|
|
2
2
|
import { previewToken } from "./preview";
|
|
3
|
-
import
|
|
3
|
+
import type { CfPreviewToken } from "./preview";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* A Cloudflare account.
|
|
@@ -22,6 +22,11 @@ export interface CfAccount {
|
|
|
22
22
|
zoneId?: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* The type of Worker
|
|
27
|
+
*/
|
|
28
|
+
export type CfScriptFormat = "modules" | "service-worker";
|
|
29
|
+
|
|
25
30
|
/**
|
|
26
31
|
* A module type.
|
|
27
32
|
*/
|
|
@@ -53,7 +58,7 @@ export interface CfModule {
|
|
|
53
58
|
* }
|
|
54
59
|
* }
|
|
55
60
|
*/
|
|
56
|
-
content: string |
|
|
61
|
+
content: string | Buffer;
|
|
57
62
|
/**
|
|
58
63
|
* The module type.
|
|
59
64
|
*
|
|
@@ -66,7 +71,7 @@ export interface CfModule {
|
|
|
66
71
|
* A map of variable names to values.
|
|
67
72
|
*/
|
|
68
73
|
interface CfVars {
|
|
69
|
-
[key: string]:
|
|
74
|
+
[key: string]: unknown;
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
/**
|
|
@@ -77,6 +82,14 @@ interface CfKvNamespace {
|
|
|
77
82
|
id: string;
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
/**
|
|
86
|
+
* A binding to a wasm module (in service worker format)
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
interface CfWasmModuleBindings {
|
|
90
|
+
[key: string]: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
/**
|
|
81
94
|
* A Durable Object.
|
|
82
95
|
*/
|
|
@@ -86,13 +99,14 @@ interface CfDurableObject {
|
|
|
86
99
|
script_name?: string;
|
|
87
100
|
}
|
|
88
101
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
interface CfR2Bucket {
|
|
103
|
+
binding: string;
|
|
104
|
+
bucket_name: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface CfUnsafeBinding {
|
|
93
108
|
name: string;
|
|
94
|
-
|
|
95
|
-
environment: string;
|
|
109
|
+
type: string;
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
export interface CfDurableObjectMigrations {
|
|
@@ -100,7 +114,10 @@ export interface CfDurableObjectMigrations {
|
|
|
100
114
|
new_tag: string;
|
|
101
115
|
steps: {
|
|
102
116
|
new_classes?: string[];
|
|
103
|
-
renamed_classes?:
|
|
117
|
+
renamed_classes?: {
|
|
118
|
+
from: string;
|
|
119
|
+
to: string;
|
|
120
|
+
}[];
|
|
104
121
|
deleted_classes?: string[];
|
|
105
122
|
}[];
|
|
106
123
|
}
|
|
@@ -125,10 +142,12 @@ export interface CfWorkerInit {
|
|
|
125
142
|
* All the bindings
|
|
126
143
|
*/
|
|
127
144
|
bindings: {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
vars: CfVars | undefined;
|
|
146
|
+
kv_namespaces: CfKvNamespace[] | undefined;
|
|
147
|
+
wasm_modules: CfWasmModuleBindings | undefined;
|
|
148
|
+
durable_objects: { bindings: CfDurableObject[] } | undefined;
|
|
149
|
+
r2_buckets: CfR2Bucket[] | undefined;
|
|
150
|
+
unsafe: CfUnsafeBinding[] | undefined;
|
|
132
151
|
};
|
|
133
152
|
migrations: undefined | CfDurableObjectMigrations;
|
|
134
153
|
compatibility_date: string | undefined;
|
package/src/bundle.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import * as esbuild from "esbuild";
|
|
5
|
+
import makeModuleCollector from "./module-collection";
|
|
6
|
+
import type { CfModule, CfScriptFormat } from "./api/worker";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* An entry point for the worker. It consists not just of a `file`,
|
|
10
|
+
* but also of a `directory` that is used to resolve relative paths.
|
|
11
|
+
*/
|
|
12
|
+
export type Entry = { file: string; directory: string };
|
|
13
|
+
|
|
14
|
+
type BundleResult = {
|
|
15
|
+
modules: CfModule[];
|
|
16
|
+
resolvedEntryPointPath: string;
|
|
17
|
+
bundleType: "esm" | "commonjs";
|
|
18
|
+
stop: (() => void) | undefined;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate a bundle for the worker identified by the arguments passed in.
|
|
23
|
+
*/
|
|
24
|
+
export async function bundleWorker(
|
|
25
|
+
entry: Entry,
|
|
26
|
+
serveAssetsFromWorker: boolean,
|
|
27
|
+
destination: string,
|
|
28
|
+
jsxFactory: string | undefined,
|
|
29
|
+
jsxFragment: string | undefined,
|
|
30
|
+
format: CfScriptFormat,
|
|
31
|
+
watch?: esbuild.WatchMode
|
|
32
|
+
): Promise<BundleResult> {
|
|
33
|
+
const moduleCollector = makeModuleCollector({ format });
|
|
34
|
+
const result = await esbuild.build({
|
|
35
|
+
...getEntryPoint(entry.file, serveAssetsFromWorker),
|
|
36
|
+
bundle: true,
|
|
37
|
+
absWorkingDir: entry.directory,
|
|
38
|
+
outdir: destination,
|
|
39
|
+
external: ["__STATIC_CONTENT_MANIFEST"],
|
|
40
|
+
format: "esm",
|
|
41
|
+
sourcemap: true,
|
|
42
|
+
metafile: true,
|
|
43
|
+
conditions: ["worker", "browser"],
|
|
44
|
+
loader: {
|
|
45
|
+
".js": "jsx",
|
|
46
|
+
".html": "text",
|
|
47
|
+
".pem": "text",
|
|
48
|
+
".txt": "text",
|
|
49
|
+
},
|
|
50
|
+
plugins: [moduleCollector.plugin],
|
|
51
|
+
...(jsxFactory && { jsxFactory }),
|
|
52
|
+
...(jsxFragment && { jsxFragment }),
|
|
53
|
+
watch,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const entryPointOutputs = Object.entries(result.metafile.outputs).filter(
|
|
57
|
+
([_path, output]) => output.entryPoint !== undefined
|
|
58
|
+
);
|
|
59
|
+
assert(
|
|
60
|
+
entryPointOutputs.length > 0,
|
|
61
|
+
`Cannot find entry-point "${entry.file}" in generated bundle.` +
|
|
62
|
+
listEntryPoints(entryPointOutputs)
|
|
63
|
+
);
|
|
64
|
+
assert(
|
|
65
|
+
entryPointOutputs.length < 2,
|
|
66
|
+
"More than one entry-point found for generated bundle." +
|
|
67
|
+
listEntryPoints(entryPointOutputs)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const entryPointExports = entryPointOutputs[0][1].exports;
|
|
71
|
+
const bundleType = entryPointExports.length > 0 ? "esm" : "commonjs";
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
modules: moduleCollector.modules,
|
|
75
|
+
resolvedEntryPointPath: path.resolve(
|
|
76
|
+
entry.directory,
|
|
77
|
+
entryPointOutputs[0][0]
|
|
78
|
+
),
|
|
79
|
+
bundleType,
|
|
80
|
+
stop: result.stop,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type EntryPoint =
|
|
85
|
+
| { stdin: esbuild.StdinOptions; nodePaths: string[] }
|
|
86
|
+
| { entryPoints: string[] };
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create an object that describes the entry point for esbuild.
|
|
90
|
+
*
|
|
91
|
+
* If we are using the experimental asset handling, then the entry point is
|
|
92
|
+
* actually a shim worker that will either return an asset from a KV store,
|
|
93
|
+
* or delegate to the actual worker.
|
|
94
|
+
*/
|
|
95
|
+
function getEntryPoint(
|
|
96
|
+
entryFile: string,
|
|
97
|
+
serveAssetsFromWorker: boolean
|
|
98
|
+
): EntryPoint {
|
|
99
|
+
if (serveAssetsFromWorker) {
|
|
100
|
+
return {
|
|
101
|
+
stdin: {
|
|
102
|
+
contents: fs
|
|
103
|
+
.readFileSync(
|
|
104
|
+
path.join(__dirname, "../templates/static-asset-facade.js"),
|
|
105
|
+
"utf8"
|
|
106
|
+
)
|
|
107
|
+
.replace("__ENTRY_POINT__", entryFile),
|
|
108
|
+
sourcefile: "static-asset-facade.js",
|
|
109
|
+
resolveDir: path.dirname(entryFile),
|
|
110
|
+
},
|
|
111
|
+
nodePaths: [path.join(__dirname, "../vendor")],
|
|
112
|
+
};
|
|
113
|
+
} else {
|
|
114
|
+
return { entryPoints: [entryFile] };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Generate a string that describes the entry-points that were identified by esbuild.
|
|
120
|
+
*/
|
|
121
|
+
function listEntryPoints(
|
|
122
|
+
outputs: [string, ValueOf<esbuild.Metafile["outputs"]>][]
|
|
123
|
+
): string {
|
|
124
|
+
return outputs.map(([_input, output]) => output.entryPoint).join("\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type ValueOf<T> = T[keyof T];
|