@lunora/config 1.0.0-alpha.9 → 1.0.0-alpha.91

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 (79) hide show
  1. package/LICENSE.md +6 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/index.d.mts +885 -461
  4. package/dist/index.d.ts +885 -461
  5. package/dist/index.mjs +1 -22
  6. package/dist/packem_shared/ACCENT-CLeV5v0K.mjs +7 -0
  7. package/dist/packem_shared/AGENT_MODE_ENV-B54hVQ_w.mjs +1 -0
  8. package/dist/packem_shared/AGENT_RULES_DIR-hP9TiDNx.mjs +1 -0
  9. package/dist/packem_shared/DEV_DAEMON_ENV-D9Z83rlU.mjs +4 -0
  10. package/dist/packem_shared/DEV_VARS_EXAMPLE_FILE-DX6xGbpr.mjs +1 -0
  11. package/dist/packem_shared/LINKED_PROJECT_DIR-CMzUvj-1.mjs +2 -0
  12. package/dist/packem_shared/LUNORA_CONFIG_FILE-3bpx6TZN.mjs +1 -0
  13. package/dist/packem_shared/LUNORA_EVENT_SOURCE-ZclWASXS.mjs +1 -0
  14. package/dist/packem_shared/LunoraReporter-BnXUqh8t.mjs +6 -0
  15. package/dist/packem_shared/PACKAGE_SECRETS_REGISTRY-BgmvEPA-.mjs +1 -0
  16. package/dist/packem_shared/POLICY_SCAFFOLD_ENDPOINT-CUYWD0Mh.mjs +1 -0
  17. package/dist/packem_shared/REMOTE_ELIGIBLE_KEYS-ws6iE0y-.mjs +1 -0
  18. package/dist/packem_shared/REQUIRED_COMPATIBILITY_DATE-DPcRuAhj.mjs +1 -0
  19. package/dist/packem_shared/SCHEMA_EDIT_ENDPOINT-CZ8rSh2k.mjs +1 -0
  20. package/dist/packem_shared/SEED_ENDPOINT-BaYShU8q.mjs +1 -0
  21. package/dist/packem_shared/WORKERS_CACHE_MIN_DATE-B1h_wNDN.mjs +1 -0
  22. package/dist/packem_shared/WRANGLER_FILES-Bi_18Pj6.mjs +1 -0
  23. package/dist/packem_shared/applyAdditiveEdit-BdcnSa-j.mjs +6 -0
  24. package/dist/packem_shared/assetContentType-DNZyUOuF.mjs +1 -0
  25. package/dist/packem_shared/buildPackageSecretsBlock-BpvaZzQ8.mjs +18 -0
  26. package/dist/packem_shared/classifyPolicyEdit-BzC_MfYK.mjs +24 -0
  27. package/dist/packem_shared/collectWranglerSecretVariables-OacEwPmL.mjs +1 -0
  28. package/dist/packem_shared/createConfirm-7IL0kZyE.mjs +7 -0
  29. package/dist/packem_shared/detectFramework-VTQfCNXy.mjs +1 -0
  30. package/dist/packem_shared/discoverAgentInfo-BJm0QtoI.mjs +1 -0
  31. package/dist/packem_shared/discoverContainerInfo-CYG9j2LY.mjs +1 -0
  32. package/dist/packem_shared/discoverSchemaInfo-B2W3VrcT.mjs +1 -0
  33. package/dist/packem_shared/discoverWorkflowInfo-Bbc0u2gE.mjs +1 -0
  34. package/dist/packem_shared/inferLunoraBindings-DqhCZuPM.mjs +1 -0
  35. package/dist/packem_shared/jsonc-edit-BZVpxVA0.mjs +1 -0
  36. package/dist/packem_shared/parseDevVariable-NAVJ8tgA.mjs +1 -0
  37. package/dist/packem_shared/parseSchema-BPmtMgmb.mjs +1 -0
  38. package/dist/packem_shared/{policy-scaffold.d-DCmwn7zQ.d.mts → policy-scaffold.d-CFd2FlqG.d.mts} +22 -22
  39. package/dist/packem_shared/{policy-scaffold.d-DCmwn7zQ.d.ts → policy-scaffold.d-CFd2FlqG.d.ts} +22 -22
  40. package/dist/packem_shared/reconcileWranglerBindings-Dd3tLauZ.mjs +1 -0
  41. package/dist/packem_shared/reconcileWranglerCompatibilityDate-BXNiEQ7p.mjs +1 -0
  42. package/dist/packem_shared/reconcileWranglerCrons-BmQa_kGL.mjs +1 -0
  43. package/dist/packem_shared/renderStudioHtml-B2AkxcW1.mjs +15 -0
  44. package/dist/packem_shared/serveJsonHandler-C5GWlWJF.mjs +1 -0
  45. package/dist/packem_shared/streamContainerLogs-BPYBNrWS.mjs +2 -0
  46. package/dist/packem_shared/write-atomic-Cdr2oQb-.mjs +1 -0
  47. package/dist/studio-host/index.d.mts +134 -92
  48. package/dist/studio-host/index.d.ts +134 -92
  49. package/dist/studio-host/index.mjs +1 -7
  50. package/package.json +11 -8
  51. package/dist/packem_shared/ACCENT-DW1XJn8i.mjs +0 -40
  52. package/dist/packem_shared/AGENT_RULES_DIR-lcgC08aE.mjs +0 -40
  53. package/dist/packem_shared/DEV_VARS_EXAMPLE_FILE-dJPNTEnK.mjs +0 -37
  54. package/dist/packem_shared/LINKED_PROJECT_DIR-CXwXzV_C.mjs +0 -52
  55. package/dist/packem_shared/LUNORA_CONFIG_FILE-CtcIcB5-.mjs +0 -34
  56. package/dist/packem_shared/LUNORA_EVENT_SOURCE-D2fDeGB6.mjs +0 -86
  57. package/dist/packem_shared/LunoraReporter-Ci-bDCK9.mjs +0 -70
  58. package/dist/packem_shared/PACKAGE_SECRETS_REGISTRY-CySy5vR_.mjs +0 -62
  59. package/dist/packem_shared/POLICY_SCAFFOLD_ENDPOINT-CiC2IGKx.mjs +0 -103
  60. package/dist/packem_shared/REMOTE_ELIGIBLE_KEYS-BC7_e9Bz.mjs +0 -105
  61. package/dist/packem_shared/REQUIRED_COMPATIBILITY_DATE-DRSNSOOp.mjs +0 -476
  62. package/dist/packem_shared/SCHEMA_EDIT_ENDPOINT-Df-Wrix-.mjs +0 -99
  63. package/dist/packem_shared/SEED_ENDPOINT-DVCjaGO-.mjs +0 -61
  64. package/dist/packem_shared/WRANGLER_FILES-DwSuC-Kn.mjs +0 -25
  65. package/dist/packem_shared/applyAdditiveEdit-C-snTFEV.mjs +0 -228
  66. package/dist/packem_shared/buildPackageSecretsBlock-S74dgmwy.mjs +0 -187
  67. package/dist/packem_shared/classifyPolicyEdit-BHeAqF8P.mjs +0 -99
  68. package/dist/packem_shared/createConfirm-fvpdgJ9s.mjs +0 -100
  69. package/dist/packem_shared/detectFramework-Br-BcPBq.mjs +0 -41
  70. package/dist/packem_shared/discoverContainerInfo-BXFs6Wav.mjs +0 -19
  71. package/dist/packem_shared/discoverSchemaInfo-BB-CKlTK.mjs +0 -25
  72. package/dist/packem_shared/discoverWorkflowInfo-CedvR0mn.mjs +0 -19
  73. package/dist/packem_shared/inferLunoraBindings-DIku9mTN.mjs +0 -302
  74. package/dist/packem_shared/loadStudioAssets-Csk5RS4E.mjs +0 -28
  75. package/dist/packem_shared/parseDevVariable-CJiq2IwE.mjs +0 -30
  76. package/dist/packem_shared/parseSchema-DSeyktvG.mjs +0 -107
  77. package/dist/packem_shared/reconcileWranglerBindings-DTHmqTbL.mjs +0 -277
  78. package/dist/packem_shared/renderStudioHtml-449Ysn75.mjs +0 -37
  79. package/dist/packem_shared/serveJsonHandler-B4OLTGLS.mjs +0 -86
