@lunora/config 1.0.0-alpha.7 → 1.0.0-alpha.70
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 +466 -10
- package/dist/index.d.ts +466 -10
- package/dist/index.mjs +17 -9
- 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/{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/{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 +45 -3
- package/dist/studio-host/index.d.ts +45 -3
- 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
package/dist/packem_shared/{inferLunoraBindings-XNJH4pup.mjs → inferLunoraBindings-BcvvtpSG.mjs}
RENAMED
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
import { existsSync, statSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { init, parse } from 'es-module-lexer';
|
|
3
|
+
import { discoverAgentInfo } from './discoverAgentInfo-eXA3hF4v.mjs';
|
|
3
4
|
import { discoverContainerInfo } from './discoverContainerInfo-BXFs6Wav.mjs';
|
|
4
|
-
import {
|
|
5
|
+
import { FLAGS_FILENAME, discoverFlags, QUEUES_FILENAME, discoverQueues } from '@lunora/codegen';
|
|
6
|
+
import { Project } from 'ts-morph';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { discoverSchemaInfo } from './discoverSchemaInfo-BB-CKlTK.mjs';
|
|
5
9
|
import { discoverWorkflowInfo } from './discoverWorkflowInfo-CedvR0mn.mjs';
|
|
6
10
|
import { WRANGLER_FILES, readWranglerJsonc } from './WRANGLER_FILES-DwSuC-Kn.mjs';
|
|
7
|
-
|
|
11
|
+
|
|
12
|
+
const discoverFlagsInfo = (projectRoot, schemaDirectory) => {
|
|
13
|
+
const flagsPath = join(projectRoot, schemaDirectory, FLAGS_FILENAME);
|
|
14
|
+
if (!existsSync(flagsPath)) {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const project = new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
|
|
19
|
+
return { flags: discoverFlags(project, join(projectRoot, schemaDirectory)) };
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const discoverQueueInfo = (projectRoot, schemaDirectory) => {
|
|
26
|
+
const queuesPath = join(projectRoot, schemaDirectory, QUEUES_FILENAME);
|
|
27
|
+
if (!existsSync(queuesPath)) {
|
|
28
|
+
return { queues: [] };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const project = new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
|
|
32
|
+
return { queues: discoverQueues(project, join(projectRoot, schemaDirectory)) };
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return { error: error instanceof Error ? error.message : String(error), queues: [] };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
8
37
|
|
|
9
38
|
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cjs", ".cts", ".js", ".jsx", ".mjs", ".mts", ".ts", ".tsx"]);
|
|
10
39
|
const IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".lunora-cache", ".wrangler", "_generated", "dist", "node_modules"]);
|
|
@@ -23,20 +52,60 @@ const TYPE_ONLY_EXPORT_PATTERNS = {
|
|
|
23
52
|
};
|
|
24
53
|
const ENV_DB_PATTERN = /\benv\s*\.\s*DB\b/;
|
|
25
54
|
const ENV_AI_PATTERN = /\benv\s*\.\s*AI\b/;
|
|
55
|
+
const CTX_PIPELINES_PATTERN = /\bctx\s*\.\s*pipelines\b/;
|
|
26
56
|
const TYPE_ONLY_IMPORT_PATTERN = /^\s*import\s+type\b/;
|
|
57
|
+
const SANDBOX_MODULE_SPECIFIERS = /* @__PURE__ */ new Set(["@lunora/agent", "@lunora/agent/sandbox"]);
|
|
58
|
+
const extractImportSpecifierList = (statementText) => {
|
|
59
|
+
const openBraceIndex = statementText.indexOf("{");
|
|
60
|
+
if (openBraceIndex === -1) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
const closeBraceIndex = statementText.indexOf("}", openBraceIndex + 1);
|
|
64
|
+
return closeBraceIndex === -1 ? statementText.slice(openBraceIndex + 1) : statementText.slice(openBraceIndex + 1, closeBraceIndex);
|
|
65
|
+
};
|
|
66
|
+
const TYPE_BROWSER_TOOL_SPECIFIER_PATTERN = /\btype\s+browserTool\b/;
|
|
67
|
+
const BROWSER_TOOL_NAME_PATTERN = /\bbrowserTool\b/;
|
|
68
|
+
const SANDBOX_BROWSER_TOOL_FALLBACK_PATTERN = /import\s+\{[^}]*\bbrowserTool\b[^}]*\}\s+from\s+["']@lunora\/agent(?:\/sandbox)?["']/;
|
|
69
|
+
const isValueBrowserToolImport = (statementText) => {
|
|
70
|
+
if (TYPE_ONLY_IMPORT_PATTERN.test(statementText)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const specifierList = extractImportSpecifierList(statementText);
|
|
74
|
+
return BROWSER_TOOL_NAME_PATTERN.test(specifierList) && !TYPE_BROWSER_TOOL_SPECIFIER_PATTERN.test(specifierList);
|
|
75
|
+
};
|
|
76
|
+
const hasSandboxBrowserToolImport = (code) => {
|
|
77
|
+
try {
|
|
78
|
+
const [imports] = parse(code);
|
|
79
|
+
return imports.some(
|
|
80
|
+
(entry) => entry.n !== void 0 && SANDBOX_MODULE_SPECIFIERS.has(entry.n) && isValueBrowserToolImport(code.slice(entry.ss, entry.se))
|
|
81
|
+
);
|
|
82
|
+
} catch {
|
|
83
|
+
return SANDBOX_BROWSER_TOOL_FALLBACK_PATTERN.test(code);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
27
86
|
const CAPABILITY_SOURCES = {
|
|
28
87
|
usesAi: { pattern: /\bfrom\s+["']@lunora\/ai["']/, source: "@lunora/ai" },
|
|
29
|
-
usesAnalytics: { pattern: /\bfrom\s+["']@lunora\/analytics["']/, source: "@lunora/analytics" },
|
|
88
|
+
usesAnalytics: { pattern: /\bfrom\s+["']@lunora\/bindings\/analytics["']/, source: "@lunora/bindings/analytics" },
|
|
30
89
|
usesAuth: { pattern: /\bfrom\s+["']@lunora\/auth["']/, source: "@lunora/auth" },
|
|
31
90
|
usesBrowser: { pattern: /\bfrom\s+["']@lunora\/browser["']/, source: "@lunora/browser" },
|
|
32
91
|
usesHyperdrive: { pattern: /\bfrom\s+["']@lunora\/hyperdrive["']/, source: "@lunora/hyperdrive" },
|
|
33
|
-
usesImages: { pattern: /\bfrom\s+["']@lunora\/images["']/, source: "@lunora/images" },
|
|
34
|
-
usesKv: { pattern: /\bfrom\s+["']@lunora\/kv["']/, source: "@lunora/kv" },
|
|
92
|
+
usesImages: { pattern: /\bfrom\s+["']@lunora\/bindings\/images["']/, source: "@lunora/bindings/images" },
|
|
93
|
+
usesKv: { pattern: /\bfrom\s+["']@lunora\/bindings\/kv["']/, source: "@lunora/bindings/kv" },
|
|
35
94
|
usesMail: { pattern: /\bfrom\s+["']@lunora\/mail["']/, source: "@lunora/mail" },
|
|
36
95
|
usesPayment: { pattern: /\bfrom\s+["']@lunora\/payment["']/, source: "@lunora/payment" },
|
|
37
|
-
|
|
96
|
+
// Keyed off the `ctx.pipelines` access (not an import) — see CTX_PIPELINES_PATTERN.
|
|
97
|
+
// Pipelines is codegen-wired onto ActionCtx, so apps reach it via `ctx.pipelines`
|
|
98
|
+
// rather than importing `@lunora/bindings/pipelines`; `source` names that subpath
|
|
99
|
+
// for the hint message.
|
|
100
|
+
usesPipelines: { pattern: CTX_PIPELINES_PATTERN, source: "@lunora/bindings/pipelines" },
|
|
38
101
|
usesScheduler: { pattern: /\bfrom\s+["']@lunora\/scheduler["']/, source: "@lunora/scheduler" },
|
|
39
|
-
usesStorage: { pattern: /\bfrom\s+["']@lunora\/storage["']/, source: "@lunora/storage" }
|
|
102
|
+
usesStorage: { pattern: /\bfrom\s+["']@lunora\/storage["']/, source: "@lunora/storage" },
|
|
103
|
+
// x402 rails are opt-in add-on subpaths (not part of the `lunorash` umbrella),
|
|
104
|
+
// so they key off the exact `@lunora/x402/{charge,pay}` specifiers. Neither
|
|
105
|
+
// implies a `.dev.vars` secret: the charge recipient is a user-named `[vars]`
|
|
106
|
+
// entry and the pay wallet key is a Secrets Store binding — both hint-only.
|
|
107
|
+
usesX402Charge: { pattern: /\bfrom\s+["']@lunora\/x402\/charge["']/, source: "@lunora/x402/charge" },
|
|
108
|
+
usesX402Pay: { pattern: /\bfrom\s+["']@lunora\/x402\/pay["']/, source: "@lunora/x402/pay" }
|
|
40
109
|
};
|
|
41
110
|
const CAPABILITY_FLAGS = Object.keys(CAPABILITY_SOURCES);
|
|
42
111
|
const PAYMENT_PROVIDER_SECRETS = "STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET (Stripe) or POLAR_ACCESS_TOKEN + POLAR_WEBHOOK_SECRET (Polar)";
|
|
@@ -90,7 +159,12 @@ const capabilitiesFromSource = (code) => {
|
|
|
90
159
|
} catch {
|
|
91
160
|
capabilities = regexCapabilities(code);
|
|
92
161
|
}
|
|
93
|
-
return mergeCapabilities(capabilities, {
|
|
162
|
+
return mergeCapabilities(capabilities, {
|
|
163
|
+
...NO_CAPABILITIES,
|
|
164
|
+
needsD1: ENV_DB_PATTERN.test(code),
|
|
165
|
+
usesAi: ENV_AI_PATTERN.test(code),
|
|
166
|
+
usesPipelines: CTX_PIPELINES_PATTERN.test(code)
|
|
167
|
+
});
|
|
94
168
|
};
|
|
95
169
|
const collectSourceFiles = (directory, accumulator) => {
|
|
96
170
|
let entries;
|
|
@@ -149,56 +223,39 @@ const detectExportedDurableObjects = (entryPath) => {
|
|
|
149
223
|
};
|
|
150
224
|
});
|
|
151
225
|
};
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return { ...container, exported: false };
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
const code = readFileSync(entryPath, "utf8");
|
|
163
|
-
const starReexport = CONTAINERS_STAR_REEXPORT_PATTERN.test(code);
|
|
164
|
-
let exportedNames;
|
|
165
|
-
try {
|
|
166
|
-
const [, exports] = parse(code);
|
|
167
|
-
exportedNames = new Set(exports.map((entry) => entry.n));
|
|
168
|
-
} catch {
|
|
169
|
-
exportedNames = new Set(
|
|
170
|
-
containers.map((container) => container.className).filter((className) => new RegExp(String.raw`\bexport\b[^\n;]*\b${className}\b`).test(code))
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
return containers.map((container) => {
|
|
174
|
-
return { ...container, exported: starReexport || exportedNames.has(container.className) };
|
|
175
|
-
});
|
|
176
|
-
};
|
|
177
|
-
const WORKFLOWS_STAR_REEXPORT_PATTERN = /\bexport\s*\*\s*from\s*["'][^"']*_generated\/workflows(?:\.js)?["']/;
|
|
178
|
-
const detectWorkflowExports = (entryPath, workflows) => {
|
|
179
|
-
if (workflows.length === 0) {
|
|
226
|
+
const isTypeOnlyClassExport = (code, className) => (
|
|
227
|
+
// `export type Foo` / `export type { … Foo … }` / `export { type Foo }` — all
|
|
228
|
+
// three type-only forms list the name via the lexer but compile away.
|
|
229
|
+
new RegExp(String.raw`\bexport\s+type\s+${className}\b`).test(code) || new RegExp(String.raw`\bexport\s+type\s*\{[^}]*\b${className}\b`).test(code) || new RegExp(String.raw`\bexport\s+\{[^}]*\btype\s+${className}\b`).test(code)
|
|
230
|
+
);
|
|
231
|
+
const detectClassExports = (entryPath, definitions, generatedModule) => {
|
|
232
|
+
if (definitions.length === 0) {
|
|
180
233
|
return [];
|
|
181
234
|
}
|
|
182
235
|
if (entryPath === void 0) {
|
|
183
|
-
return
|
|
184
|
-
return { ...
|
|
236
|
+
return definitions.map((definition) => {
|
|
237
|
+
return { ...definition, exported: false };
|
|
185
238
|
});
|
|
186
239
|
}
|
|
187
240
|
const code = readFileSync(entryPath, "utf8");
|
|
188
|
-
const starReexport =
|
|
241
|
+
const starReexport = new RegExp(String.raw`\bexport\s*\*\s*from\s*["'][^"']*_generated\/${generatedModule}(?:\.js)?["']`).test(code);
|
|
189
242
|
let exportedNames;
|
|
190
243
|
try {
|
|
191
244
|
const [, exports] = parse(code);
|
|
192
245
|
exportedNames = new Set(exports.map((entry) => entry.n));
|
|
193
246
|
} catch {
|
|
194
247
|
exportedNames = new Set(
|
|
195
|
-
|
|
248
|
+
definitions.map((definition) => definition.className).filter((className) => new RegExp(String.raw`\bexport\b[^\n;]*\b${className}\b`).test(code))
|
|
196
249
|
);
|
|
197
250
|
}
|
|
198
|
-
return
|
|
199
|
-
|
|
251
|
+
return definitions.map((definition) => {
|
|
252
|
+
const exported = starReexport || exportedNames.has(definition.className) && !isTypeOnlyClassExport(code, definition.className);
|
|
253
|
+
return { ...definition, exported };
|
|
200
254
|
});
|
|
201
255
|
};
|
|
256
|
+
const detectContainerExports = (entryPath, containers) => detectClassExports(entryPath, containers, "containers");
|
|
257
|
+
const detectWorkflowExports = (entryPath, workflows) => detectClassExports(entryPath, workflows, "workflows");
|
|
258
|
+
const detectAgentExports = (entryPath, agents) => detectClassExports(entryPath, agents, "agents");
|
|
202
259
|
const schemaNeedsD1 = (projectRoot, schemaDirectory) => discoverSchemaInfo(projectRoot, schemaDirectory).info?.hasGlobalTable ?? false;
|
|
203
260
|
const scanCapabilities = (projectRoot, scanDirectories) => {
|
|
204
261
|
let merged = NO_CAPABILITIES;
|
|
@@ -215,12 +272,24 @@ const scanCapabilities = (projectRoot, scanDirectories) => {
|
|
|
215
272
|
}
|
|
216
273
|
return merged;
|
|
217
274
|
};
|
|
218
|
-
const
|
|
275
|
+
const scanSandboxBrowserToolUsage = (projectRoot, lunoraDirectory) => {
|
|
276
|
+
const absolute = join(projectRoot, lunoraDirectory);
|
|
277
|
+
if (!existsSync(absolute) || !statSync(absolute).isDirectory()) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
const files = [];
|
|
281
|
+
collectSourceFiles(absolute, files);
|
|
282
|
+
return files.some((file) => hasSandboxBrowserToolImport(readFileSync(file, "utf8")));
|
|
283
|
+
};
|
|
284
|
+
const describeDeclaredExports = (containers, workflows, agents) => [
|
|
219
285
|
...containers.map(
|
|
220
286
|
(container) => container.exported ? `${container.bindingName}/${container.className} (container "${container.exportName}" declared and exported)` : `hint: container "${container.exportName}" is declared but ${container.className} is not exported by the worker entry — add \`export * from "./lunora/_generated/containers"\``
|
|
221
287
|
),
|
|
222
288
|
...workflows.map(
|
|
223
289
|
(workflow) => workflow.exported ? `${workflow.bindingName}/${workflow.className} (workflow "${workflow.exportName}" declared and exported)` : `hint: workflow "${workflow.exportName}" is declared but ${workflow.className} is not exported by the worker entry — add \`export * from "./lunora/_generated/workflows"\``
|
|
290
|
+
),
|
|
291
|
+
...agents.map(
|
|
292
|
+
(agent) => agent.exported ? `${agent.bindingName}/${agent.className} (agent "${agent.exportName}" declared and exported)` : `hint: agent "${agent.exportName}" is declared but ${agent.className} is not exported by the worker entry — add \`export * from "./lunora/_generated/agents"\``
|
|
224
293
|
)
|
|
225
294
|
];
|
|
226
295
|
const describeCapabilitySignals = (capabilities, exported) => {
|
|
@@ -237,14 +306,14 @@ const describeCapabilitySignals = (capabilities, exported) => {
|
|
|
237
306
|
// Self-describing bindings: the binding name is the whole config (no remote
|
|
238
307
|
// id to mint), so reconcile auto-writes them like the DO/D1 bindings.
|
|
239
308
|
[capabilities.usesBrowser, "browser (@lunora/browser imported) — self-describing { binding: BROWSER }"],
|
|
240
|
-
[capabilities.usesImages, "images (@lunora/images imported) — self-describing { binding: IMAGES }"],
|
|
241
|
-
[capabilities.usesAnalytics, "analytics_engine_datasets (@lunora/analytics imported) — self-describing { binding: ANALYTICS, dataset }"],
|
|
309
|
+
[capabilities.usesImages, "images (@lunora/bindings/images imported) — self-describing { binding: IMAGES }"],
|
|
310
|
+
[capabilities.usesAnalytics, "analytics_engine_datasets (@lunora/bindings/analytics imported) — self-describing { binding: ANALYTICS, dataset }"],
|
|
242
311
|
// Hint bindings: each needs a remote resource Lunora can't fabricate (a KV
|
|
243
312
|
// namespace id, a Hyperdrive id, a Pipelines pipeline name), so they surface
|
|
244
313
|
// as hints — never an auto-write — exactly like R2's user-defined bucket name.
|
|
245
314
|
[
|
|
246
315
|
capabilities.usesKv,
|
|
247
|
-
"hint: @lunora/kv is imported; add a kv_namespaces binding ({ binding, id }) and pass env.<BINDING> to createKv() — the namespace id can't be auto-provisioned"
|
|
316
|
+
"hint: @lunora/bindings/kv is imported; add a kv_namespaces binding ({ binding, id }) and pass env.<BINDING> to createKv() — the namespace id can't be auto-provisioned"
|
|
248
317
|
],
|
|
249
318
|
[
|
|
250
319
|
capabilities.usesHyperdrive,
|
|
@@ -252,39 +321,65 @@ const describeCapabilitySignals = (capabilities, exported) => {
|
|
|
252
321
|
],
|
|
253
322
|
[
|
|
254
323
|
capabilities.usesPipelines,
|
|
255
|
-
"hint:
|
|
324
|
+
"hint: ctx.pipelines is used; run 'wrangler pipelines create <name>' and add a 'pipelines' binding ({ binding, pipeline }) — the pipeline resource can't be auto-provisioned"
|
|
325
|
+
],
|
|
326
|
+
[
|
|
327
|
+
capabilities.usesX402Charge,
|
|
328
|
+
"hint: @lunora/x402/charge is imported; set the recipient wallet address as a [vars] entry (the var name is yours to choose) and pass it to the charge config — the x402 facilitator settles USDC to that address"
|
|
329
|
+
],
|
|
330
|
+
[
|
|
331
|
+
capabilities.usesX402Pay,
|
|
332
|
+
"hint: @lunora/x402/pay is imported (ActionCtx-only, spends real funds); add a secrets_store_secrets[] binding for the agent wallet key (name it to match signer.secretName) and pair the pay rail with a spend policy — ctx.secrets reads a Secrets Store binding, not .dev.vars, so the key can't be auto-provisioned"
|
|
256
333
|
]
|
|
257
334
|
];
|
|
258
335
|
return rules.filter(([active]) => active).map(([, signal]) => signal);
|
|
259
336
|
};
|
|
260
|
-
const describeSignals = (durableObjects, needsD1, capabilities, containers = [], workflows = []) => {
|
|
337
|
+
const describeSignals = (durableObjects, needsD1, capabilities, containers = [], workflows = [], agents = []) => {
|
|
261
338
|
const exported = new Set(durableObjects.map((object) => object.className));
|
|
262
339
|
const signals = durableObjects.map((object) => `${object.binding}/${object.className} (exported by worker entry)`);
|
|
263
340
|
if (needsD1) {
|
|
264
341
|
signals.push("DB (.global() table declared)");
|
|
265
342
|
}
|
|
266
|
-
signals.push(...describeDeclaredExports(containers, workflows), ...describeCapabilitySignals(capabilities, exported));
|
|
343
|
+
signals.push(...describeDeclaredExports(containers, workflows, agents), ...describeCapabilitySignals(capabilities, exported));
|
|
267
344
|
return signals;
|
|
268
345
|
};
|
|
269
346
|
const inferLunoraBindings = async (options) => {
|
|
270
347
|
await init;
|
|
271
348
|
const schemaDirectory = options.schemaDir ?? "lunora";
|
|
272
349
|
const scanDirectories = options.scanDirs ?? DEFAULT_SCAN_DIRECTORIES;
|
|
273
|
-
const
|
|
350
|
+
const scannedCapabilities = scanCapabilities(options.projectRoot, scanDirectories);
|
|
351
|
+
const capabilities = {
|
|
352
|
+
...scannedCapabilities,
|
|
353
|
+
usesBrowser: scannedCapabilities.usesBrowser || scanSandboxBrowserToolUsage(options.projectRoot, schemaDirectory)
|
|
354
|
+
};
|
|
274
355
|
const entryPath = resolveWorkerEntry(options.projectRoot);
|
|
275
356
|
const durableObjects = entryPath ? detectExportedDurableObjects(entryPath) : [];
|
|
276
357
|
const needsD1 = capabilities.needsD1 || schemaNeedsD1(options.projectRoot, schemaDirectory);
|
|
277
358
|
const containers = detectContainerExports(entryPath, discoverContainerInfo(options.projectRoot, schemaDirectory).containers);
|
|
278
359
|
const workflows = detectWorkflowExports(entryPath, discoverWorkflowInfo(options.projectRoot, schemaDirectory).workflows);
|
|
360
|
+
const agents = detectAgentExports(entryPath, discoverAgentInfo(options.projectRoot, schemaDirectory).agents);
|
|
361
|
+
const queues = [...discoverQueueInfo(options.projectRoot, schemaDirectory).queues];
|
|
362
|
+
const { flags } = discoverFlagsInfo(options.projectRoot, schemaDirectory);
|
|
363
|
+
const flagshipBinding = flags?.provider === "flagship" && flags.mode === "binding" ? flags.bindingName : void 0;
|
|
279
364
|
const capabilityFlags = {};
|
|
280
365
|
for (const flag of CAPABILITY_FLAGS) {
|
|
281
366
|
capabilityFlags[flag] = capabilities[flag];
|
|
282
367
|
}
|
|
368
|
+
const signals = describeSignals(durableObjects, needsD1, capabilities, containers, workflows, agents);
|
|
369
|
+
if (flagshipBinding !== void 0) {
|
|
370
|
+
signals.push(
|
|
371
|
+
`hint: lunora/flags.ts uses Flagship in binding mode; add a flagship binding ({ binding: "${flagshipBinding}", app_id }) — the app_id can't be auto-provisioned`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
283
374
|
return {
|
|
375
|
+
agents,
|
|
284
376
|
containers,
|
|
285
377
|
durableObjects,
|
|
378
|
+
flagshipBinding,
|
|
286
379
|
needsD1,
|
|
287
|
-
|
|
380
|
+
queues,
|
|
381
|
+
signals,
|
|
382
|
+
usesFlags: flags !== void 0,
|
|
288
383
|
workflows,
|
|
289
384
|
...capabilityFlags
|
|
290
385
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { modify, applyEdits } from 'jsonc-parser';
|
|
2
|
+
|
|
3
|
+
const FORMATTING = { formattingOptions: { insertSpaces: true, tabSize: 4 } };
|
|
4
|
+
const applyModify = (text, path, value) => {
|
|
5
|
+
const edits = modify(text, [...path], value, FORMATTING);
|
|
6
|
+
return edits.length > 0 ? applyEdits(text, edits) : text;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export { FORMATTING as F, applyModify as a };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { parseDevVariableEntries } from './DEV_VARS_EXAMPLE_FILE-dJPNTEnK.mjs';
|
|
4
|
+
|
|
5
|
+
const parseDevVariable = (contents, key) => {
|
|
6
|
+
const entry = parseDevVariableEntries(contents).find((candidate) => candidate.key === key);
|
|
7
|
+
return entry === void 0 || entry.value === "" ? void 0 : entry.value;
|
|
8
|
+
};
|
|
9
|
+
const resolveAdminToken = (root) => {
|
|
10
|
+
const fromEnv = process.env["LUNORA_ADMIN_TOKEN"];
|
|
11
|
+
if (typeof fromEnv === "string" && fromEnv !== "") {
|
|
12
|
+
return fromEnv;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return parseDevVariable(readFileSync(join(root, ".dev.vars"), "utf8"), "LUNORA_ADMIN_TOKEN");
|
|
16
|
+
} catch {
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export { parseDevVariable, resolveAdminToken };
|
|
@@ -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 });
|