@lunora/config 1.0.0-alpha.81 → 1.0.0-alpha.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/index.mjs +1 -30
  2. package/dist/packem_shared/ACCENT-CLeV5v0K.mjs +7 -0
  3. package/dist/packem_shared/AGENT_MODE_ENV-B54hVQ_w.mjs +1 -0
  4. package/dist/packem_shared/AGENT_RULES_DIR-hP9TiDNx.mjs +1 -0
  5. package/dist/packem_shared/DEV_DAEMON_ENV-D9Z83rlU.mjs +4 -0
  6. package/dist/packem_shared/DEV_VARS_EXAMPLE_FILE-DX6xGbpr.mjs +1 -0
  7. package/dist/packem_shared/LINKED_PROJECT_DIR-CMzUvj-1.mjs +2 -0
  8. package/dist/packem_shared/LUNORA_CONFIG_FILE-3bpx6TZN.mjs +1 -0
  9. package/dist/packem_shared/LUNORA_EVENT_SOURCE-ZclWASXS.mjs +1 -0
  10. package/dist/packem_shared/LunoraReporter-BnXUqh8t.mjs +6 -0
  11. package/dist/packem_shared/PACKAGE_SECRETS_REGISTRY-BgmvEPA-.mjs +1 -0
  12. package/dist/packem_shared/POLICY_SCAFFOLD_ENDPOINT-CUYWD0Mh.mjs +1 -0
  13. package/dist/packem_shared/REMOTE_ELIGIBLE_KEYS-ws6iE0y-.mjs +1 -0
  14. package/dist/packem_shared/REQUIRED_COMPATIBILITY_DATE-DPcRuAhj.mjs +1 -0
  15. package/dist/packem_shared/SCHEMA_EDIT_ENDPOINT-CZ8rSh2k.mjs +1 -0
  16. package/dist/packem_shared/SEED_ENDPOINT-BaYShU8q.mjs +1 -0
  17. package/dist/packem_shared/WORKERS_CACHE_MIN_DATE-B1h_wNDN.mjs +1 -0
  18. package/dist/packem_shared/WRANGLER_FILES-Bi_18Pj6.mjs +1 -0
  19. package/dist/packem_shared/applyAdditiveEdit-BdcnSa-j.mjs +6 -0
  20. package/dist/packem_shared/assetContentType-DNZyUOuF.mjs +1 -0
  21. package/dist/packem_shared/buildPackageSecretsBlock-BpvaZzQ8.mjs +18 -0
  22. package/dist/packem_shared/classifyPolicyEdit-BzC_MfYK.mjs +24 -0
  23. package/dist/packem_shared/collectWranglerSecretVariables-OacEwPmL.mjs +1 -0
  24. package/dist/packem_shared/createConfirm-7IL0kZyE.mjs +7 -0
  25. package/dist/packem_shared/detectFramework-VTQfCNXy.mjs +1 -0
  26. package/dist/packem_shared/discoverAgentInfo-BJm0QtoI.mjs +1 -0
  27. package/dist/packem_shared/discoverContainerInfo-CYG9j2LY.mjs +1 -0
  28. package/dist/packem_shared/discoverSchemaInfo-B2W3VrcT.mjs +1 -0
  29. package/dist/packem_shared/discoverWorkflowInfo-Bbc0u2gE.mjs +1 -0
  30. package/dist/packem_shared/inferLunoraBindings-DWSabh1j.mjs +1 -0
  31. package/dist/packem_shared/jsonc-edit-BZVpxVA0.mjs +1 -0
  32. package/dist/packem_shared/parseDevVariable-NAVJ8tgA.mjs +1 -0
  33. package/dist/packem_shared/parseSchema-BPmtMgmb.mjs +1 -0
  34. package/dist/packem_shared/reconcileWranglerBindings-Dd3tLauZ.mjs +1 -0
  35. package/dist/packem_shared/reconcileWranglerCompatibilityDate-BXNiEQ7p.mjs +1 -0
  36. package/dist/packem_shared/reconcileWranglerCrons-BmQa_kGL.mjs +1 -0
  37. package/dist/packem_shared/renderStudioHtml-B2AkxcW1.mjs +15 -0
  38. package/dist/packem_shared/serveJsonHandler-C5GWlWJF.mjs +1 -0
  39. package/dist/packem_shared/streamContainerLogs-BPYBNrWS.mjs +2 -0
  40. package/dist/packem_shared/write-atomic-Cdr2oQb-.mjs +1 -0
  41. package/dist/studio-host/index.mjs +1 -7
  42. package/package.json +3 -3
  43. package/dist/packem_shared/ACCENT-DW1XJn8i.mjs +0 -40
  44. package/dist/packem_shared/AGENT_MODE_ENV-B5R7jpY7.mjs +0 -17
  45. package/dist/packem_shared/AGENT_RULES_DIR-lcgC08aE.mjs +0 -40
  46. package/dist/packem_shared/DEV_DAEMON_ENV-FIEtW7Fz.mjs +0 -163
  47. package/dist/packem_shared/DEV_VARS_EXAMPLE_FILE-dJPNTEnK.mjs +0 -37
  48. package/dist/packem_shared/LINKED_PROJECT_DIR-CXwXzV_C.mjs +0 -52
  49. package/dist/packem_shared/LUNORA_CONFIG_FILE-CtcIcB5-.mjs +0 -34
  50. package/dist/packem_shared/LUNORA_EVENT_SOURCE-M1KM9_pR.mjs +0 -112
  51. package/dist/packem_shared/LunoraReporter-Ci-bDCK9.mjs +0 -70
  52. package/dist/packem_shared/PACKAGE_SECRETS_REGISTRY-NCTSphDk.mjs +0 -138
  53. package/dist/packem_shared/POLICY_SCAFFOLD_ENDPOINT-CvTZpaZF.mjs +0 -100
  54. package/dist/packem_shared/REMOTE_ELIGIBLE_KEYS-C-F_Flgy.mjs +0 -100
  55. package/dist/packem_shared/REQUIRED_COMPATIBILITY_DATE-DP0SFVy9.mjs +0 -607
  56. package/dist/packem_shared/SCHEMA_EDIT_ENDPOINT-Bt2SNkpV.mjs +0 -95
  57. package/dist/packem_shared/SEED_ENDPOINT-7izz_Jao.mjs +0 -61
  58. package/dist/packem_shared/WORKERS_CACHE_MIN_DATE-CaXbS7CX.mjs +0 -11
  59. package/dist/packem_shared/WRANGLER_FILES-DwSuC-Kn.mjs +0 -25
  60. package/dist/packem_shared/applyAdditiveEdit-C-snTFEV.mjs +0 -228
  61. package/dist/packem_shared/assetContentType-ByZjeV3D.mjs +0 -72
  62. package/dist/packem_shared/buildPackageSecretsBlock-BxOcyaAY.mjs +0 -281
  63. package/dist/packem_shared/classifyPolicyEdit-BHeAqF8P.mjs +0 -99
  64. package/dist/packem_shared/collectWranglerSecretVariables-BRWIvT-e.mjs +0 -56
  65. package/dist/packem_shared/createConfirm-BMikeJC9.mjs +0 -117
  66. package/dist/packem_shared/detectFramework-U08038Yp.mjs +0 -41
  67. package/dist/packem_shared/discoverAgentInfo-eXA3hF4v.mjs +0 -19
  68. package/dist/packem_shared/discoverContainerInfo-BXFs6Wav.mjs +0 -19
  69. package/dist/packem_shared/discoverSchemaInfo-BB-CKlTK.mjs +0 -25
  70. package/dist/packem_shared/discoverWorkflowInfo-CedvR0mn.mjs +0 -19
  71. package/dist/packem_shared/inferLunoraBindings-BcvvtpSG.mjs +0 -397
  72. package/dist/packem_shared/jsonc-edit-SbcD7MAe.mjs +0 -9
  73. package/dist/packem_shared/parseDevVariable-B0FEvfHC.mjs +0 -21
  74. package/dist/packem_shared/parseSchema-DSeyktvG.mjs +0 -107
  75. package/dist/packem_shared/reconcileWranglerBindings-2Cww5VLa.mjs +0 -362
  76. package/dist/packem_shared/reconcileWranglerCompatibilityDate-C8c497ag.mjs +0 -31
  77. package/dist/packem_shared/reconcileWranglerCrons-DvsceVFz.mjs +0 -29
  78. package/dist/packem_shared/renderStudioHtml-449Ysn75.mjs +0 -37
  79. package/dist/packem_shared/serveJsonHandler-Cl2Wqkv8.mjs +0 -86
  80. package/dist/packem_shared/streamContainerLogs-BZ4cOZwH.mjs +0 -157
  81. package/dist/packem_shared/write-atomic-B2REqapi.mjs +0 -14