@@ -1,61 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { discoverSchema, schemaFromIr } from '@lunora/codegen';
3
- import { seedPlan } from '@lunora/seed';
4
- import { Project } from 'ts-morph';
5
- import { join } from 'node:path';
6
-
7
- const SEED_ENDPOINT = "/__lunora/seed";
8
- const MAX_SEED_ROWS = 1e3;
9
- const jsonReplacer = (_key, value) => {
10
- if (typeof value === "bigint") {
11
- return Number(value);
12
- }
13
- if (value instanceof ArrayBuffer) {
14
- return [...new Uint8Array(value)];
15
- }
16
- return value;
17
- };
18
- const clampCount = (count) => {
19
- if (typeof count !== "number" || !Number.isFinite(count)) {
20
- return 10;
21
- }
22
- return Math.min(Math.max(1, Math.floor(count)), MAX_SEED_ROWS);
23
- };
24
- const handleSeedRequest = (request) => {
25
- if (request.method !== "POST") {
26
- return { body: { error: "method-not-allowed", ok: false }, status: 405 };
27
- }
28
- const { body } = request;
29
- if (body === void 0 || typeof body !== "object") {
30
- return { body: { error: "invalid-request", ok: false }, status: 400 };
31
- }
32
- const { count, existingIds, seed, table } = body;
33
- if (typeof table !== "string" || table.length === 0) {
34
- return { body: { error: "missing-table", ok: false }, status: 400 };
35
- }
36
- const lunoraDirectory = request.schemaDirectory ?? "lunora";
37
- const schemaPath = join(request.projectRoot, lunoraDirectory, "schema.ts");
38
- if (!existsSync(schemaPath)) {
39
- return { body: { error: "schema-not-found", ok: false }, status: 404 };
40
- }
41
- const project = new Project({ skipAddingFilesFromTsConfig: true });
42
- const ir = discoverSchema(project, schemaPath);
43
- if (!ir.tables.some((candidate) => candidate.name === table)) {
44
- return { body: { error: "unknown-table", ok: false, table }, status: 404 };
45
- }
46
- const schema = schemaFromIr(ir);
47
- const plan = seedPlan(schema, {
48
- // Pass every sampled parent table as `existingIds` so the generator links
49
- // to live rows and never fabricates parents — matching the studio's
50
- // "existing rows are not affected, FKs point at what's there" semantics.
51
- existingIds: existingIds ?? {},
52
- defaultCount: clampCount(count),
53
- only: [table],
54
- seed: seed ?? 0
55
- });
56
- const rows = plan.find((entry) => entry.table === table)?.rows ?? [];
57
- const jsonSafeRows = JSON.parse(JSON.stringify(rows, jsonReplacer));
58
- return { body: { ok: true, rows: jsonSafeRows }, status: 200 };
59
- };
60
-
61
- export { SEED_ENDPOINT, handleSeedRequest };
@@ -1,25 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { parse } from 'jsonc-parser';
3
- import { join } from 'node:path';
4
-
5
- const WRANGLER_FILES = ["wrangler.jsonc", "wrangler.json"];
6
- const findWranglerFile = (projectRoot) => {
7
- for (const candidate of WRANGLER_FILES) {
8
- const fullPath = join(projectRoot, candidate);
9
- if (existsSync(fullPath)) {
10
- return fullPath;
11
- }
12
- }
13
- return void 0;
14
- };
15
- const readWranglerJsonc = (wranglerPath) => {
16
- const text = readFileSync(wranglerPath, "utf8");
17
- const parseErrors = [];
18
- const value = parse(text, parseErrors, { allowTrailingComma: true });
19
- if (parseErrors.length > 0 || value === null || typeof value !== "object") {
20
- return { parsed: void 0, text };
21
- }
22
- return { parsed: value, text };
23
- };
24
-
25
- export { WRANGLER_FILES, findWranglerFile, readWranglerJsonc };
@@ -1,228 +0,0 @@
1
- import { Project, SyntaxKind } from 'ts-morph';
2
- import { collectCalls } from './parseSchema-DSeyktvG.mjs';
3
-
4
- const IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
5
- const VALIDATOR_METHODS = /* @__PURE__ */ new Set([
6
- "any",
7
- "array",
8
- "bigint",
9
- "boolean",
10
- "bytes",
11
- "date",
12
- "id",
13
- "literal",
14
- "null",
15
- "number",
16
- "object",
17
- "optional",
18
- "record",
19
- "storage",
20
- "string",
21
- "timestamp",
22
- "union"
23
- ]);
24
- const isValidatorExpression = (node) => {
25
- if (node.getKind() === SyntaxKind.ParenthesizedExpression) {
26
- return isValidatorExpression(node.asKindOrThrow(SyntaxKind.ParenthesizedExpression).getExpression());
27
- }
28
- if (node.getKind() !== SyntaxKind.CallExpression) {
29
- return false;
30
- }
31
- const call = node.asKindOrThrow(SyntaxKind.CallExpression);
32
- const callee = call.getExpression();
33
- if (callee.getKind() !== SyntaxKind.PropertyAccessExpression) {
34
- return false;
35
- }
36
- const access = callee.asKindOrThrow(SyntaxKind.PropertyAccessExpression);
37
- if (access.getExpression().getKind() !== SyntaxKind.Identifier || access.getExpression().getText() !== "v" || !VALIDATOR_METHODS.has(access.getName())) {
38
- return false;
39
- }
40
- return call.getArguments().every((argument) => isValidatorArgument(argument));
41
- };
42
- const isValidatorArgument = (node) => {
43
- const kind = node.getKind();
44
- if (kind === SyntaxKind.ParenthesizedExpression) {
45
- return isValidatorArgument(node.asKindOrThrow(SyntaxKind.ParenthesizedExpression).getExpression());
46
- }
47
- if (kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NumericLiteral || kind === SyntaxKind.TrueKeyword || kind === SyntaxKind.FalseKeyword || kind === SyntaxKind.NullKeyword) {
48
- return true;
49
- }
50
- if (kind === SyntaxKind.PrefixUnaryExpression) {
51
- const unary = node.asKindOrThrow(SyntaxKind.PrefixUnaryExpression);
52
- return unary.getOperand().getKind() === SyntaxKind.NumericLiteral;
53
- }
54
- if (kind === SyntaxKind.ObjectLiteralExpression) {
55
- return node.asKindOrThrow(SyntaxKind.ObjectLiteralExpression).getProperties().every((property) => {
56
- if (property.getKind() !== SyntaxKind.PropertyAssignment) {
57
- return false;
58
- }
59
- const initializer = property.getInitializer();
60
- return initializer !== void 0 && isValidatorArgument(initializer);
61
- });
62
- }
63
- if (kind === SyntaxKind.ArrayLiteralExpression) {
64
- return node.asKindOrThrow(SyntaxKind.ArrayLiteralExpression).getElements().every((element) => isValidatorArgument(element));
65
- }
66
- return isValidatorExpression(node);
67
- };
68
- const isAllowedValidatorText = (validator) => {
69
- if (typeof validator !== "string" || validator.trim().length === 0) {
70
- return false;
71
- }
72
- const project = new Project({ compilerOptions: { allowJs: true }, useInMemoryFileSystem: true });
73
- const sourceFile = project.createSourceFile("validator.ts", `const __v = (${validator});`, { overwrite: true });
74
- if (project.getProgram().getSyntacticDiagnostics(sourceFile).length > 0) {
75
- return false;
76
- }
77
- if (sourceFile.getStatements().length !== 1) {
78
- return false;
79
- }
80
- const declaration = sourceFile.getVariableDeclaration("__v");
81
- const initializer = declaration?.getInitializer();
82
- return initializer !== void 0 && isValidatorExpression(initializer);
83
- };
84
- const ADDITIVE_KINDS = /* @__PURE__ */ new Set(["addIndex", "addOptionalColumn", "addTable"]);
85
- const classifyEdit = (edit) => ADDITIVE_KINDS.has(edit.kind) ? "additive" : "destructive";
86
- const isIdentifier = (value) => typeof value === "string" && IDENTIFIER_PATTERN.test(value);
87
- const validateAdditiveEdit = (edit) => {
88
- if (!isIdentifier(edit.table)) {
89
- return "invalid-identifier";
90
- }
91
- if (edit.kind === "addOptionalColumn") {
92
- if (!isIdentifier(edit.column)) {
93
- return "invalid-identifier";
94
- }
95
- if (!isAllowedValidatorText(edit.validator)) {
96
- return "invalid-validator";
97
- }
98
- }
99
- if (edit.kind === "addIndex") {
100
- if (!isIdentifier(edit.name)) {
101
- return "invalid-identifier";
102
- }
103
- if (!Array.isArray(edit.fields) || !edit.fields.every((field) => isIdentifier(field))) {
104
- return "invalid-identifier";
105
- }
106
- }
107
- return void 0;
108
- };
109
- const findTablesObject = (source) => {
110
- const project = new Project({ compilerOptions: { allowJs: true }, useInMemoryFileSystem: true });
111
- const sourceFile = project.createSourceFile("schema.ts", source, { overwrite: true });
112
- let defineSchemaCall;
113
- for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
114
- if (call.getExpression().getText() === "defineSchema") {
115
- defineSchemaCall = call;
116
- break;
117
- }
118
- }
119
- if (defineSchemaCall === void 0) {
120
- const aliased = sourceFile.getImportDeclarations().some((declaration) => declaration.getNamedImports().some((named) => named.getName() === "defineSchema" && named.getAliasNode() !== void 0));
121
- return { reason: aliased ? "aliased-define-schema" : "no-define-schema" };
122
- }
123
- const tablesArgument = defineSchemaCall.getArguments()[0];
124
- if (tablesArgument?.getKind() !== SyntaxKind.ObjectLiteralExpression) {
125
- return { reason: "non-object-argument" };
126
- }
127
- return { object: tablesArgument.asKindOrThrow(SyntaxKind.ObjectLiteralExpression), sourceFile };
128
- };
129
- const findTableProperty = (tablesObject, table) => {
130
- for (const property of tablesObject.getProperties()) {
131
- if (property.getKind() === SyntaxKind.PropertyAssignment && property.getNameNode().getText().replaceAll(/["']/gu, "") === table) {
132
- return property;
133
- }
134
- }
135
- return void 0;
136
- };
137
- const findDefineTableShape = (tableProperty) => {
138
- const defineTableCall = tableProperty.getInitializer()?.getDescendantsOfKind(SyntaxKind.CallExpression).find((call) => call.getExpression().getText() === "defineTable");
139
- const shape = defineTableCall?.getArguments()[0];
140
- return shape?.getKind() === SyntaxKind.ObjectLiteralExpression ? shape.asKindOrThrow(SyntaxKind.ObjectLiteralExpression) : void 0;
141
- };
142
- const applyAddTable = (tablesObject, edit) => {
143
- if (findTableProperty(tablesObject, edit.table) !== void 0) {
144
- return "duplicate-table";
145
- }
146
- tablesObject.addPropertyAssignment({
147
- initializer: `defineTable({
148
- // Add your column validators here.
149
- // Example:
150
- // text: v.string(),
151
- // createdAt: v.number(),
152
- })`,
153
- name: edit.table
154
- });
155
- return void 0;
156
- };
157
- const applyAddOptionalColumn = (tablesObject, edit) => {
158
- const tableProperty = findTableProperty(tablesObject, edit.table);
159
- if (tableProperty === void 0) {
160
- return "unknown-table";
161
- }
162
- const shape = findDefineTableShape(tableProperty);
163
- if (shape === void 0) {
164
- return "unknown-table";
165
- }
166
- const exists = shape.getProperties().some(
167
- (property) => property.getKind() === SyntaxKind.PropertyAssignment && property.getNameNode().getText().replaceAll(/["']/gu, "") === edit.column
168
- );
169
- if (exists) {
170
- return "duplicate-column";
171
- }
172
- shape.addPropertyAssignment({ initializer: `v.optional(${edit.validator})`, name: edit.column });
173
- return void 0;
174
- };
175
- const applyAddIndex = (tableProperty, edit) => {
176
- if (tableProperty === void 0) {
177
- return "unknown-table";
178
- }
179
- const initializer = tableProperty.getInitializer();
180
- if (initializer === void 0) {
181
- return "unknown-table";
182
- }
183
- const duplicate = collectCalls(initializer).some((call) => {
184
- const expression = call.getExpression();
185
- if (expression.getKind() !== SyntaxKind.PropertyAccessExpression) {
186
- return false;
187
- }
188
- const access = expression.asKindOrThrow(SyntaxKind.PropertyAccessExpression);
189
- const [nameArgument] = call.getArguments();
190
- return access.getName() === "index" && nameArgument?.getKind() === SyntaxKind.StringLiteral && nameArgument.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralText() === edit.name;
191
- });
192
- if (duplicate) {
193
- return "duplicate-index";
194
- }
195
- const fields = edit.fields.map((field) => JSON.stringify(field)).join(", ");
196
- const options = edit.unique === true ? ", { unique: true }" : "";
197
- tableProperty.setInitializer(`${initializer.getText()}.index(${JSON.stringify(edit.name)}, [${fields}]${options})`);
198
- return void 0;
199
- };
200
- const applyAdditiveEdit = (source, edit) => {
201
- if (classifyEdit(edit) === "destructive") {
202
- return { ok: false, reason: "destructive" };
203
- }
204
- const additive = edit;
205
- const invalid = validateAdditiveEdit(additive);
206
- if (invalid !== void 0) {
207
- return { ok: false, reason: invalid };
208
- }
209
- const located = findTablesObject(source);
210
- if ("reason" in located) {
211
- return { ok: false, reason: located.reason };
212
- }
213
- const { object: tablesObject, sourceFile } = located;
214
- let failure;
215
- if (additive.kind === "addTable") {
216
- failure = applyAddTable(tablesObject, additive);
217
- } else if (additive.kind === "addOptionalColumn") {
218
- failure = applyAddOptionalColumn(tablesObject, additive);
219
- } else {
220
- failure = applyAddIndex(findTableProperty(tablesObject, additive.table), additive);
221
- }
222
- if (failure !== void 0) {
223
- return { ok: false, reason: failure };
224
- }
225
- return { ok: true, text: sourceFile.getFullText() };
226
- };
227
-
228
- export { applyAdditiveEdit, classifyEdit };
@@ -1,187 +0,0 @@
1
- import { randomBytes } from 'node:crypto';
2
- import { existsSync, readFileSync, writeFileSync, renameSync, rmSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import { DEV_VARS_FILE, DEV_VARS_EXAMPLE_FILE, parseDevVariableEntries, DEV_VARS_NEWLINE, splitDevVariableLine, unquoteDevVariable } from './DEV_VARS_EXAMPLE_FILE-dJPNTEnK.mjs';
5
- import { secretsForPackages } from './PACKAGE_SECRETS_REGISTRY-CySy5vR_.mjs';
6
-
7
- const SECRET_BYTES = 32;
8
- const SECRET_KEY = /(?:KEY|PASSWORD|SECRET|TOKEN)$/u;
9
- const PLACEHOLDER_MARKERS = [
10
- "replace",
11
- "openssl",
12
- "changeme",
13
- "change-me",
14
- "change_me",
15
- "change-this",
16
- "change_this",
17
- "your-",
18
- "your_",
19
- "example",
20
- "placeholder",
21
- "todo",
22
- "fill-me",
23
- "fill_me",
24
- "fill-in",
25
- "fill_in",
26
- "xxx"
27
- ];
28
- const REGEXP_SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/gu;
29
- const MARKER_ENDS_ALPHANUMERIC = /[a-z0-9]$/u;
30
- const isPlaceholderValue = (value) => {
31
- const normalised = value.trim().toLowerCase();
32
- if (normalised === "") {
33
- return true;
34
- }
35
- if (normalised.startsWith("<") && normalised.endsWith(">")) {
36
- return true;
37
- }
38
- return PLACEHOLDER_MARKERS.some((marker) => {
39
- const escaped = marker.replaceAll(REGEXP_SPECIAL_CHARS, String.raw`\$&`);
40
- const needsTrailingBoundary = MARKER_ENDS_ALPHANUMERIC.test(marker);
41
- const pattern = needsTrailingBoundary ? `(^|[^a-z0-9])${escaped}([^a-z0-9]|$)` : `(^|[^a-z0-9])${escaped}`;
42
- return new RegExp(pattern, "u").test(normalised);
43
- });
44
- };
45
- const isPlaceholder = (rawValue) => isPlaceholderValue(unquoteDevVariable(rawValue.trim()));
46
- const defaultRandomHex = (bytes) => randomBytes(bytes).toString("hex");
47
- const generatedSecretFor = (key, rawValue, randomHex) => SECRET_KEY.test(key) && isPlaceholder(rawValue) ? randomHex(SECRET_BYTES) : void 0;
48
- const planDevVariablesScaffold = (input) => {
49
- if (input.devVarsExists) {
50
- return { status: "exists" };
51
- }
52
- if (input.exampleContent === void 0) {
53
- return { status: "no-example" };
54
- }
55
- const randomHex = input.randomHex ?? defaultRandomHex;
56
- const generatedKeys = [];
57
- const lines = input.exampleContent.split(DEV_VARS_NEWLINE).map((line) => {
58
- const parsed = splitDevVariableLine(line);
59
- const secret = parsed ? generatedSecretFor(parsed.key, parsed.value, randomHex) : void 0;
60
- if (!parsed || secret === void 0) {
61
- return line;
62
- }
63
- generatedKeys.push(parsed.key);
64
- return `${parsed.key}="${secret}"`;
65
- });
66
- return { content: lines.join("\n"), generatedKeys, status: "generate" };
67
- };
68
- const planDevVariablesAugment = (input) => {
69
- const randomHex = input.randomHex ?? defaultRandomHex;
70
- const present = new Set(parseDevVariableEntries(input.existingContent).map((entry) => entry.key));
71
- const additions = [];
72
- const generatedKeys = [];
73
- const missingKeys = [];
74
- for (const line of input.exampleContent.split(DEV_VARS_NEWLINE)) {
75
- const parsed = splitDevVariableLine(line);
76
- if (!parsed || present.has(parsed.key)) {
77
- continue;
78
- }
79
- const secret = generatedSecretFor(parsed.key, parsed.value, randomHex);
80
- missingKeys.push(parsed.key);
81
- if (secret === void 0) {
82
- additions.push(`${parsed.key}="${unquoteDevVariable(parsed.value)}"`);
83
- } else {
84
- generatedKeys.push(parsed.key);
85
- additions.push(`${parsed.key}="${secret}"`);
86
- }
87
- }
88
- return { additions, generatedKeys, missingKeys };
89
- };
90
- const generatedSuffix = (keys) => keys.length > 0 ? ` (generated ${keys.join(", ")})` : "";
91
- const atomicCreateDevVariables = (path, content) => {
92
- const temporaryPath = `${path}.tmp-${String(process.pid)}`;
93
- try {
94
- writeFileSync(temporaryPath, content, { encoding: "utf8", flag: "wx", mode: 384 });
95
- renameSync(temporaryPath, path);
96
- } catch (error) {
97
- rmSync(temporaryPath, { force: true });
98
- throw error;
99
- }
100
- };
101
- const appendDevVariables = (path, additions) => {
102
- const existing = readFileSync(path, "utf8");
103
- const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
104
- const content = `${existing}${separator}${additions.join("\n")}
105
- `;
106
- const temporaryPath = `${path}.tmp-${String(process.pid)}`;
107
- try {
108
- writeFileSync(temporaryPath, content, { encoding: "utf8", mode: 384 });
109
- renameSync(temporaryPath, path);
110
- } catch (error) {
111
- rmSync(temporaryPath, { force: true });
112
- throw error;
113
- }
114
- };
115
- const ensureDevVariables = async (deps) => {
116
- const devVariablesPath = join(deps.cwd, DEV_VARS_FILE);
117
- const examplePath = join(deps.cwd, DEV_VARS_EXAMPLE_FILE);
118
- if (!existsSync(examplePath)) {
119
- return { addedKeys: [], generatedKeys: [], status: "no-example" };
120
- }
121
- const exampleContent = readFileSync(examplePath, "utf8");
122
- if (!existsSync(devVariablesPath)) {
123
- const plan = planDevVariablesScaffold({ devVarsExists: false, exampleContent, randomHex: deps.randomHex });
124
- if (plan.status !== "generate") {
125
- return { addedKeys: [], generatedKeys: [], status: "no-example" };
126
- }
127
- const proceed2 = deps.yes === true || await deps.confirm(`No ${DEV_VARS_FILE} found. Generate it from ${DEV_VARS_EXAMPLE_FILE} (secrets auto-filled)?`);
128
- if (!proceed2) {
129
- deps.info(`Skipped — copy ${DEV_VARS_EXAMPLE_FILE} to ${DEV_VARS_FILE} and fill it in when you're ready.`);
130
- return { addedKeys: [], generatedKeys: [], status: "declined" };
131
- }
132
- if (existsSync(devVariablesPath)) {
133
- return { addedKeys: [], generatedKeys: [], status: "skipped-exists" };
134
- }
135
- atomicCreateDevVariables(devVariablesPath, plan.content);
136
- deps.info(`Created ${DEV_VARS_FILE}${generatedSuffix(plan.generatedKeys)}.`);
137
- return { addedKeys: [], generatedKeys: plan.generatedKeys, status: "generated" };
138
- }
139
- const augment = planDevVariablesAugment({ existingContent: readFileSync(devVariablesPath, "utf8"), exampleContent, randomHex: deps.randomHex });
140
- if (augment.missingKeys.length === 0) {
141
- return { addedKeys: [], generatedKeys: [], status: "exists" };
142
- }
143
- const list = augment.missingKeys.join(", ");
144
- 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?`);
145
- if (!proceed) {
146
- deps.info(`Skipped — add ${list} to ${DEV_VARS_FILE} when you're ready.`);
147
- return { addedKeys: [], generatedKeys: [], status: "declined" };
148
- }
149
- appendDevVariables(devVariablesPath, augment.additions);
150
- deps.info(`Updated ${DEV_VARS_FILE} — added ${list}${generatedSuffix(augment.generatedKeys)}.`);
151
- return { addedKeys: augment.missingKeys, generatedKeys: augment.generatedKeys, status: "augmented" };
152
- };
153
- const secretEntryBlock = (entry) => {
154
- const lines = [`# ${entry.description}`, `# Docs: ${entry.docsUrl}`, `${entry.key}="${entry.placeholderValue}"`];
155
- return lines.join("\n");
156
- };
157
- const buildPackageSecretsBlock = (packageNames, existingKeys) => {
158
- const entries = secretsForPackages(packageNames).filter((entry) => !existingKeys.has(entry.key));
159
- if (entries.length === 0) {
160
- return "";
161
- }
162
- return entries.map((entry) => secretEntryBlock(entry)).join("\n\n");
163
- };
164
- const ensureDevVariablesExample = (cwd, packageNames) => {
165
- const examplePath = join(cwd, DEV_VARS_EXAMPLE_FILE);
166
- const existing = existsSync(examplePath) ? readFileSync(examplePath, "utf8") : "";
167
- const existingKeys = new Set(parseDevVariableEntries(existing).map((entry) => entry.key));
168
- const block = buildPackageSecretsBlock(packageNames, existingKeys);
169
- if (block === "") {
170
- return [];
171
- }
172
- const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
173
- const newContent = `${existing}${separator}
174
- ${block}
175
- `;
176
- const temporaryPath = `${examplePath}.tmp-${String(process.pid)}`;
177
- try {
178
- writeFileSync(temporaryPath, newContent, { encoding: "utf8" });
179
- renameSync(temporaryPath, examplePath);
180
- } catch (error) {
181
- rmSync(temporaryPath, { force: true });
182
- throw error;
183
- }
184
- return secretsForPackages(packageNames).filter((entry) => !existingKeys.has(entry.key)).map((entry) => entry.key);
185
- };
186
-
187
- export { buildPackageSecretsBlock, ensureDevVariables, ensureDevVariablesExample as ensureDevVarsExample, isPlaceholderValue, planDevVariablesAugment, planDevVariablesScaffold };
@@ -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 };