@lunora/config 1.0.0-alpha.7 → 1.0.0-alpha.71
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/LICENSE.md +6 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +885 -461
- package/dist/index.d.ts +885 -461
- package/dist/index.mjs +18 -10
- package/dist/packem_shared/AGENT_MODE_ENV-B5R7jpY7.mjs +17 -0
- package/dist/packem_shared/DEV_DAEMON_ENV-FIEtW7Fz.mjs +163 -0
- package/dist/packem_shared/{LUNORA_EVENT_SOURCE-D2fDeGB6.mjs → LUNORA_EVENT_SOURCE-M1KM9_pR.mjs} +28 -2
- package/dist/packem_shared/{PACKAGE_SECRETS_REGISTRY-CySy5vR_.mjs → PACKAGE_SECRETS_REGISTRY-BeRSvrl5.mjs} +45 -1
- package/dist/packem_shared/{POLICY_SCAFFOLD_ENDPOINT-CiC2IGKx.mjs → POLICY_SCAFFOLD_ENDPOINT-CvTZpaZF.mjs} +7 -10
- package/dist/packem_shared/{REMOTE_ELIGIBLE_KEYS-BC7_e9Bz.mjs → REMOTE_ELIGIBLE_KEYS-C-F_Flgy.mjs} +1 -6
- package/dist/packem_shared/{REQUIRED_COMPATIBILITY_DATE-ycqievRl.mjs → REQUIRED_COMPATIBILITY_DATE-DP0SFVy9.mjs} +135 -4
- package/dist/packem_shared/{SCHEMA_EDIT_ENDPOINT-Df-Wrix-.mjs → SCHEMA_EDIT_ENDPOINT-Bt2SNkpV.mjs} +4 -8
- package/dist/packem_shared/{SEED_ENDPOINT-DVCjaGO-.mjs → SEED_ENDPOINT-7izz_Jao.mjs} +1 -1
- package/dist/packem_shared/WORKERS_CACHE_MIN_DATE-CaXbS7CX.mjs +11 -0
- package/dist/packem_shared/assetContentType-ByZjeV3D.mjs +72 -0
- package/dist/packem_shared/{buildPackageSecretsBlock-S74dgmwy.mjs → buildPackageSecretsBlock-B-dWb-Sa.mjs} +115 -21
- package/dist/packem_shared/collectWranglerSecretVariables-p4ezBYQ4.mjs +56 -0
- package/dist/packem_shared/{createConfirm-fvpdgJ9s.mjs → createConfirm-BMikeJC9.mjs} +22 -5
- package/dist/packem_shared/{detectFramework-Br-BcPBq.mjs → detectFramework-U08038Yp.mjs} +3 -3
- package/dist/packem_shared/discoverAgentInfo-eXA3hF4v.mjs +19 -0
- package/dist/packem_shared/{discoverSchemaInfo-DWtypqpP.mjs → discoverSchemaInfo-BB-CKlTK.mjs} +1 -1
- package/dist/packem_shared/{inferLunoraBindings-XNJH4pup.mjs → inferLunoraBindings-BcvvtpSG.mjs} +146 -51
- package/dist/packem_shared/jsonc-edit-SbcD7MAe.mjs +9 -0
- package/dist/packem_shared/parseDevVariable-B0FEvfHC.mjs +21 -0
- package/dist/packem_shared/{policy-scaffold.d-DCmwn7zQ.d.ts → policy-scaffold.d-CFd2FlqG.d.mts} +22 -22
- package/dist/packem_shared/{policy-scaffold.d-DCmwn7zQ.d.mts → policy-scaffold.d-CFd2FlqG.d.ts} +22 -22
- package/dist/packem_shared/{reconcileWranglerBindings-DTHmqTbL.mjs → reconcileWranglerBindings-2Cww5VLa.mjs} +114 -29
- package/dist/packem_shared/reconcileWranglerCompatibilityDate-C8c497ag.mjs +31 -0
- package/dist/packem_shared/reconcileWranglerCrons-DvsceVFz.mjs +29 -0
- package/dist/packem_shared/{serveJsonHandler-B4OLTGLS.mjs → serveJsonHandler-Cl2Wqkv8.mjs} +2 -2
- package/dist/packem_shared/streamContainerLogs-BZ4cOZwH.mjs +157 -0
- package/dist/packem_shared/write-atomic-B2REqapi.mjs +14 -0
- package/dist/studio-host/index.d.mts +134 -92
- package/dist/studio-host/index.d.ts +134 -92
- package/dist/studio-host/index.mjs +6 -6
- package/package.json +11 -8
- package/dist/packem_shared/loadStudioAssets-Csk5RS4E.mjs +0 -28
- package/dist/packem_shared/parseDevVariable-CJiq2IwE.mjs +0 -30
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { writeFileSync } from 'node:fs';
|
|
2
2
|
import { containerBuildTag } from '@lunora/container';
|
|
3
|
-
import {
|
|
3
|
+
import { a as applyModify } from './jsonc-edit-SbcD7MAe.mjs';
|
|
4
4
|
import { findWranglerFile, readWranglerJsonc } from './WRANGLER_FILES-DwSuC-Kn.mjs';
|
|
5
5
|
|
|
6
|
-
const FORMATTING = { formattingOptions: { insertSpaces: true, tabSize: 4 } };
|
|
7
6
|
const D1_PLACEHOLDER_ID = "<replace-with-d1-create-id>";
|
|
8
7
|
const collectExportGaps = (inferred) => {
|
|
9
8
|
const gaps = [];
|
|
@@ -17,13 +16,19 @@ const collectExportGaps = (inferred) => {
|
|
|
17
16
|
gaps.push({ className: workflow.className, exportName: workflow.exportName, kind: "workflow", module: "workflows" });
|
|
18
17
|
}
|
|
19
18
|
}
|
|
19
|
+
for (const agent of inferred.agents) {
|
|
20
|
+
if (!agent.exported) {
|
|
21
|
+
gaps.push({ className: agent.className, exportName: agent.exportName, kind: "agent", module: "agents" });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
20
24
|
return gaps;
|
|
21
25
|
};
|
|
22
26
|
const collectHintBindingWarnings = (inferred, parsed) => {
|
|
27
|
+
const flagshipBindingMissing = inferred.flagshipBinding !== void 0 && !(parsed?.flagship ?? []).some((entry) => entry.binding === inferred.flagshipBinding);
|
|
23
28
|
const rules = [
|
|
24
29
|
[
|
|
25
30
|
inferred.usesKv && (parsed?.kv_namespaces?.length ?? 0) === 0,
|
|
26
|
-
"@lunora/kv is used but no kv_namespaces binding exists; add a kv_namespaces entry ({ binding, id }) and pass env.<BINDING> to createKv() — the namespace id can't be auto-provisioned."
|
|
31
|
+
"@lunora/bindings/kv is used but no kv_namespaces binding exists; add a kv_namespaces entry ({ binding, id }) and pass env.<BINDING> to createKv() — the namespace id can't be auto-provisioned."
|
|
27
32
|
],
|
|
28
33
|
[
|
|
29
34
|
inferred.usesHyperdrive && (parsed?.hyperdrive?.length ?? 0) === 0,
|
|
@@ -31,11 +36,31 @@ const collectHintBindingWarnings = (inferred, parsed) => {
|
|
|
31
36
|
],
|
|
32
37
|
[
|
|
33
38
|
inferred.usesPipelines && (parsed?.pipelines?.length ?? 0) === 0,
|
|
34
|
-
"
|
|
39
|
+
"ctx.pipelines is used but no pipelines binding exists; run 'wrangler pipelines create <name>' and add a 'pipelines' binding ({ binding, pipeline }) — the pipeline resource can't be auto-provisioned."
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
flagshipBindingMissing,
|
|
43
|
+
`lunora/flags.ts uses Flagship in binding mode but no flagship binding "${inferred.flagshipBinding ?? ""}" exists; add a flagship entry ({ binding: "${inferred.flagshipBinding ?? ""}", app_id }) — the app_id can't be auto-provisioned.`
|
|
35
44
|
]
|
|
36
45
|
];
|
|
37
46
|
return rules.filter(([active]) => active).map(([, warning]) => warning);
|
|
38
47
|
};
|
|
48
|
+
const collectX402Warnings = (inferred) => {
|
|
49
|
+
const rules = [
|
|
50
|
+
[
|
|
51
|
+
inferred.usesX402Charge,
|
|
52
|
+
"@lunora/x402/charge is used; set the recipient wallet address as a [vars] entry (the var name is your choice) and pass it to the charge config — the x402 facilitator settles USDC to that address."
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
inferred.usesX402Pay,
|
|
56
|
+
"@lunora/x402/pay is used (ActionCtx-only, spends real funds); add a secrets_store_secrets[] binding holding the agent wallet key (binding name == signer.secretName) and pair the pay rail with a spend policy — ctx.secrets reads a Secrets Store binding, not .dev.vars."
|
|
57
|
+
]
|
|
58
|
+
];
|
|
59
|
+
return rules.filter(([active]) => active).map(([, warning]) => warning);
|
|
60
|
+
};
|
|
61
|
+
const unexportedDeclarationWarnings = (kind, module, declarations) => declarations.filter((declaration) => !declaration.exported).map(
|
|
62
|
+
(declaration) => `${kind} "${declaration.exportName}" is declared but ${declaration.className} is not exported by the worker entry; add \`export * from "./lunora/_generated/${module}"\` so its binding can be provisioned.`
|
|
63
|
+
);
|
|
39
64
|
const collectWarnings = (inferred, parsed) => {
|
|
40
65
|
const exported = new Set(inferred.durableObjects.map((object) => object.className));
|
|
41
66
|
const warnings = [];
|
|
@@ -54,20 +79,11 @@ const collectWarnings = (inferred, parsed) => {
|
|
|
54
79
|
if (inferred.usesScheduler && !exported.has("SchedulerDO")) {
|
|
55
80
|
warnings.push("@lunora/scheduler is used but the worker entry exports no SchedulerDO; export it so the SCHEDULER binding can be provisioned.");
|
|
56
81
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
for (const workflow of inferred.workflows) {
|
|
65
|
-
if (!workflow.exported) {
|
|
66
|
-
warnings.push(
|
|
67
|
-
`workflow "${workflow.exportName}" is declared but ${workflow.className} is not exported by the worker entry; add \`export * from "./lunora/_generated/workflows"\` so its binding can be provisioned.`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
82
|
+
warnings.push(
|
|
83
|
+
...unexportedDeclarationWarnings("container", "containers", inferred.containers),
|
|
84
|
+
...unexportedDeclarationWarnings("workflow", "workflows", inferred.workflows),
|
|
85
|
+
...unexportedDeclarationWarnings("agent", "agents", inferred.agents)
|
|
86
|
+
);
|
|
71
87
|
if (inferred.containers.length > 0 && parsed?.observability?.enabled === false) {
|
|
72
88
|
warnings.push("containers are declared but observability is explicitly disabled in wrangler.jsonc — container logs will not be captured.");
|
|
73
89
|
}
|
|
@@ -76,13 +92,9 @@ const collectWarnings = (inferred, parsed) => {
|
|
|
76
92
|
"@lunora/payment is used; set the provider secrets in .dev.vars — STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET (Stripe) or POLAR_ACCESS_TOKEN + POLAR_WEBHOOK_SECRET (Polar)."
|
|
77
93
|
);
|
|
78
94
|
}
|
|
79
|
-
warnings.push(...collectHintBindingWarnings(inferred, parsed));
|
|
95
|
+
warnings.push(...collectX402Warnings(inferred), ...collectHintBindingWarnings(inferred, parsed));
|
|
80
96
|
return warnings;
|
|
81
97
|
};
|
|
82
|
-
const applyModify = (text, path, value) => {
|
|
83
|
-
const edits = modify(text, [...path], value, FORMATTING);
|
|
84
|
-
return edits.length > 0 ? applyEdits(text, edits) : text;
|
|
85
|
-
};
|
|
86
98
|
const nextMigrationTag = (migrations) => {
|
|
87
99
|
const used = new Set(migrations.map((migration) => migration.tag));
|
|
88
100
|
let index = 1;
|
|
@@ -212,15 +224,77 @@ const reconcileObservability = (text, parsed) => {
|
|
|
212
224
|
const workflowEntryFor = (workflow) => {
|
|
213
225
|
return { binding: workflow.bindingName, class_name: workflow.className, name: workflow.name };
|
|
214
226
|
};
|
|
215
|
-
const
|
|
227
|
+
const agentEntryFor = (agent) => {
|
|
228
|
+
return { binding: agent.bindingName, class_name: agent.className, name: agent.name };
|
|
229
|
+
};
|
|
230
|
+
const reconcileWorkflows = (text, parsed, workflows, agents = []) => {
|
|
216
231
|
const existing = parsed.workflows ?? [];
|
|
217
232
|
const existingClasses = new Set(existing.map((entry) => entry.class_name));
|
|
218
|
-
const
|
|
219
|
-
|
|
233
|
+
const missingWorkflows = workflows.filter((workflow) => !existingClasses.has(workflow.className));
|
|
234
|
+
const missingAgents = agents.filter((agent) => !existingClasses.has(agent.className));
|
|
235
|
+
if (missingWorkflows.length === 0 && missingAgents.length === 0) {
|
|
236
|
+
return { added: [], text };
|
|
237
|
+
}
|
|
238
|
+
const nextText = applyModify(
|
|
239
|
+
text,
|
|
240
|
+
["workflows"],
|
|
241
|
+
[...existing, ...missingWorkflows.map((workflow) => workflowEntryFor(workflow)), ...missingAgents.map((agent) => agentEntryFor(agent))]
|
|
242
|
+
);
|
|
243
|
+
return {
|
|
244
|
+
added: [...missingWorkflows.map((workflow) => `workflows/${workflow.className}`), ...missingAgents.map((agent) => `workflows/${agent.className}`)],
|
|
245
|
+
text: nextText
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
const reconcileQueues = (text, parsed, queues) => {
|
|
249
|
+
const existing = parsed.queues ?? {};
|
|
250
|
+
const existingProducers = existing.producers ?? [];
|
|
251
|
+
const existingConsumers = existing.consumers ?? [];
|
|
252
|
+
const haveProducer = new Set(existingProducers.map((entry) => entry.binding));
|
|
253
|
+
const haveConsumer = new Set(existingConsumers.map((entry) => entry.queue));
|
|
254
|
+
const missingProducers = queues.filter((queue) => !haveProducer.has(queue.bindingName));
|
|
255
|
+
const missingConsumers = queues.filter((queue) => !haveConsumer.has(queue.name));
|
|
256
|
+
if (missingProducers.length === 0 && missingConsumers.length === 0) {
|
|
220
257
|
return { added: [], text };
|
|
221
258
|
}
|
|
222
|
-
const
|
|
223
|
-
|
|
259
|
+
const nextProducers = [
|
|
260
|
+
...existingProducers,
|
|
261
|
+
...missingProducers.map((queue) => {
|
|
262
|
+
return { binding: queue.bindingName, queue: queue.name };
|
|
263
|
+
})
|
|
264
|
+
];
|
|
265
|
+
const nextConsumers = [
|
|
266
|
+
...existingConsumers,
|
|
267
|
+
...missingConsumers.map((queue) => {
|
|
268
|
+
const consumer = { queue: queue.name };
|
|
269
|
+
if (queue.mode === "pull") {
|
|
270
|
+
consumer.type = "http_pull";
|
|
271
|
+
}
|
|
272
|
+
if (queue.tuning.maxBatchSize !== void 0) {
|
|
273
|
+
consumer.max_batch_size = queue.tuning.maxBatchSize;
|
|
274
|
+
}
|
|
275
|
+
if (queue.tuning.maxBatchTimeout !== void 0) {
|
|
276
|
+
consumer.max_batch_timeout = queue.tuning.maxBatchTimeout;
|
|
277
|
+
}
|
|
278
|
+
if (queue.tuning.maxRetries !== void 0) {
|
|
279
|
+
consumer.max_retries = queue.tuning.maxRetries;
|
|
280
|
+
}
|
|
281
|
+
if (queue.tuning.deadLetterQueue !== void 0) {
|
|
282
|
+
consumer.dead_letter_queue = queue.tuning.deadLetterQueue;
|
|
283
|
+
}
|
|
284
|
+
if (queue.tuning.retryDelay !== void 0) {
|
|
285
|
+
consumer.retry_delay = queue.tuning.retryDelay;
|
|
286
|
+
}
|
|
287
|
+
return consumer;
|
|
288
|
+
})
|
|
289
|
+
];
|
|
290
|
+
const nextText = applyModify(text, ["queues"], { consumers: nextConsumers, producers: nextProducers });
|
|
291
|
+
return {
|
|
292
|
+
added: [
|
|
293
|
+
...missingProducers.map((queue) => `queues.producers/${queue.bindingName}`),
|
|
294
|
+
...missingConsumers.map((queue) => `queues.consumers/${queue.name}`)
|
|
295
|
+
],
|
|
296
|
+
text: nextText
|
|
297
|
+
};
|
|
224
298
|
};
|
|
225
299
|
const reconcileWranglerBindings = (projectRoot, inferred) => {
|
|
226
300
|
const wranglerPath = findWranglerFile(projectRoot);
|
|
@@ -234,13 +308,20 @@ const reconcileWranglerBindings = (projectRoot, inferred) => {
|
|
|
234
308
|
}
|
|
235
309
|
const warnings = collectWarnings(inferred, parsed);
|
|
236
310
|
const exportedContainers = inferred.containers.filter((container) => container.exported);
|
|
311
|
+
const voiceAgents = inferred.agents.filter(
|
|
312
|
+
(agent) => agent.exported && agent.voice === true && agent.voiceBindingName !== void 0 && agent.voiceClassName !== void 0
|
|
313
|
+
);
|
|
237
314
|
const requiredDurableObjects = [
|
|
238
315
|
...inferred.durableObjects,
|
|
239
316
|
...exportedContainers.map((container) => {
|
|
240
317
|
return { binding: container.bindingName, className: container.className };
|
|
318
|
+
}),
|
|
319
|
+
...voiceAgents.map((agent) => {
|
|
320
|
+
return { binding: agent.voiceBindingName, className: agent.voiceClassName };
|
|
241
321
|
})
|
|
242
322
|
];
|
|
243
323
|
const exportedWorkflows = inferred.workflows.filter((workflow) => workflow.exported);
|
|
324
|
+
const exportedAgents = inferred.agents.filter((agent) => agent.exported);
|
|
244
325
|
const pipeline = [
|
|
245
326
|
{ enabled: true, run: (text2) => reconcileDurableObjects(text2, parsed, requiredDurableObjects) },
|
|
246
327
|
{ enabled: inferred.needsD1, run: (text2) => reconcileD1(text2, parsed) },
|
|
@@ -250,7 +331,11 @@ const reconcileWranglerBindings = (projectRoot, inferred) => {
|
|
|
250
331
|
{ enabled: inferred.usesAnalytics, run: (text2) => reconcileAnalytics(text2, parsed) },
|
|
251
332
|
{ enabled: true, run: (text2) => reconcileObservability(text2, parsed) },
|
|
252
333
|
{ enabled: exportedContainers.length > 0, run: (text2) => reconcileContainers(text2, parsed, exportedContainers) },
|
|
253
|
-
{
|
|
334
|
+
{
|
|
335
|
+
enabled: exportedWorkflows.length > 0 || exportedAgents.length > 0,
|
|
336
|
+
run: (text2) => reconcileWorkflows(text2, parsed, exportedWorkflows, exportedAgents)
|
|
337
|
+
},
|
|
338
|
+
{ enabled: inferred.queues.length > 0, run: (text2) => reconcileQueues(text2, parsed, inferred.queues) }
|
|
254
339
|
];
|
|
255
340
|
let text = original;
|
|
256
341
|
const added = [];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { modify, applyEdits } from 'jsonc-parser';
|
|
3
|
+
import { F as FORMATTING } from './jsonc-edit-SbcD7MAe.mjs';
|
|
4
|
+
import { isCacheEnabled, WORKERS_CACHE_MIN_DATE } from './WORKERS_CACHE_MIN_DATE-CaXbS7CX.mjs';
|
|
5
|
+
import { findWranglerFile, readWranglerJsonc } from './WRANGLER_FILES-DwSuC-Kn.mjs';
|
|
6
|
+
|
|
7
|
+
const reconcileWranglerCompatibilityDate = (projectRoot) => {
|
|
8
|
+
const wranglerPath = findWranglerFile(projectRoot);
|
|
9
|
+
if (!wranglerPath) {
|
|
10
|
+
return { changed: false, date: void 0, reason: "wrangler.jsonc not found" };
|
|
11
|
+
}
|
|
12
|
+
const { parsed, text } = readWranglerJsonc(wranglerPath);
|
|
13
|
+
if (parsed === void 0) {
|
|
14
|
+
return { changed: false, date: void 0, reason: `failed to parse ${wranglerPath} as JSONC`, wranglerPath };
|
|
15
|
+
}
|
|
16
|
+
const currentDate = parsed.compatibility_date ?? "";
|
|
17
|
+
if (!isCacheEnabled(parsed)) {
|
|
18
|
+
return { changed: false, date: currentDate || void 0, reason: "cache not enabled", wranglerPath };
|
|
19
|
+
}
|
|
20
|
+
if (currentDate >= WORKERS_CACHE_MIN_DATE) {
|
|
21
|
+
return { changed: false, date: currentDate, reason: `compatibility_date already >= ${WORKERS_CACHE_MIN_DATE}`, wranglerPath };
|
|
22
|
+
}
|
|
23
|
+
const edits = modify(text, ["compatibility_date"], WORKERS_CACHE_MIN_DATE, FORMATTING);
|
|
24
|
+
if (edits.length === 0) {
|
|
25
|
+
return { changed: false, date: currentDate || void 0, reason: "no structural edit produced", wranglerPath };
|
|
26
|
+
}
|
|
27
|
+
writeFileSync(wranglerPath, applyEdits(text, edits), "utf8");
|
|
28
|
+
return { changed: true, date: WORKERS_CACHE_MIN_DATE, wranglerPath };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { reconcileWranglerCompatibilityDate };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { modify, applyEdits } from 'jsonc-parser';
|
|
3
|
+
import { findWranglerFile, readWranglerJsonc } from './WRANGLER_FILES-DwSuC-Kn.mjs';
|
|
4
|
+
|
|
5
|
+
const sameTriggers = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
|
|
6
|
+
const reconcileWranglerCrons = (projectRoot, cronTriggers) => {
|
|
7
|
+
const wranglerPath = findWranglerFile(projectRoot);
|
|
8
|
+
if (!wranglerPath) {
|
|
9
|
+
return { changed: false, reason: "wrangler.jsonc not found" };
|
|
10
|
+
}
|
|
11
|
+
const { parsed, text } = readWranglerJsonc(wranglerPath);
|
|
12
|
+
if (parsed === void 0) {
|
|
13
|
+
return { changed: false, reason: `failed to parse ${wranglerPath} as JSONC`, wranglerPath };
|
|
14
|
+
}
|
|
15
|
+
const existing = Array.isArray(parsed.triggers?.crons) ? parsed.triggers.crons.filter((value) => typeof value === "string") : [];
|
|
16
|
+
if (sameTriggers(existing, cronTriggers)) {
|
|
17
|
+
return { changed: false, reason: "triggers.crons already in sync", wranglerPath };
|
|
18
|
+
}
|
|
19
|
+
const edits = modify(text, ["triggers", "crons"], [...cronTriggers], {
|
|
20
|
+
formattingOptions: { insertSpaces: true, tabSize: 4 }
|
|
21
|
+
});
|
|
22
|
+
if (edits.length === 0) {
|
|
23
|
+
return { changed: false, reason: "no structural edit produced", wranglerPath };
|
|
24
|
+
}
|
|
25
|
+
writeFileSync(wranglerPath, applyEdits(text, edits), "utf8");
|
|
26
|
+
return { changed: true, wranglerPath };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export { reconcileWranglerCrons };
|
|
@@ -57,7 +57,7 @@ const csrfRejectionReason = (request) => {
|
|
|
57
57
|
}
|
|
58
58
|
return void 0;
|
|
59
59
|
};
|
|
60
|
-
const serveJsonHandler = (request, response, handle, projectRoot) => {
|
|
60
|
+
const serveJsonHandler = (request, response, handle, projectRoot, schemaDirectory) => {
|
|
61
61
|
const run = async () => {
|
|
62
62
|
try {
|
|
63
63
|
const rejection = csrfRejectionReason(request);
|
|
@@ -73,7 +73,7 @@ const serveJsonHandler = (request, response, handle, projectRoot) => {
|
|
|
73
73
|
respondJson(response, 400, { error: "invalid-json", ok: false });
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
|
-
const result = handle({ body: parsed, method: request.method ?? "POST", projectRoot });
|
|
76
|
+
const result = handle({ body: parsed, method: request.method ?? "POST", projectRoot, schemaDirectory });
|
|
77
77
|
respondJson(response, result.status, result.body);
|
|
78
78
|
} catch (error) {
|
|
79
79
|
respondJson(response, 500, { error: error instanceof Error ? error.message : String(error), ok: false });
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
3
|
+
|
|
4
|
+
const DEV_CONTAINER_IMAGE_PREFIX = "cloudflare-dev/";
|
|
5
|
+
const DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
6
|
+
const createDefaultDocker = async () => {
|
|
7
|
+
const { default: Dockerode } = await import('dockerode');
|
|
8
|
+
return new Dockerode();
|
|
9
|
+
};
|
|
10
|
+
const classFromImage = (image) => {
|
|
11
|
+
if (!image.startsWith(DEV_CONTAINER_IMAGE_PREFIX)) {
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
14
|
+
const [segment] = image.slice(DEV_CONTAINER_IMAGE_PREFIX.length).split(":");
|
|
15
|
+
return segment !== void 0 && segment.length > 0 ? segment : void 0;
|
|
16
|
+
};
|
|
17
|
+
const lineBufferWritable = (emit) => {
|
|
18
|
+
const decoder = new StringDecoder("utf8");
|
|
19
|
+
let buffer = "";
|
|
20
|
+
const flushLine = (line) => {
|
|
21
|
+
emit(line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
22
|
+
};
|
|
23
|
+
return new Writable({
|
|
24
|
+
final(callback) {
|
|
25
|
+
buffer += decoder.end();
|
|
26
|
+
if (buffer.length > 0) {
|
|
27
|
+
flushLine(buffer);
|
|
28
|
+
buffer = "";
|
|
29
|
+
}
|
|
30
|
+
callback();
|
|
31
|
+
},
|
|
32
|
+
write(chunk, _encoding, callback) {
|
|
33
|
+
buffer += decoder.write(chunk);
|
|
34
|
+
const lines = buffer.split("\n");
|
|
35
|
+
buffer = lines.pop() ?? "";
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
flushLine(line);
|
|
38
|
+
}
|
|
39
|
+
callback();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
const streamContainerLogs = (options) => {
|
|
44
|
+
const classToExport = new Map(options.containers.map((source) => [source.className.toLowerCase(), source.exportName]));
|
|
45
|
+
if (classToExport.size === 0) {
|
|
46
|
+
return { close: () => {
|
|
47
|
+
} };
|
|
48
|
+
}
|
|
49
|
+
const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
50
|
+
const attached = /* @__PURE__ */ new Map();
|
|
51
|
+
let closed = false;
|
|
52
|
+
let unavailableNotified = false;
|
|
53
|
+
let timer;
|
|
54
|
+
let dockerPromise;
|
|
55
|
+
const getDocker = async () => {
|
|
56
|
+
dockerPromise ??= options.docker ? Promise.resolve(options.docker) : createDefaultDocker();
|
|
57
|
+
return dockerPromise;
|
|
58
|
+
};
|
|
59
|
+
const attach = async (docker, id, exportName) => {
|
|
60
|
+
try {
|
|
61
|
+
const stream = await docker.getContainer(id).logs({ follow: true, stderr: true, stdout: true, tail: "all", timestamps: false });
|
|
62
|
+
if (closed) {
|
|
63
|
+
stream.destroy();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const stdout = lineBufferWritable((text) => {
|
|
67
|
+
options.onLine({ level: "info", name: exportName, text });
|
|
68
|
+
});
|
|
69
|
+
const stderr = lineBufferWritable((text) => {
|
|
70
|
+
options.onLine({ level: "error", name: exportName, text });
|
|
71
|
+
});
|
|
72
|
+
docker.modem.demuxStream(stream, stdout, stderr);
|
|
73
|
+
stream.on("end", () => {
|
|
74
|
+
stdout.end();
|
|
75
|
+
stderr.end();
|
|
76
|
+
attached.delete(id);
|
|
77
|
+
});
|
|
78
|
+
stream.on("error", () => {
|
|
79
|
+
attached.delete(id);
|
|
80
|
+
});
|
|
81
|
+
attached.set(id, stream);
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const listRunning = async () => {
|
|
86
|
+
try {
|
|
87
|
+
const docker = await getDocker();
|
|
88
|
+
const running = await docker.listContainers({ filters: { status: ["running"] } });
|
|
89
|
+
unavailableNotified = false;
|
|
90
|
+
return running;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (!unavailableNotified) {
|
|
93
|
+
unavailableNotified = true;
|
|
94
|
+
options.onUnavailable?.(error instanceof Error ? error.message : String(error));
|
|
95
|
+
}
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const reconcile = async (docker, running) => {
|
|
100
|
+
const live = /* @__PURE__ */ new Set();
|
|
101
|
+
for (const summary of running) {
|
|
102
|
+
const className = classFromImage(summary.Image);
|
|
103
|
+
const exportName = className === void 0 ? void 0 : classToExport.get(className);
|
|
104
|
+
if (exportName === void 0) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
live.add(summary.Id);
|
|
108
|
+
if (!attached.has(summary.Id)) {
|
|
109
|
+
await attach(docker, summary.Id, exportName);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const [id, stream] of attached) {
|
|
113
|
+
if (!live.has(id)) {
|
|
114
|
+
stream.destroy();
|
|
115
|
+
attached.delete(id);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const poll = async () => {
|
|
120
|
+
if (closed) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const running = await listRunning();
|
|
124
|
+
if (running === void 0) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
await reconcile(await getDocker(), running);
|
|
128
|
+
};
|
|
129
|
+
let polling = false;
|
|
130
|
+
const onTick = () => {
|
|
131
|
+
if (polling || closed) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
polling = true;
|
|
135
|
+
poll().finally(() => {
|
|
136
|
+
polling = false;
|
|
137
|
+
}).catch(() => void 0);
|
|
138
|
+
};
|
|
139
|
+
timer = setInterval(onTick, interval);
|
|
140
|
+
timer.unref();
|
|
141
|
+
onTick();
|
|
142
|
+
return {
|
|
143
|
+
close: () => {
|
|
144
|
+
closed = true;
|
|
145
|
+
if (timer) {
|
|
146
|
+
clearInterval(timer);
|
|
147
|
+
timer = void 0;
|
|
148
|
+
}
|
|
149
|
+
for (const [id, stream] of attached) {
|
|
150
|
+
stream.destroy();
|
|
151
|
+
attached.delete(id);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export { streamContainerLogs };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { writeFileSync, renameSync, rmSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
const writeFileAtomic = (path, text) => {
|
|
4
|
+
const temporaryPath = `${path}.lunora-tmp`;
|
|
5
|
+
try {
|
|
6
|
+
writeFileSync(temporaryPath, text, "utf8");
|
|
7
|
+
renameSync(temporaryPath, path);
|
|
8
|
+
} catch (error) {
|
|
9
|
+
rmSync(temporaryPath, { force: true });
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export { writeFileAtomic as w };
|