@moku-labs/worker 0.1.0
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/README.md +376 -0
- package/dist/cli.cjs +743 -0
- package/dist/cli.d.cts +139 -0
- package/dist/cli.d.mts +139 -0
- package/dist/cli.mjs +740 -0
- package/dist/config-AjH57AmD.d.cts +36 -0
- package/dist/config-AjH57AmD.d.mts +36 -0
- package/dist/index.cjs +544 -0
- package/dist/index.d.cts +962 -0
- package/dist/index.d.mts +960 -0
- package/dist/index.mjs +491 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/storage-BaQ6BBtl.cjs +990 -0
- package/dist/storage-bL-U_fkA.mjs +882 -0
- package/package.json +77 -0
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_storage = require("./storage-BaQ6BBtl.cjs");
|
|
3
|
+
let node_child_process = require("node:child_process");
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
node_path = require_storage.__toESM(node_path, 1);
|
|
7
|
+
let node_fs = require("node:fs");
|
|
8
|
+
//#region src/plugins/deploy/runner.ts
|
|
9
|
+
/**
|
|
10
|
+
* @file deploy plugin — wrangler subprocess wrapper (node:child_process).
|
|
11
|
+
*
|
|
12
|
+
* Spawns `wrangler` with the given args and resolves the deployed URL
|
|
13
|
+
* (extracted from stdout for `wrangler deploy`), or the full stdout for other verbs.
|
|
14
|
+
* This module is node-only; never imported by the runtime Worker bundle.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Extract the deployed URL from `wrangler deploy` stdout.
|
|
18
|
+
* Wrangler prints a line like: "Published my-worker (1.23 sec) https://..."
|
|
19
|
+
* or "Deployed my-worker (1.23 sec) https://...".
|
|
20
|
+
*
|
|
21
|
+
* @param output - The combined stdout from wrangler deploy.
|
|
22
|
+
* @returns The deployed URL, or empty string when not found.
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* extractDeployedUrl("Deployed my-worker (0.5 sec) https://my-worker.workers.dev");
|
|
26
|
+
* // "https://my-worker.workers.dev"
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
const extractDeployedUrl = (output) => {
|
|
30
|
+
return /https:\/\/[^\s]+\.workers\.dev[^\s]*/u.exec(output)?.[0] ?? "";
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Spawn `wrangler` with the given args and resolve the output string.
|
|
34
|
+
* For `wrangler deploy`, the resolved value is the deployed URL parsed from stdout.
|
|
35
|
+
* For all other verbs (dev, kv namespace create, etc.), the resolved value is stdout.
|
|
36
|
+
*
|
|
37
|
+
* @param args - Wrangler CLI arguments (e.g. ["deploy", "--config", "wrangler.jsonc"]).
|
|
38
|
+
* @returns Resolves with the deployed URL (deploy verb) or full stdout (other verbs).
|
|
39
|
+
* @throws {Error} When wrangler exits with a non-zero code.
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const url = await runWrangler(["deploy", "--config", "wrangler.jsonc"]);
|
|
43
|
+
* await runWrangler(["kv", "namespace", "create", "CACHE"]);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
const runWrangler = (args) => new Promise((resolve, reject) => {
|
|
47
|
+
const chunks = [];
|
|
48
|
+
const errChunks = [];
|
|
49
|
+
const child = (0, node_child_process.spawn)("wrangler", args, {
|
|
50
|
+
env: { ...process.env },
|
|
51
|
+
stdio: [
|
|
52
|
+
"ignore",
|
|
53
|
+
"pipe",
|
|
54
|
+
"pipe"
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
child.stdout.on("data", (chunk) => {
|
|
58
|
+
chunks.push(chunk);
|
|
59
|
+
});
|
|
60
|
+
child.stderr.on("data", (chunk) => {
|
|
61
|
+
errChunks.push(chunk);
|
|
62
|
+
});
|
|
63
|
+
child.on("error", (err) => {
|
|
64
|
+
reject(/* @__PURE__ */ new Error(`[moku-worker] Failed to spawn wrangler.\n ${err.message}`));
|
|
65
|
+
});
|
|
66
|
+
child.on("close", (code) => {
|
|
67
|
+
const stdout = Buffer.concat(chunks).toString("utf8");
|
|
68
|
+
const stderr = Buffer.concat(errChunks).toString("utf8");
|
|
69
|
+
if (code !== 0) {
|
|
70
|
+
reject(/* @__PURE__ */ new Error(`[moku-worker] wrangler exited with code ${String(code)}.\n ${stderr || stdout}`));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
resolve(args[0] === "deploy" ? extractDeployedUrl(stdout) : stdout);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/plugins/deploy/providers/d1.ts
|
|
78
|
+
/**
|
|
79
|
+
* @file deploy plugin — D1 provisioning adapter.
|
|
80
|
+
*
|
|
81
|
+
* Creates a Cloudflare D1 database via `wrangler d1 create <binding>`.
|
|
82
|
+
* Node-only; never imported by the runtime Worker bundle.
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* Provision a D1 database via `wrangler d1 create` and apply migrations.
|
|
86
|
+
*
|
|
87
|
+
* @param manifest - The D1 resource descriptor.
|
|
88
|
+
* @param _ci - Whether running non-interactively.
|
|
89
|
+
* @returns Resolves once the database is created (and migrations applied when specified).
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* await provisionD1({ kind: "d1", binding: "DB", migrations: "./migrations" }, false);
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
const provisionD1 = async (manifest, _ci) => {
|
|
96
|
+
await runWrangler([
|
|
97
|
+
"d1",
|
|
98
|
+
"create",
|
|
99
|
+
manifest.binding
|
|
100
|
+
]);
|
|
101
|
+
if (manifest.migrations) await runWrangler([
|
|
102
|
+
"d1",
|
|
103
|
+
"migrations",
|
|
104
|
+
"apply",
|
|
105
|
+
manifest.binding,
|
|
106
|
+
"--local"
|
|
107
|
+
]);
|
|
108
|
+
};
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/plugins/deploy/providers/do.ts
|
|
111
|
+
/**
|
|
112
|
+
* Provision Durable Object bindings. DOs are config-driven (no `wrangler do create` command
|
|
113
|
+
* exists) — the actual binding entries are written by writeWranglerConfig. This function is
|
|
114
|
+
* a resolved no-op for the dispatch step.
|
|
115
|
+
*
|
|
116
|
+
* @param _manifest - The Durable Objects resource descriptor.
|
|
117
|
+
* @param _ci - Whether running non-interactively.
|
|
118
|
+
* @returns Resolves immediately (DOs are config-only provisioning).
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* await provisionDurableObject({ kind: "do", bindings: { counter: "COUNTER" } }, false);
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
const provisionDurableObject = async (_manifest, _ci) => {};
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/plugins/deploy/providers/kv.ts
|
|
127
|
+
/**
|
|
128
|
+
* @file deploy plugin — KV provisioning adapter.
|
|
129
|
+
*
|
|
130
|
+
* Creates a Cloudflare KV namespace via `wrangler kv namespace create <binding>`.
|
|
131
|
+
* Node-only; never imported by the runtime Worker bundle.
|
|
132
|
+
*/
|
|
133
|
+
/**
|
|
134
|
+
* Provision a KV namespace via `wrangler kv namespace create`.
|
|
135
|
+
*
|
|
136
|
+
* @param manifest - The KV resource descriptor.
|
|
137
|
+
* @param _ci - Whether running non-interactively (passed through; wrangler respects env vars).
|
|
138
|
+
* @returns Resolves once the namespace is created.
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* await provisionKv({ kind: "kv", binding: "CACHE" }, false);
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
const provisionKv = async (manifest, _ci) => {
|
|
145
|
+
await runWrangler([
|
|
146
|
+
"kv",
|
|
147
|
+
"namespace",
|
|
148
|
+
"create",
|
|
149
|
+
manifest.binding
|
|
150
|
+
]);
|
|
151
|
+
};
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/plugins/deploy/providers/queues.ts
|
|
154
|
+
/**
|
|
155
|
+
* @file deploy plugin — Queues provisioning adapter.
|
|
156
|
+
*
|
|
157
|
+
* Creates Cloudflare Queues via `wrangler queues create <name>` for each producer.
|
|
158
|
+
* Node-only; never imported by the runtime Worker bundle.
|
|
159
|
+
*/
|
|
160
|
+
/**
|
|
161
|
+
* Provision queues via `wrangler queues create` for each declared producer.
|
|
162
|
+
*
|
|
163
|
+
* @param manifest - The queue resource descriptor.
|
|
164
|
+
* @param _ci - Whether running non-interactively.
|
|
165
|
+
* @returns Resolves once all queues are created.
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* await provisionQueue({ kind: "queue", producers: ["orders"] }, false);
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
const provisionQueue = async (manifest, _ci) => {
|
|
172
|
+
for (const producer of manifest.producers) await runWrangler([
|
|
173
|
+
"queues",
|
|
174
|
+
"create",
|
|
175
|
+
producer
|
|
176
|
+
]);
|
|
177
|
+
};
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/plugins/deploy/providers/r2.ts
|
|
180
|
+
/**
|
|
181
|
+
* @file deploy plugin — R2 provisioning + asset upload adapter.
|
|
182
|
+
*
|
|
183
|
+
* Provides two exports:
|
|
184
|
+
* - `provisionR2`: creates an R2 bucket via `wrangler r2 bucket create`.
|
|
185
|
+
* - `uploadDirToR2`: walks a directory recursively and uploads each file via
|
|
186
|
+
* `wrangler r2 object put`, returning the uploaded file count.
|
|
187
|
+
*
|
|
188
|
+
* Node-only; never imported by the runtime Worker bundle.
|
|
189
|
+
*/
|
|
190
|
+
/**
|
|
191
|
+
* Provision an R2 bucket via `wrangler r2 bucket create`.
|
|
192
|
+
*
|
|
193
|
+
* @param manifest - The R2 resource descriptor.
|
|
194
|
+
* @param _ci - Whether running non-interactively.
|
|
195
|
+
* @returns Resolves once the bucket is created.
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* await provisionR2({ kind: "r2", bucket: "ASSETS" }, false);
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
const provisionR2 = async (manifest, _ci) => {
|
|
202
|
+
await runWrangler([
|
|
203
|
+
"r2",
|
|
204
|
+
"bucket",
|
|
205
|
+
"create",
|
|
206
|
+
manifest.bucket
|
|
207
|
+
]);
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Walk a directory recursively and return all file paths (absolute).
|
|
211
|
+
*
|
|
212
|
+
* @param directory - Directory path to walk.
|
|
213
|
+
* @returns All file paths found under the directory.
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* const files = await walkDir("./public");
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
const walkDir = async (directory) => {
|
|
220
|
+
const entries = await (0, node_fs_promises.readdir)(directory);
|
|
221
|
+
const results = [];
|
|
222
|
+
for (const entry of entries) {
|
|
223
|
+
const fullPath = node_path.default.join(directory, entry);
|
|
224
|
+
if ((await (0, node_fs_promises.stat)(fullPath)).isDirectory()) {
|
|
225
|
+
const nested = await walkDir(fullPath);
|
|
226
|
+
results.push(...nested);
|
|
227
|
+
} else results.push(fullPath);
|
|
228
|
+
}
|
|
229
|
+
return results;
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Upload a directory to an R2 bucket and return the uploaded file count.
|
|
233
|
+
* Each file is uploaded via `wrangler r2 object put <bucket>/<key> --file <path>`.
|
|
234
|
+
*
|
|
235
|
+
* @param bucket - The R2 bucket binding name.
|
|
236
|
+
* @param directory - The directory to upload.
|
|
237
|
+
* @returns The number of files uploaded.
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* const count = await uploadDirToR2("ASSETS", "./public");
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
const uploadDirToR2 = async (bucket, directory) => {
|
|
244
|
+
const files = await walkDir(directory);
|
|
245
|
+
for (const filePath of files) await runWrangler([
|
|
246
|
+
"r2",
|
|
247
|
+
"object",
|
|
248
|
+
"put",
|
|
249
|
+
`${bucket}/${node_path.default.relative(directory, filePath)}`,
|
|
250
|
+
"--file",
|
|
251
|
+
filePath
|
|
252
|
+
]);
|
|
253
|
+
return files.length;
|
|
254
|
+
};
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/plugins/deploy/providers/index.ts
|
|
257
|
+
/**
|
|
258
|
+
* Dispatch a resource descriptor to the matching provider's provisioning routine.
|
|
259
|
+
*
|
|
260
|
+
* @param resource - The resource descriptor to provision.
|
|
261
|
+
* @param ci - Whether running non-interactively.
|
|
262
|
+
* @returns Resolves once the resource is provisioned.
|
|
263
|
+
* @example
|
|
264
|
+
* ```ts
|
|
265
|
+
* await provisionResource({ kind: "kv", binding: "CACHE" }, false);
|
|
266
|
+
* await provisionResource({ kind: "r2", bucket: "ASSETS" }, false);
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
const provisionResource = async (resource, ci) => {
|
|
270
|
+
switch (resource.kind) {
|
|
271
|
+
case "kv":
|
|
272
|
+
await provisionKv(resource, ci);
|
|
273
|
+
break;
|
|
274
|
+
case "r2":
|
|
275
|
+
await provisionR2(resource, ci);
|
|
276
|
+
break;
|
|
277
|
+
case "d1":
|
|
278
|
+
await provisionD1(resource, ci);
|
|
279
|
+
break;
|
|
280
|
+
case "queue":
|
|
281
|
+
await provisionQueue(resource, ci);
|
|
282
|
+
break;
|
|
283
|
+
case "do":
|
|
284
|
+
await provisionDurableObject(resource, ci);
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/plugins/deploy/wrangler-config.ts
|
|
290
|
+
/**
|
|
291
|
+
* @file deploy plugin — wrangler config generation + scaffold.
|
|
292
|
+
*
|
|
293
|
+
* Provides two exports:
|
|
294
|
+
* - `writeWranglerConfig`: generates/updates a wrangler.jsonc file from an ExternalManifest.
|
|
295
|
+
* Non-destructive: preserves existing top-level keys not managed by deploy.
|
|
296
|
+
* - `scaffoldWranglerAndCi`: creates a minimal starter wrangler config when the file does not
|
|
297
|
+
* exist yet; idempotent (leaves existing files untouched).
|
|
298
|
+
*
|
|
299
|
+
* Node-only; never imported by the runtime Worker bundle.
|
|
300
|
+
*/
|
|
301
|
+
/**
|
|
302
|
+
* Strip JSONC line- and block-comments, then JSON.parse the result.
|
|
303
|
+
*
|
|
304
|
+
* @param source - Raw JSONC file contents.
|
|
305
|
+
* @returns The parsed object.
|
|
306
|
+
* @example
|
|
307
|
+
* ```ts
|
|
308
|
+
* const cfg = parseJsonc('{ "name": "w" } // trailing comment');
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
const parseJsonc = (source) => {
|
|
312
|
+
const stripped = source.replaceAll(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/gu, "");
|
|
313
|
+
return JSON.parse(stripped);
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* Build the wrangler `kv_namespaces` array from the manifest's kv resources.
|
|
317
|
+
*
|
|
318
|
+
* @param resources - All resource descriptors from the manifest.
|
|
319
|
+
* @returns One wrangler KV namespace entry per kv resource.
|
|
320
|
+
* @example
|
|
321
|
+
* ```ts
|
|
322
|
+
* const kv = buildKvNamespaces([{ kind: "kv", binding: "CACHE" }]);
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
const buildKvNamespaces = (resources) => resources.filter((resource) => resource.kind === "kv").map((resource) => ({
|
|
326
|
+
binding: resource.binding,
|
|
327
|
+
id: ""
|
|
328
|
+
}));
|
|
329
|
+
/**
|
|
330
|
+
* Build the wrangler `r2_buckets` array from the manifest's r2 resources.
|
|
331
|
+
*
|
|
332
|
+
* @param resources - All resource descriptors from the manifest.
|
|
333
|
+
* @returns One wrangler R2 bucket entry per r2 resource.
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* const r2 = buildR2Buckets([{ kind: "r2", bucket: "ASSETS" }]);
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
const buildR2Buckets = (resources) => resources.filter((resource) => resource.kind === "r2").map((resource) => ({
|
|
340
|
+
binding: resource.bucket,
|
|
341
|
+
bucket_name: resource.bucket.toLowerCase()
|
|
342
|
+
}));
|
|
343
|
+
/**
|
|
344
|
+
* Build the wrangler `d1_databases` array from the manifest's d1 resources.
|
|
345
|
+
*
|
|
346
|
+
* @param resources - All resource descriptors from the manifest.
|
|
347
|
+
* @returns One wrangler D1 database entry per d1 resource (migrations_dir set when present).
|
|
348
|
+
* @example
|
|
349
|
+
* ```ts
|
|
350
|
+
* const d1 = buildD1Databases([{ kind: "d1", binding: "DB" }]);
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
const buildD1Databases = (resources) => resources.filter((resource) => resource.kind === "d1").map((resource) => {
|
|
354
|
+
const entry = {
|
|
355
|
+
binding: resource.binding,
|
|
356
|
+
database_name: resource.binding.toLowerCase(),
|
|
357
|
+
database_id: ""
|
|
358
|
+
};
|
|
359
|
+
if (resource.migrations) entry.migrations_dir = resource.migrations;
|
|
360
|
+
return entry;
|
|
361
|
+
});
|
|
362
|
+
/**
|
|
363
|
+
* Build the wrangler `queues` producers section from the manifest's queue resources.
|
|
364
|
+
*
|
|
365
|
+
* @param resources - All resource descriptors from the manifest.
|
|
366
|
+
* @returns The queues section, or undefined when there are no queue resources.
|
|
367
|
+
* @example
|
|
368
|
+
* ```ts
|
|
369
|
+
* const q = buildQueues([{ kind: "queue", producers: ["jobs"] }]);
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
const buildQueues = (resources) => {
|
|
373
|
+
const queueResources = resources.filter((resource) => resource.kind === "queue");
|
|
374
|
+
if (queueResources.length === 0) return void 0;
|
|
375
|
+
return { producers: queueResources.flatMap((resource) => resource.producers.map((producer) => ({
|
|
376
|
+
queue: producer,
|
|
377
|
+
binding: producer.toUpperCase()
|
|
378
|
+
}))) };
|
|
379
|
+
};
|
|
380
|
+
/**
|
|
381
|
+
* Build the wrangler `durable_objects` bindings section from the manifest's do resources.
|
|
382
|
+
*
|
|
383
|
+
* @param resources - All resource descriptors from the manifest.
|
|
384
|
+
* @returns The durable_objects section, or undefined when there are no do resources.
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* const dobj = buildDurableObjects([{ kind: "do", bindings: { Counter: "COUNTER" } }]);
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
const buildDurableObjects = (resources) => {
|
|
391
|
+
const doResources = resources.filter((resource) => resource.kind === "do");
|
|
392
|
+
if (doResources.length === 0) return void 0;
|
|
393
|
+
return { bindings: doResources.flatMap((resource) => Object.entries(resource.bindings).map(([className, bindingName]) => ({
|
|
394
|
+
name: bindingName,
|
|
395
|
+
class_name: className
|
|
396
|
+
}))) };
|
|
397
|
+
};
|
|
398
|
+
/**
|
|
399
|
+
* Generate/update the wrangler config file from a manifest (non-destructive merge).
|
|
400
|
+
* If the file exists, its top-level keys are preserved and only deploy-managed keys
|
|
401
|
+
* (name, compatibility_date, kv_namespaces, r2_buckets, d1_databases, queues,
|
|
402
|
+
* durable_objects) are updated.
|
|
403
|
+
*
|
|
404
|
+
* @param configFile - Path to the wrangler config file.
|
|
405
|
+
* @param manifest - The assembled deploy manifest.
|
|
406
|
+
* @returns Resolves once the file is written.
|
|
407
|
+
* @example
|
|
408
|
+
* ```ts
|
|
409
|
+
* await writeWranglerConfig("wrangler.jsonc", manifest);
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
const writeWranglerConfig = async (configFile, manifest) => {
|
|
413
|
+
let existing = {};
|
|
414
|
+
if ((0, node_fs.existsSync)(configFile)) try {
|
|
415
|
+
existing = parseJsonc((0, node_fs.readFileSync)(configFile, "utf8"));
|
|
416
|
+
} catch {
|
|
417
|
+
existing = {};
|
|
418
|
+
}
|
|
419
|
+
const kvNamespaces = buildKvNamespaces(manifest.resources);
|
|
420
|
+
const r2Buckets = buildR2Buckets(manifest.resources);
|
|
421
|
+
const d1Databases = buildD1Databases(manifest.resources);
|
|
422
|
+
const queues = buildQueues(manifest.resources);
|
|
423
|
+
const durableObjects = buildDurableObjects(manifest.resources);
|
|
424
|
+
const updated = {
|
|
425
|
+
...existing,
|
|
426
|
+
name: manifest.name,
|
|
427
|
+
compatibility_date: manifest.compatibilityDate
|
|
428
|
+
};
|
|
429
|
+
if (kvNamespaces.length > 0) updated.kv_namespaces = kvNamespaces;
|
|
430
|
+
if (r2Buckets.length > 0) updated.r2_buckets = r2Buckets;
|
|
431
|
+
if (d1Databases.length > 0) updated.d1_databases = d1Databases;
|
|
432
|
+
if (queues !== void 0) updated.queues = queues;
|
|
433
|
+
if (durableObjects !== void 0) updated.durable_objects = durableObjects;
|
|
434
|
+
await (0, node_fs_promises.writeFile)(configFile, JSON.stringify(updated, void 0, 2));
|
|
435
|
+
};
|
|
436
|
+
/**
|
|
437
|
+
* Scaffold a starting wrangler config and, when ci is set, CI workflow files.
|
|
438
|
+
* Idempotent: an existing config file is left completely untouched.
|
|
439
|
+
*
|
|
440
|
+
* @param configFile - Path to the wrangler config file.
|
|
441
|
+
* @param _ci - Whether to also scaffold CI workflow files.
|
|
442
|
+
* @returns Resolves once scaffolding is written.
|
|
443
|
+
* @example
|
|
444
|
+
* ```ts
|
|
445
|
+
* await scaffoldWranglerAndCi("wrangler.jsonc", true);
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
448
|
+
const scaffoldWranglerAndCi = async (configFile, _ci) => {
|
|
449
|
+
if ((0, node_fs.existsSync)(configFile)) return;
|
|
450
|
+
const starter = {
|
|
451
|
+
name: "my-worker",
|
|
452
|
+
main: "src/worker.ts",
|
|
453
|
+
compatibility_date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
454
|
+
};
|
|
455
|
+
await (0, node_fs_promises.writeFile)(configFile, JSON.stringify(starter, void 0, 2));
|
|
456
|
+
};
|
|
457
|
+
//#endregion
|
|
458
|
+
//#region src/plugins/deploy/api.ts
|
|
459
|
+
/**
|
|
460
|
+
* @file deploy plugin — API factory (run, dev, init).
|
|
461
|
+
*
|
|
462
|
+
* Pure ctx-taking factory. Assembles the deploy manifest from each resource plugin's own
|
|
463
|
+
* deployManifest() api (never sibling pluginConfigs — design F6), provisions resources,
|
|
464
|
+
* generates/updates the wrangler config, uploads the R2 upload dir, and runs wrangler deploy.
|
|
465
|
+
* Emits only global events: deploy:phase, deploy:complete, provision:resource.
|
|
466
|
+
*
|
|
467
|
+
* Node-only: uses node:child_process (via runner.ts) and node:fs (via wrangler-config.ts).
|
|
468
|
+
* Never called in the deployed Worker runtime.
|
|
469
|
+
*/
|
|
470
|
+
/**
|
|
471
|
+
* Derive a human-readable name string from a resource descriptor (used in provision:resource).
|
|
472
|
+
*
|
|
473
|
+
* @param resource - The resource descriptor.
|
|
474
|
+
* @returns A name suitable for the provision:resource event payload.
|
|
475
|
+
* @example
|
|
476
|
+
* ```ts
|
|
477
|
+
* resourceName({ kind: "kv", binding: "CACHE" }); // "CACHE"
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
const resourceName = (resource) => {
|
|
481
|
+
switch (resource.kind) {
|
|
482
|
+
case "r2": return resource.bucket;
|
|
483
|
+
case "do": return Object.values(resource.bindings).join(",");
|
|
484
|
+
case "queue": return resource.producers.join(",");
|
|
485
|
+
default: return resource.binding;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
/**
|
|
489
|
+
* Create the deploy api. Assembles the manifest from each resource plugin's own
|
|
490
|
+
* deployManifest() (never sibling config), provisions, generates config, uploads,
|
|
491
|
+
* and runs `wrangler deploy`, emitting global deploy events along the way.
|
|
492
|
+
*
|
|
493
|
+
* @param ctx - Plugin context (own config + require + has + emit + global).
|
|
494
|
+
* @returns The app.deploy api: run / dev / init.
|
|
495
|
+
* @example
|
|
496
|
+
* ```ts
|
|
497
|
+
* const api = createDeployApi(ctx);
|
|
498
|
+
* await api.run();
|
|
499
|
+
* ```
|
|
500
|
+
*/
|
|
501
|
+
const createDeployApi = (ctx) => ({
|
|
502
|
+
/**
|
|
503
|
+
* Run the full deploy pipeline: detect → provision → wrangler-config → upload → deploy.
|
|
504
|
+
* When opts.manifest is supplied, it is used verbatim (universal path).
|
|
505
|
+
*
|
|
506
|
+
* @param opts - Optional run options.
|
|
507
|
+
* @param opts.guided - Enable interactive confirmation steps (skipped when ci=true).
|
|
508
|
+
* @param opts.yes - Auto-confirm all prompts.
|
|
509
|
+
* @param opts.manifest - Caller-supplied manifest (bypasses deployManifest() assembly).
|
|
510
|
+
* @returns Resolves once the deploy completes.
|
|
511
|
+
* @example
|
|
512
|
+
* ```ts
|
|
513
|
+
* await api.run({ guided: true });
|
|
514
|
+
* await api.run({ manifest: { name: "w", compatibilityDate: "2026-06-17", resources: [] } });
|
|
515
|
+
* ```
|
|
516
|
+
*/
|
|
517
|
+
async run(opts) {
|
|
518
|
+
ctx.emit("deploy:phase", { phase: "detect" });
|
|
519
|
+
const manifest = opts?.manifest ?? {
|
|
520
|
+
name: ctx.global.name,
|
|
521
|
+
compatibilityDate: ctx.global.compatibilityDate,
|
|
522
|
+
resources: [
|
|
523
|
+
ctx.has("storage") ? ctx.require(require_storage.storagePlugin).deployManifest() : void 0,
|
|
524
|
+
ctx.has("kv") ? ctx.require(require_storage.kvPlugin).deployManifest() : void 0,
|
|
525
|
+
ctx.has("d1") ? ctx.require(require_storage.d1Plugin).deployManifest() : void 0,
|
|
526
|
+
ctx.has("queues") ? ctx.require(require_storage.queuesPlugin).deployManifest() : void 0,
|
|
527
|
+
ctx.has("durableObjects") ? ctx.require(require_storage.durableObjectsPlugin).deployManifest() : void 0
|
|
528
|
+
].filter((resource) => resource !== void 0)
|
|
529
|
+
};
|
|
530
|
+
ctx.emit("deploy:phase", { phase: "provision" });
|
|
531
|
+
for (const resource of manifest.resources) {
|
|
532
|
+
await provisionResource(resource, ctx.config.ci);
|
|
533
|
+
ctx.emit("provision:resource", {
|
|
534
|
+
kind: resource.kind,
|
|
535
|
+
name: resourceName(resource)
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
ctx.emit("deploy:phase", { phase: "wrangler-config" });
|
|
539
|
+
await writeWranglerConfig(ctx.config.configFile, manifest);
|
|
540
|
+
const r2Resource = manifest.resources.find((resource) => resource.kind === "r2");
|
|
541
|
+
if (r2Resource?.upload) {
|
|
542
|
+
const count = await uploadDirToR2(r2Resource.bucket, r2Resource.upload);
|
|
543
|
+
ctx.emit("deploy:phase", {
|
|
544
|
+
phase: "upload",
|
|
545
|
+
detail: `${String(count)} files`
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
ctx.emit("deploy:phase", { phase: "deploy" });
|
|
549
|
+
const url = await runWrangler([
|
|
550
|
+
"deploy",
|
|
551
|
+
"--config",
|
|
552
|
+
ctx.config.configFile
|
|
553
|
+
]);
|
|
554
|
+
ctx.emit("deploy:complete", { url });
|
|
555
|
+
},
|
|
556
|
+
/**
|
|
557
|
+
* Start a local Cloudflare dev session via `wrangler dev`.
|
|
558
|
+
*
|
|
559
|
+
* @param opts - Optional options.
|
|
560
|
+
* @param opts.port - Local dev port (default 8787).
|
|
561
|
+
* @returns Resolves when the dev session ends.
|
|
562
|
+
* @example
|
|
563
|
+
* ```ts
|
|
564
|
+
* await api.dev({ port: 8787 });
|
|
565
|
+
* ```
|
|
566
|
+
*/
|
|
567
|
+
dev: async (opts) => {
|
|
568
|
+
await runWrangler([
|
|
569
|
+
"dev",
|
|
570
|
+
"--port",
|
|
571
|
+
String(opts?.port ?? 8787),
|
|
572
|
+
"--config",
|
|
573
|
+
ctx.config.configFile
|
|
574
|
+
]);
|
|
575
|
+
},
|
|
576
|
+
/**
|
|
577
|
+
* Scaffold a starting wrangler config (and CI files when ci is set).
|
|
578
|
+
* Idempotent: an existing config file is left untouched.
|
|
579
|
+
*
|
|
580
|
+
* @param opts - Optional options.
|
|
581
|
+
* @param opts.ci - Also scaffold CI workflow files.
|
|
582
|
+
* @returns Resolves once scaffolding is written.
|
|
583
|
+
* @example
|
|
584
|
+
* ```ts
|
|
585
|
+
* await api.init({ ci: true });
|
|
586
|
+
* ```
|
|
587
|
+
*/
|
|
588
|
+
init: async (opts) => {
|
|
589
|
+
await scaffoldWranglerAndCi(ctx.config.configFile, opts?.ci ?? ctx.config.ci);
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
/**
|
|
593
|
+
* Complex tier (node-only) — build-time deploy orchestrator over the five resource plugins.
|
|
594
|
+
*
|
|
595
|
+
* Assembles each resource plugin's deployManifest() via ctx.require, provisions resources,
|
|
596
|
+
* generates/updates wrangler config, uploads the R2 upload dir, and runs wrangler deploy.
|
|
597
|
+
* Also supports a universal path: run({ manifest }) uses a caller-supplied manifest verbatim.
|
|
598
|
+
*
|
|
599
|
+
* @see README.md
|
|
600
|
+
*/
|
|
601
|
+
const deployPlugin = require_storage.createPlugin("deploy", {
|
|
602
|
+
config: {
|
|
603
|
+
configFile: "wrangler.jsonc",
|
|
604
|
+
ci: false
|
|
605
|
+
},
|
|
606
|
+
depends: [
|
|
607
|
+
require_storage.storagePlugin,
|
|
608
|
+
require_storage.kvPlugin,
|
|
609
|
+
require_storage.d1Plugin,
|
|
610
|
+
require_storage.queuesPlugin,
|
|
611
|
+
require_storage.durableObjectsPlugin
|
|
612
|
+
],
|
|
613
|
+
api: (ctx) => createDeployApi(ctx)
|
|
614
|
+
});
|
|
615
|
+
//#endregion
|
|
616
|
+
//#region src/plugins/cli/api.ts
|
|
617
|
+
/**
|
|
618
|
+
* Builds app.cli.* — thin passthroughs to the deploy plugin via ctx.require(deployPlugin).
|
|
619
|
+
* Both verbs forward their opts verbatim; `dev` defaults port to ctx.config.port when no
|
|
620
|
+
* opts are supplied.
|
|
621
|
+
*
|
|
622
|
+
* @param ctx - CLI plugin context (own config + typed require to deployPlugin).
|
|
623
|
+
* @returns The cli API object with `dev` and `deploy` methods.
|
|
624
|
+
* @example
|
|
625
|
+
* ```ts
|
|
626
|
+
* const api = createCliApi(ctx);
|
|
627
|
+
* await api.dev(); // → deploy.dev({ port: 8787 })
|
|
628
|
+
* await api.deploy({ yes: true }); // → deploy.run({ yes: true })
|
|
629
|
+
* ```
|
|
630
|
+
*/
|
|
631
|
+
const createCliApi = (ctx) => ({
|
|
632
|
+
/**
|
|
633
|
+
* Run the Worker locally; defaults port to ctx.config.port (8787) when no opts supplied.
|
|
634
|
+
*
|
|
635
|
+
* @param opts - Optional local dev options.
|
|
636
|
+
* @param opts.port - Local dev port to bind. Defaults to ctx.config.port (8787).
|
|
637
|
+
* @returns Resolves when the dev session ends.
|
|
638
|
+
* @example
|
|
639
|
+
* ```ts
|
|
640
|
+
* await api.dev(); // port 8787
|
|
641
|
+
* await api.dev({ port: 3000 }); // port 3000
|
|
642
|
+
* ```
|
|
643
|
+
*/
|
|
644
|
+
dev(opts) {
|
|
645
|
+
return ctx.require(deployPlugin).dev(opts ?? { port: ctx.config.port });
|
|
646
|
+
},
|
|
647
|
+
/**
|
|
648
|
+
* One-command guided Cloudflare deploy; forwards flags verbatim to deploy.run.
|
|
649
|
+
* Passes `undefined` when called with no opts (not a default empty object).
|
|
650
|
+
*
|
|
651
|
+
* @param opts - Optional deploy options.
|
|
652
|
+
* @param opts.guided - Walk through each step interactively.
|
|
653
|
+
* @param opts.yes - Skip confirmation prompts (non-interactive / CI).
|
|
654
|
+
* @returns Resolves once the deploy completes.
|
|
655
|
+
* @example
|
|
656
|
+
* ```ts
|
|
657
|
+
* await api.deploy({ guided: true });
|
|
658
|
+
* await api.deploy({ yes: true }); // CI
|
|
659
|
+
* await api.deploy(); // opts === undefined
|
|
660
|
+
* ```
|
|
661
|
+
*/
|
|
662
|
+
deploy(opts) {
|
|
663
|
+
return ctx.require(deployPlugin).run(opts);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
//#endregion
|
|
667
|
+
//#region src/plugins/cli/handlers.ts
|
|
668
|
+
/**
|
|
669
|
+
* Builds the hook handlers that turn global deploy events into a live progress TUI
|
|
670
|
+
* via ctx.log. Pure observers — print and return; never mutate state, never block
|
|
671
|
+
* the deploy pipeline (fire-and-forget, spec/07 §3,§4).
|
|
672
|
+
*
|
|
673
|
+
* @param ctx - CLI plugin context with injected log core API.
|
|
674
|
+
* @returns Hook map for the three global deploy events.
|
|
675
|
+
* @example
|
|
676
|
+
* ```ts
|
|
677
|
+
* const hooks = createCliHooks(ctx);
|
|
678
|
+
* hooks["deploy:phase"]({ phase: "detect" }); // logs "> detect"
|
|
679
|
+
* hooks["provision:resource"]({ kind: "kv", name: "KV" }); // logs " + kv KV"
|
|
680
|
+
* hooks["deploy:complete"]({ url: "https://x.workers.dev" }); // logs "done -> https://x.workers.dev"
|
|
681
|
+
* ```
|
|
682
|
+
*/
|
|
683
|
+
const createCliHooks = (ctx) => ({
|
|
684
|
+
/**
|
|
685
|
+
* Print one line per pipeline phase: "> phase" or "> phase - detail".
|
|
686
|
+
*
|
|
687
|
+
* @param p - The deploy:phase event payload.
|
|
688
|
+
* @example
|
|
689
|
+
* ```ts
|
|
690
|
+
* handler({ phase: "detect" }); // "> detect"
|
|
691
|
+
* handler({ phase: "upload", detail: "3 files" }); // "> upload - 3 files"
|
|
692
|
+
* ```
|
|
693
|
+
*/
|
|
694
|
+
"deploy:phase"(p) {
|
|
695
|
+
const detail = p.detail ? ` - ${p.detail}` : "";
|
|
696
|
+
ctx.log.info(`> ${p.phase}${detail}`);
|
|
697
|
+
},
|
|
698
|
+
/**
|
|
699
|
+
* Print one indented line per provisioned resource: " + kind name".
|
|
700
|
+
*
|
|
701
|
+
* @param p - The provision:resource event payload.
|
|
702
|
+
* @example
|
|
703
|
+
* ```ts
|
|
704
|
+
* handler({ kind: "kv", name: "KV" }); // " + kv KV"
|
|
705
|
+
* ```
|
|
706
|
+
*/
|
|
707
|
+
"provision:resource"(p) {
|
|
708
|
+
ctx.log.info(` + ${p.kind} ${p.name}`);
|
|
709
|
+
},
|
|
710
|
+
/**
|
|
711
|
+
* Print the terminal success line with the deployed URL.
|
|
712
|
+
*
|
|
713
|
+
* @param p - The deploy:complete event payload.
|
|
714
|
+
* @example
|
|
715
|
+
* ```ts
|
|
716
|
+
* handler({ url: "https://my-worker.workers.dev" }); // "done -> https://my-worker.workers.dev"
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
719
|
+
"deploy:complete"(p) {
|
|
720
|
+
ctx.log.info(`done -> ${p.url}`);
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
/**
|
|
724
|
+
* Standard tier (node-only) — developer-facing CLI surface.
|
|
725
|
+
*
|
|
726
|
+
* Mounts `app.cli.dev()` and `app.cli.deploy()` as thin passthroughs to deployPlugin.
|
|
727
|
+
* Hooks subscribe to the global deploy:phase / provision:resource / deploy:complete events
|
|
728
|
+
* and print a live progress TUI via the injected ctx.log core API.
|
|
729
|
+
*
|
|
730
|
+
* Inline lambdas on `api`/`hooks` preserve event-name inference so the hook map keys
|
|
731
|
+
* are constrained to `WorkerEvents` keys (spec/15 §5).
|
|
732
|
+
*
|
|
733
|
+
* @see README.md
|
|
734
|
+
*/
|
|
735
|
+
const cliPlugin = require_storage.createPlugin("cli", {
|
|
736
|
+
depends: [deployPlugin],
|
|
737
|
+
config: { port: 8787 },
|
|
738
|
+
api: (ctx) => createCliApi(ctx),
|
|
739
|
+
hooks: (ctx) => createCliHooks(ctx)
|
|
740
|
+
});
|
|
741
|
+
//#endregion
|
|
742
|
+
exports.cliPlugin = cliPlugin;
|
|
743
|
+
exports.deployPlugin = deployPlugin;
|