@@ -1,281 +0,0 @@
1
- import { randomBytes } from 'node:crypto';
2
- import { existsSync, readFileSync, writeFileSync, renameSync, rmSync, statSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import { LunoraError } from '@lunora/errors';
5
- import { DEV_VARS_FILE, DEV_VARS_EXAMPLE_FILE, splitDevVariableLine, parseDevVariableEntries, DEV_VARS_NEWLINE, unquoteDevVariable } from './DEV_VARS_EXAMPLE_FILE-dJPNTEnK.mjs';
6
- import { CORE_SECRETS, PACKAGE_SECRETS_REGISTRY, secretsForPackages } from './PACKAGE_SECRETS_REGISTRY-NCTSphDk.mjs';
7
-
8
- const requiredSecrets = (packageNames) => [...CORE_SECRETS, ...secretsForPackages(packageNames)];
9
- const SECRET_BYTES = 32;
10
- const SECRET_KEY = /(?:KEY|PASSWORD|SECRET|TOKEN)$/u;
11
- const PLACEHOLDER_MARKERS = [
12
- "replace",
13
- "openssl",
14
- "changeme",
15
- "change-me",
16
- "change_me",
17
- "change-this",
18
- "change_this",
19
- "your-",
20
- "your_",
21
- "example",
22
- "placeholder",
23
- "todo",
24
- "fill-me",
25
- "fill_me",
26
- "fill-in",
27
- "fill_in",
28
- "xxx"
29
- ];
30
- const REGEXP_SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/gu;
31
- const MARKER_ENDS_ALPHANUMERIC = /[a-z0-9]$/u;
32
- const isPlaceholderValue = (value) => {
33
- const normalised = value.trim().toLowerCase();
34
- if (normalised === "") {
35
- return true;
36
- }
37
- if (normalised.startsWith("<") && normalised.endsWith(">")) {
38
- return true;
39
- }
40
- return PLACEHOLDER_MARKERS.some((marker) => {
41
- const escaped = marker.replaceAll(REGEXP_SPECIAL_CHARS, String.raw`\$&`);
42
- const needsTrailingBoundary = MARKER_ENDS_ALPHANUMERIC.test(marker);
43
- const pattern = needsTrailingBoundary ? `(^|[^a-z0-9])${escaped}([^a-z0-9]|$)` : `(^|[^a-z0-9])${escaped}`;
44
- return new RegExp(pattern, "u").test(normalised);
45
- });
46
- };
47
- const isPlaceholder = (rawValue) => isPlaceholderValue(unquoteDevVariable(rawValue.trim()));
48
- const defaultRandomHex = (bytes) => randomBytes(bytes).toString("hex");
49
- const PROVIDER_SECRET_KEYS = new Set(
50
- [...CORE_SECRETS, ...Object.values(PACKAGE_SECRETS_REGISTRY).flat()].filter((entry) => SECRET_KEY.test(entry.key) && entry.placeholderValue.startsWith("<")).map((entry) => entry.key)
51
- );
52
- const isMintableSecretKey = (key) => SECRET_KEY.test(key) && !PROVIDER_SECRET_KEYS.has(key);
53
- const generatedSecretFor = (key, rawValue, randomHex) => isMintableSecretKey(key) && isPlaceholder(rawValue) ? randomHex(SECRET_BYTES) : void 0;
54
- const generateSecretValue = (randomHex = defaultRandomHex) => randomHex(SECRET_BYTES);
55
- const planDevVariablesScaffold = (input) => {
56
- if (input.devVarsExists) {
57
- return { status: "exists" };
58
- }
59
- if (input.exampleContent === void 0) {
60
- return { status: "no-example" };
61
- }
62
- const randomHex = input.randomHex ?? defaultRandomHex;
63
- const generatedKeys = [];
64
- const lines = input.exampleContent.split(DEV_VARS_NEWLINE).map((line) => {
65
- const parsed = splitDevVariableLine(line);
66
- const secret = parsed ? generatedSecretFor(parsed.key, parsed.value, randomHex) : void 0;
67
- if (!parsed || secret === void 0) {
68
- return line;
69
- }
70
- generatedKeys.push(parsed.key);
71
- return `${parsed.key}="${secret}"`;
72
- });
73
- return { content: lines.join("\n"), generatedKeys, status: "generate" };
74
- };
75
- const planDevVariablesAugment = (input) => {
76
- const randomHex = input.randomHex ?? defaultRandomHex;
77
- const present = new Set(parseDevVariableEntries(input.existingContent).map((entry) => entry.key));
78
- const additions = [];
79
- const generatedKeys = [];
80
- const missingKeys = [];
81
- for (const line of input.exampleContent.split(DEV_VARS_NEWLINE)) {
82
- const parsed = splitDevVariableLine(line);
83
- if (!parsed || present.has(parsed.key)) {
84
- continue;
85
- }
86
- const secret = generatedSecretFor(parsed.key, parsed.value, randomHex);
87
- missingKeys.push(parsed.key);
88
- if (secret === void 0) {
89
- additions.push(`${parsed.key}="${unquoteDevVariable(parsed.value)}"`);
90
- } else {
91
- generatedKeys.push(parsed.key);
92
- additions.push(`${parsed.key}="${secret}"`);
93
- }
94
- }
95
- return { additions, generatedKeys, missingKeys };
96
- };
97
- const generatedSuffix = (keys) => keys.length > 0 ? ` (generated ${keys.join(", ")})` : "";
98
- const atomicCreateDevVariables = (path, content) => {
99
- const temporaryPath = `${path}.tmp-${String(process.pid)}`;
100
- try {
101
- writeFileSync(temporaryPath, content, { encoding: "utf8", flag: "wx", mode: 384 });
102
- renameSync(temporaryPath, path);
103
- } catch (error) {
104
- rmSync(temporaryPath, { force: true });
105
- throw error;
106
- }
107
- };
108
- const APPEND_MAX_ATTEMPTS = 5;
109
- const fingerprintOf = (path) => {
110
- try {
111
- const stats = statSync(path);
112
- return { mtimeMs: stats.mtimeMs, size: stats.size };
113
- } catch {
114
- return void 0;
115
- }
116
- };
117
- const sameFingerprint = (a, b) => {
118
- if (a === void 0 || b === void 0) {
119
- return false;
120
- }
121
- return a.mtimeMs === b.mtimeMs && a.size === b.size;
122
- };
123
- const appendDevVariables = (path, buildAdditions) => {
124
- for (let attempt = 0; attempt < APPEND_MAX_ATTEMPTS; attempt += 1) {
125
- const beforeFingerprint = fingerprintOf(path);
126
- const existing = readFileSync(path, "utf8");
127
- const additions = buildAdditions(existing);
128
- if (additions.length === 0) {
129
- return [];
130
- }
131
- const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
132
- const content = `${existing}${separator}${additions.join("\n")}
133
- `;
134
- const temporaryPath = `${path}.tmp-${String(process.pid)}-${randomBytes(6).toString("hex")}`;
135
- try {
136
- writeFileSync(temporaryPath, content, { encoding: "utf8", mode: 384 });
137
- const beforeRenameFingerprint = fingerprintOf(path);
138
- if (!sameFingerprint(beforeFingerprint, beforeRenameFingerprint)) {
139
- rmSync(temporaryPath, { force: true });
140
- continue;
141
- }
142
- renameSync(temporaryPath, path);
143
- return additions;
144
- } catch (error) {
145
- rmSync(temporaryPath, { force: true });
146
- throw error;
147
- }
148
- }
149
- throw new LunoraError("INTERNAL", `Failed to append to ${path} after ${String(APPEND_MAX_ATTEMPTS)} attempts — a concurrent writer kept winning the race.`);
150
- };
151
- const ensureDevVariables = async (deps) => {
152
- const devVariablesPath = join(deps.cwd, DEV_VARS_FILE);
153
- const examplePath = join(deps.cwd, DEV_VARS_EXAMPLE_FILE);
154
- if (!existsSync(examplePath)) {
155
- return { addedKeys: [], generatedKeys: [], status: "no-example" };
156
- }
157
- const exampleContent = readFileSync(examplePath, "utf8");
158
- if (!existsSync(devVariablesPath)) {
159
- const plan = planDevVariablesScaffold({ devVarsExists: false, exampleContent, randomHex: deps.randomHex });
160
- if (plan.status !== "generate") {
161
- return { addedKeys: [], generatedKeys: [], status: "no-example" };
162
- }
163
- const proceed2 = deps.yes === true || await deps.confirm(`No ${DEV_VARS_FILE} found. Generate it from ${DEV_VARS_EXAMPLE_FILE} (secrets auto-filled)?`);
164
- if (!proceed2) {
165
- deps.info(`Skipped — copy ${DEV_VARS_EXAMPLE_FILE} to ${DEV_VARS_FILE} and fill it in when you're ready.`);
166
- return { addedKeys: [], generatedKeys: [], status: "declined" };
167
- }
168
- if (existsSync(devVariablesPath)) {
169
- return { addedKeys: [], generatedKeys: [], status: "skipped-exists" };
170
- }
171
- atomicCreateDevVariables(devVariablesPath, plan.content);
172
- deps.info(`Created ${DEV_VARS_FILE}${generatedSuffix(plan.generatedKeys)}.`);
173
- return { addedKeys: [], generatedKeys: plan.generatedKeys, status: "generated" };
174
- }
175
- const augment = planDevVariablesAugment({ existingContent: readFileSync(devVariablesPath, "utf8"), exampleContent, randomHex: deps.randomHex });
176
- if (augment.missingKeys.length === 0) {
177
- return { addedKeys: [], generatedKeys: [], status: "exists" };
178
- }
179
- const list = augment.missingKeys.join(", ");
180
- const proceed = deps.yes === true || await deps.confirm(`${DEV_VARS_FILE} is missing ${String(augment.missingKeys.length)} key(s) from ${DEV_VARS_EXAMPLE_FILE} (${list}). Add them?`);
181
- if (!proceed) {
182
- deps.info(`Skipped — add ${list} to ${DEV_VARS_FILE} when you're ready.`);
183
- return { addedKeys: [], generatedKeys: [], status: "declined" };
184
- }
185
- const written = appendDevVariables(
186
- devVariablesPath,
187
- (currentContent) => planDevVariablesAugment({ existingContent: currentContent, exampleContent, randomHex: deps.randomHex }).additions
188
- );
189
- const writtenKeys = written.map((line) => splitDevVariableLine(line)?.key).filter((key) => key !== void 0);
190
- if (writtenKeys.length === 0) {
191
- return { addedKeys: [], generatedKeys: [], status: "exists" };
192
- }
193
- const writtenGeneratedKeys = augment.generatedKeys.filter((key) => writtenKeys.includes(key));
194
- deps.info(`Updated ${DEV_VARS_FILE} — added ${writtenKeys.join(", ")}${generatedSuffix(writtenGeneratedKeys)}.`);
195
- return { addedKeys: writtenKeys, generatedKeys: writtenGeneratedKeys, status: "augmented" };
196
- };
197
- const secretEntryBlock = (entry) => {
198
- const lines = [`# ${entry.description}`, `# Docs: ${entry.docsUrl}`, `${entry.key}="${entry.placeholderValue}"`];
199
- return lines.join("\n");
200
- };
201
- const buildPackageSecretsBlock = (packageNames, existingKeys) => {
202
- const entries = requiredSecrets(packageNames).filter((entry) => !existingKeys.has(entry.key));
203
- if (entries.length === 0) {
204
- return "";
205
- }
206
- return entries.map((entry) => secretEntryBlock(entry)).join("\n\n");
207
- };
208
- const ensureDevVariablesExample = (cwd, packageNames) => {
209
- const examplePath = join(cwd, DEV_VARS_EXAMPLE_FILE);
210
- const existing = existsSync(examplePath) ? readFileSync(examplePath, "utf8") : "";
211
- const existingKeys = new Set(parseDevVariableEntries(existing).map((entry) => entry.key));
212
- const block = buildPackageSecretsBlock(packageNames, existingKeys);
213
- if (block === "") {
214
- return [];
215
- }
216
- const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
217
- const newContent = `${existing}${separator}
218
- ${block}
219
- `;
220
- const temporaryPath = `${examplePath}.tmp-${String(process.pid)}`;
221
- try {
222
- writeFileSync(temporaryPath, newContent, { encoding: "utf8" });
223
- renameSync(temporaryPath, examplePath);
224
- } catch (error) {
225
- rmSync(temporaryPath, { force: true });
226
- throw error;
227
- }
228
- return requiredSecrets(packageNames).filter((entry) => !existingKeys.has(entry.key)).map((entry) => entry.key);
229
- };
230
- const planDevSecretsFill = (input) => {
231
- const randomHex = input.randomHex ?? defaultRandomHex;
232
- const filledKeys = [];
233
- const lines = input.existingContent.split(DEV_VARS_NEWLINE).map((line) => {
234
- const parsed = splitDevVariableLine(line);
235
- const secret = parsed ? generatedSecretFor(parsed.key, parsed.value, randomHex) : void 0;
236
- if (!parsed || secret === void 0) {
237
- return line;
238
- }
239
- filledKeys.push(parsed.key);
240
- return `${parsed.key}="${secret}"`;
241
- });
242
- const present = new Set(parseDevVariableEntries(input.existingContent).map((entry) => entry.key));
243
- const addedKeys = [];
244
- const additions = [];
245
- for (const entry of CORE_SECRETS) {
246
- if (present.has(entry.key)) {
247
- continue;
248
- }
249
- addedKeys.push(entry.key);
250
- additions.push(`# ${entry.description}`, `${entry.key}="${randomHex(SECRET_BYTES)}"`);
251
- }
252
- const body = lines.join("\n");
253
- if (additions.length === 0) {
254
- return { addedKeys, content: body, filledKeys };
255
- }
256
- const separator = body === "" || body.endsWith("\n") ? "" : "\n";
257
- return { addedKeys, content: `${body}${separator}${additions.join("\n")}
258
- `, filledKeys };
259
- };
260
- const fillDevSecrets = (deps) => {
261
- const devVariablesPath = join(deps.cwd, DEV_VARS_FILE);
262
- const exists = existsSync(devVariablesPath);
263
- const existingContent = exists ? readFileSync(devVariablesPath, "utf8") : "";
264
- const plan = planDevSecretsFill({ existingContent, randomHex: deps.randomHex });
265
- if (plan.filledKeys.length === 0 && plan.addedKeys.length === 0) {
266
- return { addedKeys: [], filledKeys: [], status: "unchanged" };
267
- }
268
- const temporaryPath = `${devVariablesPath}.tmp-${String(process.pid)}`;
269
- try {
270
- writeFileSync(temporaryPath, plan.content, { encoding: "utf8", mode: 384 });
271
- renameSync(temporaryPath, devVariablesPath);
272
- } catch (error) {
273
- rmSync(temporaryPath, { force: true });
274
- throw error;
275
- }
276
- const generated = [...plan.filledKeys, ...plan.addedKeys];
277
- deps.info?.(`Generated ${String(generated.length)} dev secret(s) in ${DEV_VARS_FILE}: ${generated.join(", ")}`);
278
- return { addedKeys: plan.addedKeys, filledKeys: plan.filledKeys, status: exists ? "filled" : "created" };
279
- };
280
-
281
- export { buildPackageSecretsBlock, ensureDevVariables, ensureDevVariablesExample as ensureDevVarsExample, fillDevSecrets, generateSecretValue, isMintableSecretKey, isPlaceholderValue, planDevSecretsFill, planDevVariablesAugment, planDevVariablesScaffold, requiredSecrets };
@@ -1,99 +0,0 @@
1
- import { Project, SyntaxKind } from 'ts-morph';
2
-
3
- const TERMINAL_METHODS = /* @__PURE__ */ new Set(["action", "mutation", "query", "stream"]);
4
- const IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
5
- const ADDITIVE_KINDS = /* @__PURE__ */ new Set(["scaffoldPolicy", "wireRls"]);
6
- const classifyPolicyEdit = (edit) => ADDITIVE_KINDS.has(edit.kind) ? "additive" : "destructive";
7
- const scaffoldPolicyFile = (edit) => {
8
- if (typeof edit.name !== "string" || typeof edit.table !== "string" || !IDENTIFIER_PATTERN.test(edit.name) || !IDENTIFIER_PATTERN.test(edit.table)) {
9
- return { ok: false, reason: "invalid-identifier" };
10
- }
11
- const policiesIdentifier = `${edit.name}Policies`;
12
- const rolesIdentifier = `${edit.name}Roles`;
13
- const table = JSON.stringify(edit.table);
14
- const source = `import { definePermission, definePolicies, definePolicy, defineRole } from "@lunora/server";
15
-
16
- /**
17
- * Access rules for the ${edit.table} table — scaffolded by the Lunora studio
18
- * (plan 025). The \`when\` predicates below DENY by default (\`() => false\`);
19
- * replace each TODO with the real condition, then wire \`${policiesIdentifier}\`
20
- * into the procedures that read/write ${edit.table} via
21
- * \`.use(rls(${policiesIdentifier}, { roles: ${rolesIdentifier} }))\`.
22
- */
23
- export const ${policiesIdentifier} = definePolicies([
24
- definePolicy({
25
- on: "read",
26
- table: ${table},
27
- // TODO: return \`true\` to allow, a \`WhereInput\` to filter, or \`false\` to deny.
28
- when: () => false,
29
- }),
30
- ]);
31
-
32
- /** Named permissions \`${policiesIdentifier}\` can check with \`ctx.auth.can(...)\`. */
33
- export const ${edit.name}View = definePermission("${edit.name}:view");
34
-
35
- /** Roles that grant the permissions above; register via \`rls(policies, { roles })\`. */
36
- export const ${rolesIdentifier} = [defineRole("${edit.name}-admin", { permissions: [${edit.name}View] })];
37
- `;
38
- return { fileName: `${edit.name}.policies.ts`, ok: true, source };
39
- };
40
- const builderReceiver = (initializer) => {
41
- const callee = initializer.getExpression();
42
- if (callee.getKind() !== SyntaxKind.PropertyAccessExpression) {
43
- return void 0;
44
- }
45
- const access = callee.asKindOrThrow(SyntaxKind.PropertyAccessExpression);
46
- return TERMINAL_METHODS.has(access.getName()) ? access.getExpression() : void 0;
47
- };
48
- const ensureRlsImport = (sourceFile) => {
49
- const serverImport = sourceFile.getImportDeclaration((declaration) => declaration.getModuleSpecifierValue() === "@lunora/server");
50
- if (serverImport === void 0) {
51
- sourceFile.addImportDeclaration({ moduleSpecifier: "@lunora/server", namedImports: ["rls"] });
52
- return;
53
- }
54
- if (!serverImport.getNamedImports().some((named) => named.getName() === "rls")) {
55
- serverImport.addNamedImport("rls");
56
- }
57
- };
58
- const chainHasRls = (receiver) => {
59
- const calls = receiver.getDescendantsOfKind(SyntaxKind.CallExpression);
60
- if (receiver.getKind() === SyntaxKind.CallExpression) {
61
- calls.push(receiver.asKindOrThrow(SyntaxKind.CallExpression));
62
- }
63
- for (const call of calls) {
64
- const callee = call.getExpression();
65
- if (callee.getKind() !== SyntaxKind.PropertyAccessExpression) {
66
- continue;
67
- }
68
- const access = callee.asKindOrThrow(SyntaxKind.PropertyAccessExpression);
69
- const [argument] = call.getArguments();
70
- if (access.getName() === "use" && argument?.getKind() === SyntaxKind.CallExpression && argument.asKindOrThrow(SyntaxKind.CallExpression).getExpression().getText() === "rls") {
71
- return true;
72
- }
73
- }
74
- return false;
75
- };
76
- const wireRlsIntoProcedure = (source, edit) => {
77
- if (typeof edit.policies !== "string" || !IDENTIFIER_PATTERN.test(edit.policies)) {
78
- return { ok: false, reason: "invalid-identifier" };
79
- }
80
- const project = new Project({ compilerOptions: { allowJs: true }, useInMemoryFileSystem: true });
81
- const sourceFile = project.createSourceFile("procedure.ts", source, { overwrite: true });
82
- const declaration = sourceFile.getVariableDeclaration(edit.exportName);
83
- const initializer = declaration?.getInitializer();
84
- if (declaration === void 0 || initializer?.getKind() !== SyntaxKind.CallExpression) {
85
- return { ok: false, reason: "unknown-procedure" };
86
- }
87
- const receiver = builderReceiver(initializer.asKindOrThrow(SyntaxKind.CallExpression));
88
- if (receiver === void 0) {
89
- return { ok: false, reason: "unsupported-procedure-shape" };
90
- }
91
- if (chainHasRls(receiver)) {
92
- return { ok: false, reason: "already-wired" };
93
- }
94
- receiver.replaceWithText(`${receiver.getText()}.use(rls(${edit.policies}))`);
95
- ensureRlsImport(sourceFile);
96
- return { ok: true, text: sourceFile.getFullText() };
97
- };
98
-
99
- export { classifyPolicyEdit, scaffoldPolicyFile, wireRlsIntoProcedure };
@@ -1,56 +0,0 @@
1
- import { relative } from 'node:path';
2
- import { secretKindOf, redact } from '@lunora/codegen';
3
- import { isPlaceholderValue } from './buildPackageSecretsBlock-BxOcyaAY.mjs';
4
- import { findWranglerFile, readWranglerJsonc } from './WRANGLER_FILES-DwSuC-Kn.mjs';
5
-
6
- const MIN_SECRET_NAMED_VALUE_LENGTH = 8;
7
- const SECRET_WORD_TOKENS = /* @__PURE__ */ new Set(["CREDENTIAL", "CREDENTIALS", "DSN", "PASSPHRASE", "PASSWD", "PASSWORD", "SECRET", "TOKEN"]);
8
- const SECRET_KEY_SUFFIXES = ["ACCESS_KEY", "API_KEY", "ENCRYPTION_KEY", "PRIVATE_KEY", "SIGNING_KEY"];
9
- const PUBLIC_KEY_TOKENS = /* @__PURE__ */ new Set(["PUBLIC", "PUBLISHABLE"]);
10
- const KEY_SEPARATOR_RE = /[_-]/u;
11
- const keyTokens = (key) => key.toUpperCase().split(KEY_SEPARATOR_RE);
12
- const isSecretKeyName = (key) => {
13
- const normalized = key.toUpperCase().replaceAll("-", "_");
14
- if (keyTokens(key).some((token) => SECRET_WORD_TOKENS.has(token))) {
15
- return true;
16
- }
17
- return SECRET_KEY_SUFFIXES.some((suffix) => normalized === suffix || normalized.endsWith(`_${suffix}`));
18
- };
19
- const isPublicKeyName = (key) => keyTokens(key).some((token) => PUBLIC_KEY_TOKENS.has(token));
20
- const scanWranglerVariablesForSecrets = (variables, file) => {
21
- if (variables === void 0) {
22
- return [];
23
- }
24
- const findings = [];
25
- for (const [key, rawValue] of Object.entries(variables)) {
26
- if (typeof rawValue !== "string") {
27
- continue;
28
- }
29
- if (isPlaceholderValue(rawValue)) {
30
- continue;
31
- }
32
- if (isPublicKeyName(key)) {
33
- continue;
34
- }
35
- const shapeKind = secretKindOf(rawValue);
36
- const kind = shapeKind ?? (isSecretKeyName(key) && rawValue.length >= MIN_SECRET_NAMED_VALUE_LENGTH ? "secret_named_var" : void 0);
37
- if (kind === void 0) {
38
- continue;
39
- }
40
- findings.push({ file, key, kind, preview: redact(rawValue) });
41
- }
42
- return findings;
43
- };
44
- const collectWranglerSecretVariables = (projectRoot) => {
45
- const wranglerPath = findWranglerFile(projectRoot);
46
- if (wranglerPath === void 0) {
47
- return [];
48
- }
49
- const { parsed } = readWranglerJsonc(wranglerPath);
50
- if (parsed === void 0) {
51
- return [];
52
- }
53
- return scanWranglerVariablesForSecrets(parsed.vars, relative(projectRoot, wranglerPath));
54
- };
55
-
56
- export { collectWranglerSecretVariables, scanWranglerVariablesForSecrets };
@@ -1,117 +0,0 @@
1
- import { createInterface } from 'node:readline';
2
-
3
- const MULTI_SELECT_SEPARATOR = /[\s,]+/u;
4
- const isInteractive = () => process.stdin.isTTY;
5
- const promptYesNo = async (prompt, options) => {
6
- const rl = createInterface({ input: process.stdin, output: process.stdout });
7
- try {
8
- const answer = await new Promise((resolve) => {
9
- rl.question(prompt, (input) => {
10
- resolve(input);
11
- });
12
- });
13
- const normalised = answer.trim().toLowerCase();
14
- if (normalised === "") {
15
- return options?.defaultYes === true;
16
- }
17
- return normalised === "y" || normalised === "yes";
18
- } finally {
19
- rl.close();
20
- }
21
- };
22
- const createConfirm = (prefix = "") => isInteractive() ? (message) => promptYesNo(`${prefix}${message} [Y/n] `, { defaultYes: true }) : () => Promise.resolve(false);
23
- const promptSelect = async (message, options, settings) => {
24
- if (!isInteractive() || options.length === 0) {
25
- return settings?.default;
26
- }
27
- const defaultIndex = settings?.default === void 0 ? -1 : options.findIndex((option) => option.value === settings.default);
28
- const lines = options.map(
29
- (option, index) => ` ${String(index + 1)}) ${option.label}${option.description === void 0 ? "" : ` — ${option.description}`}`
30
- );
31
- const promptMessage = `${message}
32
- ${lines.join("\n")}
33
- > ${defaultIndex >= 0 ? `[${String(defaultIndex + 1)}] ` : ""}`;
34
- const rl = createInterface({ input: process.stdin, output: process.stdout });
35
- try {
36
- const answer = await new Promise((resolve) => {
37
- rl.question(promptMessage, (input) => {
38
- resolve(input);
39
- });
40
- });
41
- const trimmed = answer.trim();
42
- if (trimmed === "") {
43
- return settings?.default;
44
- }
45
- const choice = Number.parseInt(trimmed, 10);
46
- if (Number.isInteger(choice) && choice >= 1 && choice <= options.length) {
47
- return options[choice - 1]?.value;
48
- }
49
- const byText = options.find((option) => option.value === trimmed || option.label.toLowerCase() === trimmed.toLowerCase());
50
- return byText?.value ?? settings?.default;
51
- } finally {
52
- rl.close();
53
- }
54
- };
55
- const promptText = async (message, settings) => {
56
- if (!isInteractive()) {
57
- return settings?.default;
58
- }
59
- const rl = createInterface({ input: process.stdin, output: process.stdout });
60
- try {
61
- const answer = await new Promise((resolve) => {
62
- rl.question(message, (input) => {
63
- resolve(input);
64
- });
65
- });
66
- const trimmed = answer.trim();
67
- return trimmed === "" ? settings?.default : trimmed;
68
- } finally {
69
- rl.close();
70
- }
71
- };
72
- const promptMultiSelect = async (message, options, settings) => {
73
- const defaults = settings?.defaults ?? [];
74
- if (!isInteractive() || options.length === 0) {
75
- return [...defaults];
76
- }
77
- const lines = options.map(
78
- (option, index) => ` ${String(index + 1)}) ${option.label}${option.description === void 0 ? "" : ` — ${option.description}`}`
79
- );
80
- const defaultHint = defaults.length === 0 ? "" : `[${defaults.join(", ")}] `;
81
- const promptMessage = `${message} (comma-separated; Enter for default)
82
- ${lines.join("\n")}
83
- > ${defaultHint}`;
84
- const rl = createInterface({ input: process.stdin, output: process.stdout });
85
- try {
86
- const answer = await new Promise((resolve) => {
87
- rl.question(promptMessage, (input) => {
88
- resolve(input);
89
- });
90
- });
91
- const trimmed = answer.trim();
92
- if (trimmed === "") {
93
- return [...defaults];
94
- }
95
- const tokens = trimmed.split(MULTI_SELECT_SEPARATOR).map((token) => token.trim()).filter((token) => token !== "");
96
- const picked = /* @__PURE__ */ new Set();
97
- for (const token of tokens) {
98
- const choice = Number.parseInt(token, 10);
99
- if (Number.isInteger(choice) && choice >= 1 && choice <= options.length && String(choice) === token) {
100
- const byIndex = options[choice - 1]?.value;
101
- if (byIndex !== void 0) {
102
- picked.add(byIndex);
103
- }
104
- continue;
105
- }
106
- const byText = options.find((option) => option.value === token || option.label.toLowerCase() === token.toLowerCase());
107
- if (byText !== void 0) {
108
- picked.add(byText.value);
109
- }
110
- }
111
- return options.filter((option) => picked.has(option.value)).map((option) => option.value);
112
- } finally {
113
- rl.close();
114
- }
115
- };
116
-
117
- export { createConfirm, isInteractive, promptMultiSelect, promptSelect, promptText, promptYesNo };
@@ -1,41 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
-
4
- const FRAMEWORK_SIGNATURES = [
5
- { class: "A", dependency: "@tanstack/react-start", framework: "tanstack-start" },
6
- { class: "A", dependency: "@tanstack/solid-start", framework: "tanstack-start-solid" },
7
- { class: "A", dependency: "@react-router/dev", framework: "react-router" },
8
- { class: "A", dependency: "@solidjs/start", framework: "solid-start" },
9
- { class: "A", dependency: "solid-start", framework: "solid-start" },
10
- { class: "B", dependency: "@sveltejs/kit", framework: "sveltekit" },
11
- { class: "B", dependency: "nuxt", framework: "nuxt" },
12
- { class: "B", dependency: "astro", framework: "astro" }
13
- ];
14
- const STANDALONE = { class: "C", framework: "none" };
15
- const readProjectDependencyNames = (root) => {
16
- const packageJsonPath = join(root, "package.json");
17
- if (!existsSync(packageJsonPath)) {
18
- return /* @__PURE__ */ new Set();
19
- }
20
- try {
21
- const raw = readFileSync(packageJsonPath, "utf8");
22
- const parsed = JSON.parse(raw);
23
- return /* @__PURE__ */ new Set([...Object.keys(parsed.dependencies ?? {}), ...Object.keys(parsed.devDependencies ?? {})]);
24
- } catch {
25
- return /* @__PURE__ */ new Set();
26
- }
27
- };
28
- const detectFramework = (root) => {
29
- const dependencies = readProjectDependencyNames(root);
30
- if (dependencies.size === 0) {
31
- return STANDALONE;
32
- }
33
- for (const signature of FRAMEWORK_SIGNATURES) {
34
- if (dependencies.has(signature.dependency)) {
35
- return { class: signature.class, framework: signature.framework };
36
- }
37
- }
38
- return STANDALONE;
39
- };
40
-
41
- export { detectFramework, readProjectDependencyNames };
@@ -1,19 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { AGENTS_FILENAME, discoverAgents } from '@lunora/codegen';
3
- import { Project } from 'ts-morph';
4
- import { join } from 'node:path';
5
-
6
- const discoverAgentInfo = (projectRoot, schemaDirectory) => {
7
- const agentsPath = join(projectRoot, schemaDirectory, AGENTS_FILENAME);
8
- if (!existsSync(agentsPath)) {
9
- return { agents: [] };
10
- }
11
- try {
12
- const project = new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
13
- return { agents: discoverAgents(project, join(projectRoot, schemaDirectory)) };
14
- } catch (error) {
15
- return { agents: [], error: error instanceof Error ? error.message : String(error) };
16
- }
17
- };
18
-
19
- export { discoverAgentInfo };
@@ -1,19 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { CONTAINERS_FILENAME, discoverContainers } from '@lunora/codegen';
3
- import { Project } from 'ts-morph';
4
- import { join } from 'node:path';
5
-
6
- const discoverContainerInfo = (projectRoot, schemaDirectory) => {
7
- const containersPath = join(projectRoot, schemaDirectory, CONTAINERS_FILENAME);
8
- if (!existsSync(containersPath)) {
9
- return { containers: [] };
10
- }
11
- try {
12
- const project = new Project({ skipAddingFilesFromTsConfig: true, skipFileDependencyResolution: true, useInMemoryFileSystem: false });
13
- return { containers: discoverContainers(project, join(projectRoot, schemaDirectory)) };
14
- } catch (error) {
15
- return { containers: [], error: error instanceof Error ? error.message : String(error) };
16
- }
17
- };
18
-
19
- export { discoverContainerInfo };
@@ -1,25 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { discoverSchema } from '@lunora/codegen';
3
- import { Project } from 'ts-morph';
4
- import { join } from 'node:path';
5
-
6
- const discoverSchemaInfo = (projectRoot, schemaDirectory) => {
7
- const schemaPath = join(projectRoot, schemaDirectory, "schema.ts");
8
- if (!existsSync(schemaPath)) {
9
- return { info: void 0 };
10
- }
11
- try {
12
- const project = new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
13
- const schema = discoverSchema(project, schemaPath, projectRoot);
14
- return {
15
- info: {
16
- hasGlobalTable: schema.tables.some((table) => table.shardMode === "global"),
17
- vectorIndexNames: schema.vectorIndexes.map((index) => index.name)
18
- }
19
- };
20
- } catch (error) {
21
- return { error: error instanceof Error ? error.message : String(error), info: void 0 };
22
- }
23
- };
24
-
25
- export { discoverSchemaInfo };