@lunora/codegen 1.0.0-alpha.5 → 1.0.0-alpha.50
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 +1950 -734
- package/dist/index.d.ts +1950 -734
- package/dist/index.mjs +32 -24
- package/dist/packem_shared/AGENTS_FILENAME-Cxx7wiSs.mjs +158 -0
- package/dist/packem_shared/{CONTAINERS_FILENAME-0K-pjNb8.mjs → CONTAINERS_FILENAME-DjpXMqhp.mjs} +1 -1
- package/dist/packem_shared/{CodegenDiagnosticError-54jWDxA9.mjs → CodegenDiagnosticError-DyQ5FwkM.mjs} +7 -5
- package/dist/packem_shared/FLAGS_FILENAME-Dg4mKUuz.mjs +139 -0
- package/dist/packem_shared/GENERATED_HEADER-C5paWNYO.mjs +4 -0
- package/dist/packem_shared/{LUNORA_ERROR_CODES-CySpQPD3.mjs → LUNORA_ERROR_CODES-DvTLozCu.mjs} +8 -1
- package/dist/packem_shared/MUTATORS_FILENAME-BZOfUhlY.mjs +81 -0
- package/dist/packem_shared/{OPENRPC_VERSION-C7zKVeKi.mjs → OPENRPC_VERSION-7A2weq2u.mjs} +2 -2
- package/dist/packem_shared/QUEUES_FILENAME-Xv6Vy-Ku.mjs +145 -0
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-G5dkcxPR.mjs +3502 -0
- package/dist/packem_shared/{SCHEMA_SNAPSHOT_VERSION-DzLDbWk3.mjs → SCHEMA_SNAPSHOT_VERSION-D0ARY6rL.mjs} +18 -2
- package/dist/packem_shared/SHAPES_FILENAME-DOhPGi-6.mjs +94 -0
- package/dist/packem_shared/WORKFLOWS_FILENAME-Dtuzvluc.mjs +158 -0
- package/dist/packem_shared/{buildOpenApiDocument-Cns1EkCQ.mjs → buildOpenApiDocument-B7mEdzA2.mjs} +2 -2
- package/dist/packem_shared/{discoverAuthApiCalls-C35R6z0T.mjs → discoverAuthApiCalls-Dx3K42rk.mjs} +1 -1
- package/dist/packem_shared/{discoverCrons-BL6iGuJ3.mjs → discoverCrons-DvqkEWdx.mjs} +68 -36
- package/dist/packem_shared/{discoverFunctions-DEgAcRuD.mjs → discoverFunctions-BJ-qR7Rg.mjs} +72 -14
- package/dist/packem_shared/{discoverHttpRoutes-C978pBiG.mjs → discoverHttpRoutes-daCzuTe8.mjs} +10 -2
- package/dist/packem_shared/{discoverInserts-CRQdXvHO.mjs → discoverInserts-DI4q5NxE.mjs} +24 -2
- package/dist/packem_shared/{discoverMaskProcedures-B64zA740.mjs → discoverMaskProcedures-BcTOEKNU.mjs} +58 -2
- package/dist/packem_shared/{discoverMigrations-Doj_-BAA.mjs → discoverMigrations-VNUFvCwr.mjs} +10 -4
- package/dist/packem_shared/{discoverNondeterministicCalls-4KiPQxQU.mjs → discoverNondeterministicCalls-S0N2xLCq.mjs} +1 -1
- package/dist/packem_shared/{discoverQueries-BkIi0dBD.mjs → discoverQueries-CJnnnLpd.mjs} +1 -1
- package/dist/packem_shared/{discoverR2sqlCalls-BpDqvcUn.mjs → discoverR2sqlCalls-pnpicWfz.mjs} +1 -1
- package/dist/packem_shared/{discoverRlsMetadata-DpRB1HMe.mjs → discoverRlsMetadata-DppniPUH.mjs} +1 -1
- package/dist/packem_shared/discoverSandboxUsage-BUM2r90k.mjs +38 -0
- package/dist/packem_shared/{discoverSchema-DB_QrIQe.mjs → discoverSchema-BaSAvooG.mjs} +326 -14
- package/dist/packem_shared/{discoverStorageRulesMetadata-DAqJUxUv.mjs → discoverStorageRulesMetadata-CnHl2rXD.mjs} +1 -1
- package/dist/packem_shared/{GENERATED_HEADER-DF1hQcix.mjs → emit-ClTbCLy4.mjs} +1458 -154
- package/dist/packem_shared/{emitApp-Ci_hcJNO.mjs → emitApp-CzzrjVqH.mjs} +129 -31
- package/dist/packem_shared/formatAdvisories-BmF7Mplc.mjs +115 -0
- package/dist/packem_shared/{parse-validator-tuQtHrsr.mjs → parse-validator-BSJo1HGP.mjs} +18 -6
- package/dist/packem_shared/redact-oTmsol5A.mjs +33 -0
- package/package.json +10 -7
- package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-BqOtU2lC.mjs +0 -922
- package/dist/packem_shared/WORKFLOWS_FILENAME-DRDQdhfq.mjs +0 -84
- package/dist/packem_shared/formatAdvisories-8NIv1k0I.mjs +0 -69
|
@@ -1,922 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { join, dirname } from 'node:path';
|
|
3
|
-
import { Node, SyntaxKind, Project } from 'ts-morph';
|
|
4
|
-
import { lintSchema } from './formatAdvisories-8NIv1k0I.mjs';
|
|
5
|
-
import { listLunoraSourceFiles, lunoraRelativePath, classifyProcedureCall, discoverFunctions } from './discoverFunctions-DEgAcRuD.mjs';
|
|
6
|
-
import discoverAuthApiCalls from './discoverAuthApiCalls-C35R6z0T.mjs';
|
|
7
|
-
import { discoverContainers } from './CONTAINERS_FILENAME-0K-pjNb8.mjs';
|
|
8
|
-
import discoverCrons from './discoverCrons-BL6iGuJ3.mjs';
|
|
9
|
-
import discoverHttpRoutes from './discoverHttpRoutes-C978pBiG.mjs';
|
|
10
|
-
import discoverInserts from './discoverInserts-CRQdXvHO.mjs';
|
|
11
|
-
import discoverMaskProcedures, { discoverMaskMetadata } from './discoverMaskProcedures-B64zA740.mjs';
|
|
12
|
-
import discoverMigrations from './discoverMigrations-Doj_-BAA.mjs';
|
|
13
|
-
import discoverNondeterministicCalls from './discoverNondeterministicCalls-4KiPQxQU.mjs';
|
|
14
|
-
import discoverQueries from './discoverQueries-BkIi0dBD.mjs';
|
|
15
|
-
import discoverR2sqlCalls from './discoverR2sqlCalls-BpDqvcUn.mjs';
|
|
16
|
-
import discoverRlsProcedures, { discoverRlsMetadata } from './discoverRlsMetadata-DpRB1HMe.mjs';
|
|
17
|
-
import discoverSchema from './discoverSchema-DB_QrIQe.mjs';
|
|
18
|
-
import discoverStorageRulesMetadata from './discoverStorageRulesMetadata-DAqJUxUv.mjs';
|
|
19
|
-
import { e as enclosingExportName$1 } from './discover-ast-CT6BgBr4.mjs';
|
|
20
|
-
import { discoverWorkflows } from './WORKFLOWS_FILENAME-DRDQdhfq.mjs';
|
|
21
|
-
import { buildStorageColumns, emitDataModel, emitApi, emitServer, emitFunctions, emitShard, emitContainers, emitWorkflows, emitCrons, emitVectors, emitDrizzleSchema, emitSeed, emitWranglerCronTriggers } from './GENERATED_HEADER-DF1hQcix.mjs';
|
|
22
|
-
import { emitApp } from './emitApp-Ci_hcJNO.mjs';
|
|
23
|
-
import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-Cns1EkCQ.mjs';
|
|
24
|
-
import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-C7zKVeKi.mjs';
|
|
25
|
-
import { buildSchemaSnapshot, serializeSchemaSnapshot } from './SCHEMA_SNAPSHOT_VERSION-DzLDbWk3.mjs';
|
|
26
|
-
|
|
27
|
-
const HTTP_VERBS = /* @__PURE__ */ new Set(["delete", "get", "head", "options", "patch", "post", "put"]);
|
|
28
|
-
const TERMINAL_STEPS = /* @__PURE__ */ new Set(["handler", "stream"]);
|
|
29
|
-
const ADMIN_PATH_RE = /\/(?:_|admin|internal|superuser|sudo|root|debug)/iu;
|
|
30
|
-
const GUARD_NAMES = /* @__PURE__ */ new Set([
|
|
31
|
-
"ADMIN_TOKEN",
|
|
32
|
-
"adminToken",
|
|
33
|
-
"assertAdmin",
|
|
34
|
-
"assertAuth",
|
|
35
|
-
"auth",
|
|
36
|
-
"Authorization",
|
|
37
|
-
"getSession",
|
|
38
|
-
"identity",
|
|
39
|
-
"isAdmin",
|
|
40
|
-
"requireAdmin",
|
|
41
|
-
"requireAuth",
|
|
42
|
-
"requireRole",
|
|
43
|
-
"verifyAdmin"
|
|
44
|
-
]);
|
|
45
|
-
const readRootVerb = (node) => {
|
|
46
|
-
if (!Node.isCallExpression(node)) {
|
|
47
|
-
return void 0;
|
|
48
|
-
}
|
|
49
|
-
const callee = node.getExpression();
|
|
50
|
-
if (!Node.isPropertyAccessExpression(callee) || !HTTP_VERBS.has(callee.getName())) {
|
|
51
|
-
return void 0;
|
|
52
|
-
}
|
|
53
|
-
const receiver = callee.getExpression();
|
|
54
|
-
if (!Node.isIdentifier(receiver) || receiver.getText() !== "httpRoute") {
|
|
55
|
-
return void 0;
|
|
56
|
-
}
|
|
57
|
-
const first = node.getArguments()[0];
|
|
58
|
-
if (!first || !Node.isStringLiteral(first)) {
|
|
59
|
-
return void 0;
|
|
60
|
-
}
|
|
61
|
-
return { method: callee.getName().toUpperCase(), path: first.getLiteralValue() };
|
|
62
|
-
};
|
|
63
|
-
const rootOfChain = (terminalCall) => {
|
|
64
|
-
const terminalCallee = terminalCall.getExpression();
|
|
65
|
-
if (!Node.isPropertyAccessExpression(terminalCallee)) {
|
|
66
|
-
return void 0;
|
|
67
|
-
}
|
|
68
|
-
let node = terminalCallee.getExpression();
|
|
69
|
-
while (Node.isCallExpression(node)) {
|
|
70
|
-
const callee = node.getExpression();
|
|
71
|
-
if (!Node.isPropertyAccessExpression(callee)) {
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
if (HTTP_VERBS.has(callee.getName())) {
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
node = callee.getExpression();
|
|
78
|
-
}
|
|
79
|
-
return readRootVerb(node);
|
|
80
|
-
};
|
|
81
|
-
const handlerReferencesGuard = (handlerBody) => {
|
|
82
|
-
for (const access of handlerBody.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)) {
|
|
83
|
-
if (GUARD_NAMES.has(access.getName())) {
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
for (const call of handlerBody.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
88
|
-
const callee = call.getExpression();
|
|
89
|
-
if (Node.isIdentifier(callee) && GUARD_NAMES.has(callee.getText())) {
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return false;
|
|
94
|
-
};
|
|
95
|
-
const adminRouteFromDeclaration = (declaration, relativePath) => {
|
|
96
|
-
const initializer = declaration.getInitializer();
|
|
97
|
-
if (!initializer || !Node.isCallExpression(initializer)) {
|
|
98
|
-
return void 0;
|
|
99
|
-
}
|
|
100
|
-
const callee = initializer.getExpression();
|
|
101
|
-
if (!Node.isPropertyAccessExpression(callee) || !TERMINAL_STEPS.has(callee.getName())) {
|
|
102
|
-
return void 0;
|
|
103
|
-
}
|
|
104
|
-
const root = rootOfChain(initializer);
|
|
105
|
-
if (!root || !ADMIN_PATH_RE.test(root.path)) {
|
|
106
|
-
return void 0;
|
|
107
|
-
}
|
|
108
|
-
const handlerArgument = initializer.getArguments()[0];
|
|
109
|
-
const usesGuard = handlerArgument !== void 0 && (Node.isArrowFunction(handlerArgument) || Node.isFunctionExpression(handlerArgument)) && handlerReferencesGuard(handlerArgument);
|
|
110
|
-
return { exportName: declaration.getName(), file: relativePath, method: root.method, path: root.path, usesGuard };
|
|
111
|
-
};
|
|
112
|
-
const adminRoutesInSourceFile = (sourceFile, relativePath) => {
|
|
113
|
-
const found = [];
|
|
114
|
-
for (const statement of sourceFile.getVariableStatements()) {
|
|
115
|
-
if (!statement.isExported()) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
for (const declaration of statement.getDeclarations()) {
|
|
119
|
-
const ir = adminRouteFromDeclaration(declaration, relativePath);
|
|
120
|
-
if (ir) {
|
|
121
|
-
found.push(ir);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return found;
|
|
126
|
-
};
|
|
127
|
-
const discoverAdminRoutes = (project, lunoraDirectory) => {
|
|
128
|
-
const routes = [];
|
|
129
|
-
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
130
|
-
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
131
|
-
routes.push(...adminRoutesInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
|
|
132
|
-
}
|
|
133
|
-
return routes;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const BOUND_RE = /\.check\(|\.meta\(|length|max/iu;
|
|
137
|
-
const ANY_VALIDATOR_RE = /\bv\.any\s*\(/u;
|
|
138
|
-
const STRING_VALIDATOR_RE = /\bv\.string\s*\(/u;
|
|
139
|
-
const isAnyValidator = (text) => ANY_VALIDATOR_RE.test(text);
|
|
140
|
-
const isUnboundedString = (text) => STRING_VALIDATOR_RE.test(text) && !BOUND_RE.test(text);
|
|
141
|
-
const argsObjectOfFactory = (call) => {
|
|
142
|
-
const first = call.getArguments()[0];
|
|
143
|
-
if (!first || !Node.isObjectLiteralExpression(first)) {
|
|
144
|
-
return void 0;
|
|
145
|
-
}
|
|
146
|
-
const argsProperty = first.getProperty("args");
|
|
147
|
-
if (!argsProperty || !Node.isPropertyAssignment(argsProperty)) {
|
|
148
|
-
return void 0;
|
|
149
|
-
}
|
|
150
|
-
const initializer = argsProperty.getInitializer();
|
|
151
|
-
return initializer && Node.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
152
|
-
};
|
|
153
|
-
const inputObjectsInChain = (receiver) => {
|
|
154
|
-
const objects = [];
|
|
155
|
-
let node = receiver;
|
|
156
|
-
while (Node.isCallExpression(node)) {
|
|
157
|
-
const chainCallee = node.getExpression();
|
|
158
|
-
if (!Node.isPropertyAccessExpression(chainCallee)) {
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
if (chainCallee.getName() === "input") {
|
|
162
|
-
const argument = node.getArguments()[0];
|
|
163
|
-
if (argument && Node.isObjectLiteralExpression(argument)) {
|
|
164
|
-
objects.push(argument);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
node = chainCallee.getExpression();
|
|
168
|
-
}
|
|
169
|
-
return objects;
|
|
170
|
-
};
|
|
171
|
-
const classifyArgs = (objects) => {
|
|
172
|
-
const anyArgs = [];
|
|
173
|
-
const unboundedStringArgs = [];
|
|
174
|
-
for (const object of objects) {
|
|
175
|
-
for (const property of object.getProperties()) {
|
|
176
|
-
if (!Node.isPropertyAssignment(property)) {
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
const initializer = property.getInitializer();
|
|
180
|
-
if (!initializer) {
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
const text = initializer.getText();
|
|
184
|
-
const name = property.getName();
|
|
185
|
-
if (isAnyValidator(text)) {
|
|
186
|
-
anyArgs.push(name);
|
|
187
|
-
} else if (isUnboundedString(text)) {
|
|
188
|
-
unboundedStringArgs.push(name);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return { anyArgs, unboundedStringArgs };
|
|
193
|
-
};
|
|
194
|
-
const argumentValidatorIrFromDeclaration = (declaration, relativePath) => {
|
|
195
|
-
const initializer = declaration.getInitializer();
|
|
196
|
-
if (!initializer || !Node.isCallExpression(initializer)) {
|
|
197
|
-
return void 0;
|
|
198
|
-
}
|
|
199
|
-
const classified = classifyProcedureCall(initializer);
|
|
200
|
-
if (classified?.visibility !== "public") {
|
|
201
|
-
return void 0;
|
|
202
|
-
}
|
|
203
|
-
const objects = classified.receiver ? inputObjectsInChain(classified.receiver) : [argsObjectOfFactory(initializer)].filter((object) => object !== void 0);
|
|
204
|
-
const { anyArgs, unboundedStringArgs } = classifyArgs(objects);
|
|
205
|
-
if (anyArgs.length === 0 && unboundedStringArgs.length === 0) {
|
|
206
|
-
return void 0;
|
|
207
|
-
}
|
|
208
|
-
return {
|
|
209
|
-
anyArgs,
|
|
210
|
-
exportName: declaration.getName(),
|
|
211
|
-
file: relativePath,
|
|
212
|
-
line: initializer.getStartLineNumber(),
|
|
213
|
-
unboundedStringArgs
|
|
214
|
-
};
|
|
215
|
-
};
|
|
216
|
-
const argumentValidatorsInSourceFile = (sourceFile, relativePath) => {
|
|
217
|
-
const found = [];
|
|
218
|
-
for (const statement of sourceFile.getVariableStatements()) {
|
|
219
|
-
if (!statement.isExported()) {
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
for (const declaration of statement.getDeclarations()) {
|
|
223
|
-
const ir = argumentValidatorIrFromDeclaration(declaration, relativePath);
|
|
224
|
-
if (ir) {
|
|
225
|
-
found.push(ir);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return found;
|
|
230
|
-
};
|
|
231
|
-
const discoverArgumentValidators = (project, lunoraDirectory) => {
|
|
232
|
-
const procedures = [];
|
|
233
|
-
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
234
|
-
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
235
|
-
procedures.push(...argumentValidatorsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
|
|
236
|
-
}
|
|
237
|
-
return procedures;
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
const PROBES = {
|
|
241
|
-
ai: { contextProperty: "ai", moduleSpecifier: "@lunora/ai" },
|
|
242
|
-
analytics: { contextProperty: "analytics", moduleSpecifier: "@lunora/analytics" },
|
|
243
|
-
browser: { contextProperty: "browser", moduleSpecifier: "@lunora/browser" },
|
|
244
|
-
hyperdrive: { contextProperty: "sql", moduleSpecifier: "@lunora/hyperdrive" },
|
|
245
|
-
images: { contextProperty: "images", moduleSpecifier: "@lunora/images" },
|
|
246
|
-
kv: { contextProperty: "kv", moduleSpecifier: "@lunora/kv" },
|
|
247
|
-
mail: { moduleSpecifier: "@lunora/mail" },
|
|
248
|
-
payments: { contextProperty: "payments", moduleSpecifier: "@lunora/payment" },
|
|
249
|
-
pipelines: { contextProperty: "pipelines", moduleSpecifier: "@lunora/pipelines" },
|
|
250
|
-
r2sql: { contextProperty: "r2sql", moduleSpecifier: "@lunora/r2sql" },
|
|
251
|
-
scheduler: { contextProperty: "scheduler", moduleSpecifier: "@lunora/scheduler" },
|
|
252
|
-
storage: { contextProperty: "storage", moduleSpecifier: "@lunora/storage" },
|
|
253
|
-
vectors: { contextProperty: "vectors", moduleSpecifier: "@lunora/vectors" },
|
|
254
|
-
workflows: { contextProperty: "workflows", moduleSpecifier: "@lunora/workflow" }
|
|
255
|
-
};
|
|
256
|
-
const readsContextProperty = (sourceFile, property) => {
|
|
257
|
-
const reachesContext = (receiver) => Node.isIdentifier(receiver) && receiver.getText() === "ctx";
|
|
258
|
-
const directAccess = sourceFile.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression).some((access) => access.getName() === property && reachesContext(access.getExpression()));
|
|
259
|
-
if (directAccess) {
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
return sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration).some((declaration) => {
|
|
263
|
-
const initializer = declaration.getInitializer();
|
|
264
|
-
const nameNode = declaration.getNameNode();
|
|
265
|
-
if (initializer === void 0 || !reachesContext(initializer) || !Node.isObjectBindingPattern(nameNode)) {
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
return nameNode.getElements().some((element) => element.getPropertyNameNode()?.getText() === property || element.getName() === property);
|
|
269
|
-
});
|
|
270
|
-
};
|
|
271
|
-
const discoverFeatureUsage = (project, lunoraDirectory) => {
|
|
272
|
-
const usage = {
|
|
273
|
-
ai: false,
|
|
274
|
-
analytics: false,
|
|
275
|
-
browser: false,
|
|
276
|
-
hyperdrive: false,
|
|
277
|
-
images: false,
|
|
278
|
-
kv: false,
|
|
279
|
-
mail: false,
|
|
280
|
-
payments: false,
|
|
281
|
-
pipelines: false,
|
|
282
|
-
r2sql: false,
|
|
283
|
-
scheduler: false,
|
|
284
|
-
storage: false,
|
|
285
|
-
vectors: false,
|
|
286
|
-
workflows: false
|
|
287
|
-
};
|
|
288
|
-
const keys = Object.keys(PROBES);
|
|
289
|
-
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
290
|
-
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
291
|
-
const importSpecifiers = new Set(sourceFile.getImportDeclarations().map((declaration) => declaration.getModuleSpecifierValue()));
|
|
292
|
-
for (const key of keys) {
|
|
293
|
-
if (usage[key]) {
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
const probe = PROBES[key];
|
|
297
|
-
if (importSpecifiers.has(probe.moduleSpecifier)) {
|
|
298
|
-
usage[key] = true;
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
if (probe.contextProperty !== void 0 && readsContextProperty(sourceFile, probe.contextProperty)) {
|
|
302
|
-
usage[key] = true;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
if (keys.every((key) => usage[key])) {
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return usage;
|
|
310
|
-
};
|
|
311
|
-
const buildStudioFeatures = (usage, signals) => {
|
|
312
|
-
return {
|
|
313
|
-
mail: usage.mail || signals.dependencies.has("@lunora/mail"),
|
|
314
|
-
payments: usage.payments || signals.dependencies.has("@lunora/payment"),
|
|
315
|
-
scheduler: usage.scheduler || signals.cronCount > 0 || signals.dependencies.has("@lunora/scheduler"),
|
|
316
|
-
storage: usage.storage || signals.storageRuleCount > 0 || signals.storageColumnCount > 0 || signals.dependencies.has("@lunora/storage"),
|
|
317
|
-
vectors: usage.vectors || signals.vectorIndexCount > 0 || signals.dependencies.has("@lunora/vectors"),
|
|
318
|
-
workflows: usage.workflows || signals.workflowCount > 0 || signals.dependencies.has("@lunora/workflow")
|
|
319
|
-
};
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
const discoverPackageDependencies = (projectRoot) => {
|
|
323
|
-
const manifestPath = join(projectRoot, "package.json");
|
|
324
|
-
if (!existsSync(manifestPath)) {
|
|
325
|
-
return /* @__PURE__ */ new Set();
|
|
326
|
-
}
|
|
327
|
-
try {
|
|
328
|
-
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
329
|
-
const names = /* @__PURE__ */ new Set();
|
|
330
|
-
for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
331
|
-
const section = manifest[field];
|
|
332
|
-
if (section !== null && typeof section === "object") {
|
|
333
|
-
for (const name of Object.keys(section)) {
|
|
334
|
-
names.add(name);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
return names;
|
|
339
|
-
} catch {
|
|
340
|
-
return /* @__PURE__ */ new Set();
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
const MIDDLEWARE_FLAGS = {
|
|
345
|
-
dbRateLimit: "usesRateLimit",
|
|
346
|
-
mask: "usesMask",
|
|
347
|
-
rateLimit: "usesRateLimit",
|
|
348
|
-
rls: "usesRls",
|
|
349
|
-
verifyTurnstile: "usesCaptcha",
|
|
350
|
-
verifyTurnstileMiddleware: "usesCaptcha"
|
|
351
|
-
};
|
|
352
|
-
const USER_TABLE_RE = /account|credential|member|passkey|session|user/iu;
|
|
353
|
-
const calleeNameOf = (call) => {
|
|
354
|
-
const callee = call.getExpression();
|
|
355
|
-
if (Node.isIdentifier(callee)) {
|
|
356
|
-
return callee.getText();
|
|
357
|
-
}
|
|
358
|
-
if (Node.isPropertyAccessExpression(callee)) {
|
|
359
|
-
return callee.getName();
|
|
360
|
-
}
|
|
361
|
-
return void 0;
|
|
362
|
-
};
|
|
363
|
-
const protectPublicFlags = (call) => {
|
|
364
|
-
const argument = call.getArguments()[0];
|
|
365
|
-
if (!argument || !Node.isObjectLiteralExpression(argument)) {
|
|
366
|
-
return { usesCaptcha: false, usesRateLimit: false };
|
|
367
|
-
}
|
|
368
|
-
return { usesCaptcha: Boolean(argument.getProperty("captcha")), usesRateLimit: Boolean(argument.getProperty("rateLimit")) };
|
|
369
|
-
};
|
|
370
|
-
const resolveUseArgumentCall = (argument) => {
|
|
371
|
-
if (Node.isCallExpression(argument)) {
|
|
372
|
-
return argument;
|
|
373
|
-
}
|
|
374
|
-
if (!Node.isIdentifier(argument)) {
|
|
375
|
-
return void 0;
|
|
376
|
-
}
|
|
377
|
-
const declaration = argument.getSourceFile().getVariableDeclaration(argument.getText());
|
|
378
|
-
const initializer = declaration?.getInitializer();
|
|
379
|
-
return initializer && Node.isCallExpression(initializer) ? initializer : void 0;
|
|
380
|
-
};
|
|
381
|
-
const NO_PROTECTIONS = { usesCaptcha: false, usesMask: false, usesRateLimit: false, usesRls: false };
|
|
382
|
-
const useStepProtections = (useArgument) => {
|
|
383
|
-
const argument = resolveUseArgumentCall(useArgument);
|
|
384
|
-
const name = argument ? calleeNameOf(argument) : void 0;
|
|
385
|
-
if (argument && name === "protectPublic") {
|
|
386
|
-
const bundle = protectPublicFlags(argument);
|
|
387
|
-
return { ...NO_PROTECTIONS, usesCaptcha: bundle.usesCaptcha, usesRateLimit: bundle.usesRateLimit };
|
|
388
|
-
}
|
|
389
|
-
if (name !== void 0 && name in MIDDLEWARE_FLAGS) {
|
|
390
|
-
return { ...NO_PROTECTIONS, [MIDDLEWARE_FLAGS[name]]: true };
|
|
391
|
-
}
|
|
392
|
-
return NO_PROTECTIONS;
|
|
393
|
-
};
|
|
394
|
-
const protectionsInChain = (receiver) => {
|
|
395
|
-
const protections = { ...NO_PROTECTIONS };
|
|
396
|
-
let node = receiver;
|
|
397
|
-
while (Node.isCallExpression(node)) {
|
|
398
|
-
const chainCallee = node.getExpression();
|
|
399
|
-
if (!Node.isPropertyAccessExpression(chainCallee)) {
|
|
400
|
-
break;
|
|
401
|
-
}
|
|
402
|
-
const useArgument = chainCallee.getName() === "use" ? node.getArguments()[0] : void 0;
|
|
403
|
-
if (useArgument) {
|
|
404
|
-
const step = useStepProtections(useArgument);
|
|
405
|
-
protections.usesCaptcha ||= step.usesCaptcha;
|
|
406
|
-
protections.usesMask ||= step.usesMask;
|
|
407
|
-
protections.usesRateLimit ||= step.usesRateLimit;
|
|
408
|
-
protections.usesRls ||= step.usesRls;
|
|
409
|
-
}
|
|
410
|
-
node = chainCallee.getExpression();
|
|
411
|
-
}
|
|
412
|
-
return protections;
|
|
413
|
-
};
|
|
414
|
-
const isUserTableInsert = (call) => {
|
|
415
|
-
const callee = call.getExpression();
|
|
416
|
-
if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "insert") {
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
const receiver = callee.getExpression();
|
|
420
|
-
const onDatabase = Node.isPropertyAccessExpression(receiver) ? receiver.getName() === "db" : Node.isIdentifier(receiver) && receiver.getText() === "db";
|
|
421
|
-
if (!onDatabase) {
|
|
422
|
-
return false;
|
|
423
|
-
}
|
|
424
|
-
const tableArgument = call.getArguments()[0];
|
|
425
|
-
return Boolean(tableArgument && Node.isStringLiteral(tableArgument) && USER_TABLE_RE.test(tableArgument.getLiteralText()));
|
|
426
|
-
};
|
|
427
|
-
const referencesMail = (declaration) => declaration.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression).some((access) => {
|
|
428
|
-
const name = access.getName();
|
|
429
|
-
if (name !== "mail" && name !== "email") {
|
|
430
|
-
return false;
|
|
431
|
-
}
|
|
432
|
-
const receiver = access.getExpression();
|
|
433
|
-
return Node.isIdentifier(receiver) && receiver.getText() === "ctx";
|
|
434
|
-
});
|
|
435
|
-
const behaviourOf = (declaration) => {
|
|
436
|
-
let writesUserTable = false;
|
|
437
|
-
for (const call of declaration.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
438
|
-
if (isUserTableInsert(call)) {
|
|
439
|
-
writesUserTable = true;
|
|
440
|
-
break;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
return { callsMail: referencesMail(declaration), writesUserTable };
|
|
444
|
-
};
|
|
445
|
-
const middlewareIrFromDeclaration = (declaration, relativePath) => {
|
|
446
|
-
const initializer = declaration.getInitializer();
|
|
447
|
-
if (!initializer || !Node.isCallExpression(initializer)) {
|
|
448
|
-
return void 0;
|
|
449
|
-
}
|
|
450
|
-
const classified = classifyProcedureCall(initializer);
|
|
451
|
-
if (!classified || classified.kind !== "query" && classified.kind !== "mutation" && classified.kind !== "action") {
|
|
452
|
-
return void 0;
|
|
453
|
-
}
|
|
454
|
-
const protections = classified.receiver ? protectionsInChain(classified.receiver) : { usesCaptcha: false, usesMask: false, usesRateLimit: false, usesRls: false };
|
|
455
|
-
const { callsMail, writesUserTable } = behaviourOf(declaration);
|
|
456
|
-
return {
|
|
457
|
-
callsMail,
|
|
458
|
-
exportName: declaration.getName(),
|
|
459
|
-
file: relativePath,
|
|
460
|
-
kind: classified.kind,
|
|
461
|
-
usesCaptcha: protections.usesCaptcha,
|
|
462
|
-
usesMask: protections.usesMask,
|
|
463
|
-
usesRateLimit: protections.usesRateLimit,
|
|
464
|
-
usesRls: protections.usesRls,
|
|
465
|
-
visibility: classified.visibility,
|
|
466
|
-
writesUserTable
|
|
467
|
-
};
|
|
468
|
-
};
|
|
469
|
-
const middlewareInSourceFile = (sourceFile, relativePath) => {
|
|
470
|
-
const found = [];
|
|
471
|
-
for (const statement of sourceFile.getVariableStatements()) {
|
|
472
|
-
if (!statement.isExported()) {
|
|
473
|
-
continue;
|
|
474
|
-
}
|
|
475
|
-
for (const declaration of statement.getDeclarations()) {
|
|
476
|
-
const ir = middlewareIrFromDeclaration(declaration, relativePath);
|
|
477
|
-
if (ir) {
|
|
478
|
-
found.push(ir);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
return found;
|
|
483
|
-
};
|
|
484
|
-
const discoverProcedureMiddleware = (project, lunoraDirectory) => {
|
|
485
|
-
const procedures = [];
|
|
486
|
-
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
487
|
-
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
488
|
-
procedures.push(...middlewareInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
|
|
489
|
-
}
|
|
490
|
-
return procedures;
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
const HIGH_ENTROPY_TOKEN_RE = /[\w+/=-]{40,}/u;
|
|
494
|
-
const LOWER_RE = /[a-z]/u;
|
|
495
|
-
const UPPER_RE = /[A-Z]/u;
|
|
496
|
-
const DIGIT_RE = /\d/u;
|
|
497
|
-
const HEX_SECRET_RE = /\b(?:[\da-f]{32,}|[\dA-F]{32,})\b/u;
|
|
498
|
-
const isHighEntropy = (value) => {
|
|
499
|
-
const token = HIGH_ENTROPY_TOKEN_RE.exec(value)?.[0];
|
|
500
|
-
if (token === void 0) {
|
|
501
|
-
return false;
|
|
502
|
-
}
|
|
503
|
-
return LOWER_RE.test(token) && UPPER_RE.test(token) && DIGIT_RE.test(token);
|
|
504
|
-
};
|
|
505
|
-
const isHexSecret = (value) => HEX_SECRET_RE.test(value);
|
|
506
|
-
const STRIPE_LIVE_KEY_RE = /\b(?:sk|rk)_live_[\dA-Za-z]{20,}/u;
|
|
507
|
-
const AWS_ACCESS_KEY_RE = /\bAKIA[\dA-Z]{16}\b/u;
|
|
508
|
-
const GITHUB_TOKEN_RE = /\bgh[posru]_[\dA-Za-z]{36,}/u;
|
|
509
|
-
const OPENAI_KEY_RE = /\bsk-[\dA-Za-z]{32,}/u;
|
|
510
|
-
const SLACK_TOKEN_RE = /\bxox[abprs]-[\dA-Za-z-]{10,}/u;
|
|
511
|
-
const PRIVATE_KEY_RE = /-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----/u;
|
|
512
|
-
const SECRET_RULES = [
|
|
513
|
-
{ kind: "stripe_live_key", test: (value) => STRIPE_LIVE_KEY_RE.test(value) },
|
|
514
|
-
{ kind: "aws_access_key", test: (value) => AWS_ACCESS_KEY_RE.test(value) },
|
|
515
|
-
{ kind: "github_token", test: (value) => GITHUB_TOKEN_RE.test(value) },
|
|
516
|
-
{ kind: "openai_key", test: (value) => OPENAI_KEY_RE.test(value) },
|
|
517
|
-
{ kind: "slack_token", test: (value) => SLACK_TOKEN_RE.test(value) },
|
|
518
|
-
{ kind: "private_key", test: (value) => PRIVATE_KEY_RE.test(value) },
|
|
519
|
-
{ kind: "high_entropy", test: isHighEntropy },
|
|
520
|
-
{ kind: "hex_secret", test: isHexSecret }
|
|
521
|
-
];
|
|
522
|
-
const secretKindOf = (value) => SECRET_RULES.find((rule) => rule.test(value))?.kind;
|
|
523
|
-
const redact = (value) => `${value.slice(0, 4)}…(${String(value.length)} chars)`;
|
|
524
|
-
const literalValueOf = (node) => {
|
|
525
|
-
if (Node.isStringLiteral(node) || Node.isNoSubstitutionTemplateLiteral(node)) {
|
|
526
|
-
return node.getLiteralText();
|
|
527
|
-
}
|
|
528
|
-
if (Node.isBinaryExpression(node) && node.getOperatorToken().getKind() === SyntaxKind.PlusToken) {
|
|
529
|
-
const left = literalValueOf(node.getLeft());
|
|
530
|
-
const right = literalValueOf(node.getRight());
|
|
531
|
-
return left !== void 0 && right !== void 0 ? left + right : void 0;
|
|
532
|
-
}
|
|
533
|
-
return void 0;
|
|
534
|
-
};
|
|
535
|
-
const isConcatenationOperand = (node) => {
|
|
536
|
-
const parent = node.getParent();
|
|
537
|
-
return parent !== void 0 && Node.isBinaryExpression(parent) && parent.getOperatorToken().getKind() === SyntaxKind.PlusToken;
|
|
538
|
-
};
|
|
539
|
-
const secretsInSourceFile = (sourceFile, relativePath) => {
|
|
540
|
-
const found = [];
|
|
541
|
-
const nodes = [
|
|
542
|
-
// Top-level `+` concatenations are folded; their string-literal operands are
|
|
543
|
-
// skipped below so a secret split across literals is reported once, at the root.
|
|
544
|
-
...sourceFile.getDescendantsOfKind(SyntaxKind.BinaryExpression),
|
|
545
|
-
...sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral),
|
|
546
|
-
...sourceFile.getDescendantsOfKind(SyntaxKind.NoSubstitutionTemplateLiteral)
|
|
547
|
-
];
|
|
548
|
-
for (const node of nodes) {
|
|
549
|
-
if (isConcatenationOperand(node)) {
|
|
550
|
-
continue;
|
|
551
|
-
}
|
|
552
|
-
const value = literalValueOf(node);
|
|
553
|
-
if (value === void 0) {
|
|
554
|
-
continue;
|
|
555
|
-
}
|
|
556
|
-
const kind = secretKindOf(value);
|
|
557
|
-
if (kind !== void 0) {
|
|
558
|
-
found.push({ file: relativePath, kind, line: node.getStartLineNumber(), preview: redact(value) });
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
return found;
|
|
562
|
-
};
|
|
563
|
-
const discoverSecrets = (project, lunoraDirectory) => {
|
|
564
|
-
const secrets = [];
|
|
565
|
-
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
566
|
-
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
567
|
-
secrets.push(...secretsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
|
|
568
|
-
}
|
|
569
|
-
return secrets;
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
const SQL_TEXT_METHODS = /* @__PURE__ */ new Set(["query", "unsafe"]);
|
|
573
|
-
const isContextSqlTextCallee = (node) => {
|
|
574
|
-
if (!Node.isPropertyAccessExpression(node) || !SQL_TEXT_METHODS.has(node.getName())) {
|
|
575
|
-
return false;
|
|
576
|
-
}
|
|
577
|
-
const sqlAccess = node.getExpression();
|
|
578
|
-
if (!Node.isPropertyAccessExpression(sqlAccess) || sqlAccess.getName() !== "sql") {
|
|
579
|
-
return false;
|
|
580
|
-
}
|
|
581
|
-
const receiver = sqlAccess.getExpression();
|
|
582
|
-
return Node.isIdentifier(receiver) && receiver.getText() === "ctx";
|
|
583
|
-
};
|
|
584
|
-
const isStringBuildingText = (expression) => Node.isBinaryExpression(expression) || Node.isTemplateExpression(expression);
|
|
585
|
-
const enclosingExportName = (node) => {
|
|
586
|
-
const declaration = node.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
|
|
587
|
-
return declaration?.getName() ?? "<module>";
|
|
588
|
-
};
|
|
589
|
-
const interpolationInCall = (call, relativePath) => {
|
|
590
|
-
if (!isContextSqlTextCallee(call.getExpression())) {
|
|
591
|
-
return void 0;
|
|
592
|
-
}
|
|
593
|
-
const text = call.getArguments()[0];
|
|
594
|
-
if (!text || !isStringBuildingText(text)) {
|
|
595
|
-
return void 0;
|
|
596
|
-
}
|
|
597
|
-
return { exportName: enclosingExportName(call), file: relativePath, line: text.getStartLineNumber() };
|
|
598
|
-
};
|
|
599
|
-
const interpolationsInSourceFile = (sourceFile, relativePath) => {
|
|
600
|
-
const found = [];
|
|
601
|
-
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
602
|
-
const interpolation = interpolationInCall(call, relativePath);
|
|
603
|
-
if (interpolation) {
|
|
604
|
-
found.push(interpolation);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
return found;
|
|
608
|
-
};
|
|
609
|
-
const discoverSqlInterpolation = (project, lunoraDirectory) => {
|
|
610
|
-
const interpolations = [];
|
|
611
|
-
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
612
|
-
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
613
|
-
interpolations.push(...interpolationsInSourceFile(sourceFile, lunoraRelativePath(lunoraDirectory, filePath)));
|
|
614
|
-
}
|
|
615
|
-
return interpolations;
|
|
616
|
-
};
|
|
617
|
-
|
|
618
|
-
const isWorkflowGetCall = (call) => {
|
|
619
|
-
const callee = call.getExpression();
|
|
620
|
-
if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "get") {
|
|
621
|
-
return false;
|
|
622
|
-
}
|
|
623
|
-
const receiver = callee.getExpression();
|
|
624
|
-
if (Node.isPropertyAccessExpression(receiver)) {
|
|
625
|
-
return receiver.getName() === "workflows";
|
|
626
|
-
}
|
|
627
|
-
return Node.isIdentifier(receiver) && receiver.getText() === "workflows";
|
|
628
|
-
};
|
|
629
|
-
const workflowOf = (call) => {
|
|
630
|
-
const argument = call.getArguments()[0];
|
|
631
|
-
return argument && Node.isStringLiteral(argument) ? argument.getLiteralText() : "";
|
|
632
|
-
};
|
|
633
|
-
const discoverWorkflowCalls = (project, lunoraDirectory) => {
|
|
634
|
-
const calls = [];
|
|
635
|
-
for (const filePath of listLunoraSourceFiles(lunoraDirectory)) {
|
|
636
|
-
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
637
|
-
const relativePath = lunoraRelativePath(lunoraDirectory, filePath);
|
|
638
|
-
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
639
|
-
if (!isWorkflowGetCall(call)) {
|
|
640
|
-
continue;
|
|
641
|
-
}
|
|
642
|
-
const exportName = enclosingExportName$1(call);
|
|
643
|
-
if (exportName === "") {
|
|
644
|
-
continue;
|
|
645
|
-
}
|
|
646
|
-
calls.push({ exportName, file: relativePath, line: call.getStartLineNumber(), workflow: workflowOf(call) });
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
return calls;
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
const SCHEMA_SNAPSHOT_FILENAME = ".lunora-schema.json";
|
|
653
|
-
const writeIfChanged = (filePath, content) => {
|
|
654
|
-
if (existsSync(filePath)) {
|
|
655
|
-
const existing = readFileSync(filePath, "utf8");
|
|
656
|
-
if (existing === content) {
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
writeFileSync(filePath, content, "utf8");
|
|
661
|
-
};
|
|
662
|
-
const writeIfPresent = (filePath, content) => {
|
|
663
|
-
if (content !== "") {
|
|
664
|
-
writeIfChanged(filePath, content);
|
|
665
|
-
}
|
|
666
|
-
};
|
|
667
|
-
const readProjectVersion = (projectRoot) => {
|
|
668
|
-
const manifestPath = join(projectRoot, "package.json");
|
|
669
|
-
if (!existsSync(manifestPath)) {
|
|
670
|
-
return void 0;
|
|
671
|
-
}
|
|
672
|
-
try {
|
|
673
|
-
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
674
|
-
return typeof manifest["version"] === "string" && manifest["version"] !== "" ? manifest["version"] : void 0;
|
|
675
|
-
} catch {
|
|
676
|
-
return void 0;
|
|
677
|
-
}
|
|
678
|
-
};
|
|
679
|
-
const findTsconfig = (startPath) => {
|
|
680
|
-
let directory = existsSync(startPath) ? startPath : dirname(startPath);
|
|
681
|
-
while (directory && directory !== dirname(directory)) {
|
|
682
|
-
const candidate = join(directory, "tsconfig.json");
|
|
683
|
-
if (existsSync(candidate)) {
|
|
684
|
-
return candidate;
|
|
685
|
-
}
|
|
686
|
-
directory = dirname(directory);
|
|
687
|
-
}
|
|
688
|
-
return void 0;
|
|
689
|
-
};
|
|
690
|
-
const toPosixPath = (path) => path.replaceAll("\\", "/");
|
|
691
|
-
const createCodegenProject = (lunoraDirectory) => {
|
|
692
|
-
const tsconfigPath = findTsconfig(lunoraDirectory);
|
|
693
|
-
return tsconfigPath ? new Project({ skipAddingFilesFromTsConfig: false, tsConfigFilePath: tsconfigPath, useInMemoryFileSystem: false }) : new Project({ skipAddingFilesFromTsConfig: true, useInMemoryFileSystem: false });
|
|
694
|
-
};
|
|
695
|
-
const refreshCodegenProject = (project, lunoraDirectory) => {
|
|
696
|
-
const diskPaths = listLunoraSourceFiles(lunoraDirectory);
|
|
697
|
-
const schemaPath = join(lunoraDirectory, "schema.ts");
|
|
698
|
-
if (existsSync(schemaPath)) {
|
|
699
|
-
diskPaths.push(schemaPath);
|
|
700
|
-
}
|
|
701
|
-
for (const path of diskPaths) {
|
|
702
|
-
const existing = project.getSourceFile(path);
|
|
703
|
-
if (existing === void 0) {
|
|
704
|
-
project.addSourceFileAtPath(path);
|
|
705
|
-
} else {
|
|
706
|
-
existing.refreshFromFileSystemSync();
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
const onDisk = new Set(diskPaths.map((path) => toPosixPath(path)));
|
|
710
|
-
const lunoraRoot = toPosixPath(lunoraDirectory);
|
|
711
|
-
const lunoraPrefix = `${lunoraRoot}/`;
|
|
712
|
-
for (const sourceFile of project.getSourceFiles()) {
|
|
713
|
-
const filePath = sourceFile.getFilePath();
|
|
714
|
-
if ((filePath === lunoraRoot || filePath.startsWith(lunoraPrefix)) && !onDisk.has(filePath)) {
|
|
715
|
-
project.removeSourceFile(sourceFile);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
const runCodegen = (options) => {
|
|
720
|
-
const lunoraDirectory = join(options.projectRoot, options.lunoraDirectory ?? "lunora");
|
|
721
|
-
const schemaPath = join(lunoraDirectory, "schema.ts");
|
|
722
|
-
if (!existsSync(schemaPath)) {
|
|
723
|
-
throw new Error(`schema.ts not found at ${schemaPath}`);
|
|
724
|
-
}
|
|
725
|
-
const project = options.project ?? createCodegenProject(lunoraDirectory);
|
|
726
|
-
const schema = discoverSchema(project, schemaPath);
|
|
727
|
-
const functions = discoverFunctions(project, lunoraDirectory);
|
|
728
|
-
const httpRoutes = discoverHttpRoutes(project, lunoraDirectory);
|
|
729
|
-
const migrations = discoverMigrations(project, lunoraDirectory);
|
|
730
|
-
const workflows = discoverWorkflows(project, lunoraDirectory);
|
|
731
|
-
const crons = discoverCrons(project, lunoraDirectory, workflows);
|
|
732
|
-
const containers = discoverContainers(project, lunoraDirectory);
|
|
733
|
-
const advisories = options.lint === false ? [] : lintSchema(
|
|
734
|
-
schema,
|
|
735
|
-
discoverQueries(project, lunoraDirectory),
|
|
736
|
-
discoverInserts(project, lunoraDirectory),
|
|
737
|
-
discoverAuthApiCalls(project, lunoraDirectory),
|
|
738
|
-
discoverRlsProcedures(project, lunoraDirectory),
|
|
739
|
-
containers,
|
|
740
|
-
workflows,
|
|
741
|
-
discoverWorkflowCalls(project, lunoraDirectory),
|
|
742
|
-
discoverMaskProcedures(project, lunoraDirectory),
|
|
743
|
-
discoverNondeterministicCalls(project, lunoraDirectory),
|
|
744
|
-
discoverProcedureMiddleware(project, lunoraDirectory),
|
|
745
|
-
discoverArgumentValidators(project, lunoraDirectory),
|
|
746
|
-
discoverSecrets(project, lunoraDirectory),
|
|
747
|
-
discoverSqlInterpolation(project, lunoraDirectory),
|
|
748
|
-
discoverAdminRoutes(project, lunoraDirectory),
|
|
749
|
-
discoverR2sqlCalls(project, lunoraDirectory)
|
|
750
|
-
);
|
|
751
|
-
const rlsMetadata = discoverRlsMetadata(project, lunoraDirectory);
|
|
752
|
-
const maskMetadata = discoverMaskMetadata(project, lunoraDirectory);
|
|
753
|
-
const storageRulesMetadata = discoverStorageRulesMetadata(project, lunoraDirectory);
|
|
754
|
-
const featureUsage = discoverFeatureUsage(project, lunoraDirectory);
|
|
755
|
-
const hasAi = featureUsage.ai;
|
|
756
|
-
const hasPayments = featureUsage.payments;
|
|
757
|
-
const hasKv = featureUsage.kv;
|
|
758
|
-
const hasHyperdrive = featureUsage.hyperdrive;
|
|
759
|
-
const hasBrowser = featureUsage.browser;
|
|
760
|
-
const hasImages = featureUsage.images;
|
|
761
|
-
const hasAnalytics = featureUsage.analytics;
|
|
762
|
-
const hasPipelines = featureUsage.pipelines;
|
|
763
|
-
const hasR2sql = featureUsage.r2sql;
|
|
764
|
-
const dependencies = discoverPackageDependencies(options.projectRoot);
|
|
765
|
-
const studioFeatures = buildStudioFeatures(featureUsage, {
|
|
766
|
-
cronCount: crons.length,
|
|
767
|
-
dependencies,
|
|
768
|
-
storageColumnCount: Object.keys(buildStorageColumns(schema)).length,
|
|
769
|
-
storageRuleCount: storageRulesMetadata.rules.length,
|
|
770
|
-
vectorIndexCount: schema.vectorIndexes.length,
|
|
771
|
-
workflowCount: workflows.length
|
|
772
|
-
});
|
|
773
|
-
const useUmbrella = dependencies.has("lunorash");
|
|
774
|
-
const dataModelContent = emitDataModel(schema, useUmbrella);
|
|
775
|
-
const apiContent = emitApi(functions, workflows, useUmbrella);
|
|
776
|
-
const serverContent = emitServer({
|
|
777
|
-
containers,
|
|
778
|
-
hasAi,
|
|
779
|
-
hasAnalytics,
|
|
780
|
-
hasBrowser,
|
|
781
|
-
hasHyperdrive,
|
|
782
|
-
hasImages,
|
|
783
|
-
hasKv,
|
|
784
|
-
hasPayments,
|
|
785
|
-
hasPipelines,
|
|
786
|
-
hasR2sql,
|
|
787
|
-
schema,
|
|
788
|
-
storageRuleBuckets: storageRulesMetadata.rules.map((rule) => rule.bucket),
|
|
789
|
-
useUmbrella,
|
|
790
|
-
workflows
|
|
791
|
-
});
|
|
792
|
-
const functionsContent = emitFunctions(functions, migrations);
|
|
793
|
-
const shardContent = emitShard({
|
|
794
|
-
advisories,
|
|
795
|
-
containers,
|
|
796
|
-
hasAi,
|
|
797
|
-
hasAnalytics,
|
|
798
|
-
hasBrowser,
|
|
799
|
-
hasHyperdrive,
|
|
800
|
-
hasImages,
|
|
801
|
-
hasKv,
|
|
802
|
-
hasPayments,
|
|
803
|
-
hasR2sql,
|
|
804
|
-
maskMetadata,
|
|
805
|
-
rlsMetadata,
|
|
806
|
-
schema,
|
|
807
|
-
storageRules: storageRulesMetadata,
|
|
808
|
-
studioFeatures,
|
|
809
|
-
useUmbrella,
|
|
810
|
-
workflows
|
|
811
|
-
});
|
|
812
|
-
const containersContent = emitContainers(containers);
|
|
813
|
-
const workflowsContent = emitWorkflows(workflows);
|
|
814
|
-
const cronsContent = emitCrons(crons);
|
|
815
|
-
const vectorsContent = emitVectors(schema.vectorIndexes);
|
|
816
|
-
const drizzleFiles = emitDrizzleSchema(schema, useUmbrella);
|
|
817
|
-
const seedContent = emitSeed(dependencies.has("@lunora/seed"));
|
|
818
|
-
const apiSpec = options.apiSpec ?? "openapi";
|
|
819
|
-
const wantsOpenApi = apiSpec === "openapi" || apiSpec === "both";
|
|
820
|
-
const wantsOpenRpc = apiSpec === "openrpc" || apiSpec === "both";
|
|
821
|
-
const appContent = emitApp({
|
|
822
|
-
hasAi,
|
|
823
|
-
hasAnalytics,
|
|
824
|
-
hasAuth: dependencies.has("@lunora/auth"),
|
|
825
|
-
hasBrowser,
|
|
826
|
-
// Worker-composition framework adapters expose a `withLunora` over
|
|
827
|
-
// `withFrameworkWorker`; when one is installed, surface `.buildFrameworkWorker()`.
|
|
828
|
-
hasFramework: dependencies.has("@lunora/astro") || dependencies.has("@lunora/svelte") || dependencies.has("@lunora/vue"),
|
|
829
|
-
// `hasGlobal` means **D1-backed** global tables (the `.global()` / D1
|
|
830
|
-
// app-builder wiring); Hyperdrive-backed globals are gated separately by
|
|
831
|
-
// `hasHyperdriveGlobal` so an app picks the right binding+package.
|
|
832
|
-
hasGlobal: schema.tables.some((table) => table.shardMode === "global" && table.globalBackend !== "hyperdrive"),
|
|
833
|
-
hasHyperdrive,
|
|
834
|
-
hasHyperdriveGlobal: schema.tables.some((table) => table.shardMode === "global" && table.globalBackend === "hyperdrive"),
|
|
835
|
-
hasImages,
|
|
836
|
-
hasKv,
|
|
837
|
-
hasPayments,
|
|
838
|
-
hasR2sql,
|
|
839
|
-
hasScheduler: studioFeatures.scheduler,
|
|
840
|
-
hasStorage: studioFeatures.storage,
|
|
841
|
-
hasVectors: schema.vectorIndexes.length > 0,
|
|
842
|
-
hasWorkflow: workflows.length > 0,
|
|
843
|
-
useUmbrella,
|
|
844
|
-
wantsOpenApi,
|
|
845
|
-
wantsOpenRpc
|
|
846
|
-
});
|
|
847
|
-
const projectVersion = readProjectVersion(options.projectRoot);
|
|
848
|
-
const openApiDocument = buildOpenApiDocument({ functions, httpRoutes, version: projectVersion });
|
|
849
|
-
const openRpcDocument = buildOpenRpcDocument({ functions, version: projectVersion });
|
|
850
|
-
const openApiContent = `${JSON.stringify(openApiDocument, void 0, 2)}
|
|
851
|
-
`;
|
|
852
|
-
const openRpcContent = `${JSON.stringify(openRpcDocument, void 0, 2)}
|
|
853
|
-
`;
|
|
854
|
-
const openApiModuleContent = emitOpenApiModule(openApiDocument);
|
|
855
|
-
const openRpcModuleContent = emitOpenRpcModule(openRpcDocument);
|
|
856
|
-
const schemaSnapshot = buildSchemaSnapshot(
|
|
857
|
-
schema,
|
|
858
|
-
migrations.map((migration) => migration.id)
|
|
859
|
-
);
|
|
860
|
-
const schemaSnapshotPath = join(lunoraDirectory, SCHEMA_SNAPSHOT_FILENAME);
|
|
861
|
-
const schemaSnapshotExists = existsSync(schemaSnapshotPath);
|
|
862
|
-
const outputDirectory = join(lunoraDirectory, "_generated");
|
|
863
|
-
if (!options.dryRun) {
|
|
864
|
-
if (!existsSync(outputDirectory)) {
|
|
865
|
-
mkdirSync(outputDirectory, { recursive: true });
|
|
866
|
-
}
|
|
867
|
-
writeIfChanged(join(outputDirectory, "app.ts"), appContent);
|
|
868
|
-
writeIfChanged(join(outputDirectory, "dataModel.ts"), dataModelContent);
|
|
869
|
-
writeIfChanged(join(outputDirectory, "api.ts"), apiContent);
|
|
870
|
-
writeIfChanged(join(outputDirectory, "server.ts"), serverContent);
|
|
871
|
-
writeIfChanged(join(outputDirectory, "functions.ts"), functionsContent);
|
|
872
|
-
writeIfChanged(join(outputDirectory, "shard.ts"), shardContent);
|
|
873
|
-
writeIfChanged(join(outputDirectory, "crons.ts"), cronsContent);
|
|
874
|
-
writeIfChanged(join(outputDirectory, "vectors.ts"), vectorsContent);
|
|
875
|
-
writeIfChanged(join(outputDirectory, "drizzle.global.ts"), drizzleFiles.global);
|
|
876
|
-
writeIfChanged(join(outputDirectory, "drizzle.shard.ts"), drizzleFiles.shard);
|
|
877
|
-
writeIfPresent(join(outputDirectory, "containers.ts"), containersContent);
|
|
878
|
-
writeIfPresent(join(outputDirectory, "workflows.ts"), workflowsContent);
|
|
879
|
-
writeIfPresent(join(outputDirectory, "seed.ts"), seedContent);
|
|
880
|
-
if (wantsOpenApi) {
|
|
881
|
-
writeIfChanged(join(outputDirectory, "openapi.json"), openApiContent);
|
|
882
|
-
writeIfChanged(join(outputDirectory, "openapi.ts"), openApiModuleContent);
|
|
883
|
-
}
|
|
884
|
-
if (wantsOpenRpc) {
|
|
885
|
-
writeIfChanged(join(outputDirectory, "openrpc.json"), openRpcContent);
|
|
886
|
-
writeIfChanged(join(outputDirectory, "openrpc.ts"), openRpcModuleContent);
|
|
887
|
-
}
|
|
888
|
-
if (!schemaSnapshotExists || options.updateSchemaBaseline === true) {
|
|
889
|
-
writeIfChanged(schemaSnapshotPath, serializeSchemaSnapshot(schemaSnapshot));
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
return {
|
|
893
|
-
advisories,
|
|
894
|
-
containers,
|
|
895
|
-
cronTriggers: emitWranglerCronTriggers(crons),
|
|
896
|
-
generated: {
|
|
897
|
-
api: apiContent,
|
|
898
|
-
app: appContent,
|
|
899
|
-
containers: containersContent,
|
|
900
|
-
crons: cronsContent,
|
|
901
|
-
dataModel: dataModelContent,
|
|
902
|
-
drizzleGlobal: drizzleFiles.global,
|
|
903
|
-
drizzleShard: drizzleFiles.shard,
|
|
904
|
-
functions: functionsContent,
|
|
905
|
-
openApi: openApiContent,
|
|
906
|
-
openApiModule: openApiModuleContent,
|
|
907
|
-
openRpc: openRpcContent,
|
|
908
|
-
openRpcModule: openRpcModuleContent,
|
|
909
|
-
seed: seedContent,
|
|
910
|
-
server: serverContent,
|
|
911
|
-
shard: shardContent,
|
|
912
|
-
vectors: vectorsContent,
|
|
913
|
-
workflows: workflowsContent
|
|
914
|
-
},
|
|
915
|
-
outputDirectory,
|
|
916
|
-
schemaSnapshot,
|
|
917
|
-
schemaSnapshotPath,
|
|
918
|
-
workflows
|
|
919
|
-
};
|
|
920
|
-
};
|
|
921
|
-
|
|
922
|
-
export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen };
|