@open-mercato/cli 0.6.6-develop.6381.1.042df302c3 → 0.6.6-develop.6382.1.4b9b9091ab

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 (31) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/agentic/guides/core.md +3 -38
  3. package/dist/agentic/guides/module-system.md +130 -0
  4. package/dist/agentic/shared/AGENTS.md.template +4 -12
  5. package/dist/lib/generators/index.js +2 -0
  6. package/dist/lib/generators/index.js.map +2 -2
  7. package/dist/lib/generators/module-facts-generate.js +52 -0
  8. package/dist/lib/generators/module-facts-generate.js.map +7 -0
  9. package/dist/lib/generators/module-facts.js +734 -0
  10. package/dist/lib/generators/module-facts.js.map +7 -0
  11. package/dist/mercato.js +3 -1
  12. package/dist/mercato.js.map +2 -2
  13. package/package.json +5 -5
  14. package/src/__tests__/mercato.test.ts +5 -0
  15. package/src/lib/generators/__tests__/module-facts.auth-source.test.ts +83 -0
  16. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +56 -0
  17. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +69 -0
  18. package/src/lib/generators/__tests__/module-facts.malformed.test.ts +76 -0
  19. package/src/lib/generators/index.ts +1 -0
  20. package/src/lib/generators/module-facts-generate.ts +68 -0
  21. package/src/lib/generators/module-facts.ts +958 -0
  22. package/src/mercato.ts +2 -0
  23. package/dist/agentic/guides/core.auth.md +0 -101
  24. package/dist/agentic/guides/core.catalog.md +0 -79
  25. package/dist/agentic/guides/core.currencies.md +0 -43
  26. package/dist/agentic/guides/core.customer_accounts.md +0 -129
  27. package/dist/agentic/guides/core.customers.md +0 -138
  28. package/dist/agentic/guides/core.data_sync.md +0 -107
  29. package/dist/agentic/guides/core.integrations.md +0 -113
  30. package/dist/agentic/guides/core.sales.md +0 -104
  31. package/dist/agentic/guides/core.workflows.md +0 -152
@@ -0,0 +1,734 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import ts from "typescript";
4
+ import { toSnake } from "../utils.js";
5
+ const MODULE_FACTS_ALLOWLIST = [
6
+ "auth",
7
+ "catalog",
8
+ "currencies",
9
+ "customer_accounts",
10
+ "customers",
11
+ "data_sync",
12
+ "integrations",
13
+ "sales",
14
+ "workflows"
15
+ ];
16
+ function readSourceFile(filePath) {
17
+ if (!fs.existsSync(filePath)) return null;
18
+ const source = fs.readFileSync(filePath, "utf8");
19
+ const scriptKind = filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
20
+ return ts.createSourceFile(filePath, source, ts.ScriptTarget.ES2020, true, scriptKind);
21
+ }
22
+ function resolveConventionFile(baseDir, basename) {
23
+ for (const extension of [".ts", ".tsx"]) {
24
+ const candidate = path.join(baseDir, `${basename}${extension}`);
25
+ if (fs.existsSync(candidate)) return candidate;
26
+ }
27
+ return null;
28
+ }
29
+ function readStringPropertyInitializer(objectLiteral, propertyName) {
30
+ for (const property of objectLiteral.properties) {
31
+ if (!ts.isPropertyAssignment(property)) continue;
32
+ const name = ts.isIdentifier(property.name) ? property.name.text : ts.isStringLiteralLike(property.name) ? property.name.text : void 0;
33
+ if (name !== propertyName) continue;
34
+ if (ts.isStringLiteralLike(property.initializer)) return property.initializer.text;
35
+ return void 0;
36
+ }
37
+ return void 0;
38
+ }
39
+ function getClassDecoratorCall(node, decoratorName) {
40
+ const decorators = ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : [];
41
+ for (const decorator of decorators) {
42
+ const expression = decorator.expression;
43
+ if (!ts.isCallExpression(expression)) continue;
44
+ if (ts.isIdentifier(expression.expression) && expression.expression.text === decoratorName) {
45
+ return expression;
46
+ }
47
+ }
48
+ return void 0;
49
+ }
50
+ function readDecoratorTableName(decoratorCall) {
51
+ const firstArgument = decoratorCall.arguments[0];
52
+ if (!firstArgument || !ts.isObjectLiteralExpression(firstArgument)) return void 0;
53
+ return readStringPropertyInitializer(firstArgument, "tableName");
54
+ }
55
+ function getPropertyDecoratorName(member) {
56
+ const decorators = ts.canHaveDecorators(member) ? ts.getDecorators(member) ?? [] : [];
57
+ for (const decorator of decorators) {
58
+ const expression = decorator.expression;
59
+ if (!ts.isCallExpression(expression)) continue;
60
+ const firstArgument = expression.arguments[0];
61
+ if (!firstArgument || !ts.isObjectLiteralExpression(firstArgument)) continue;
62
+ const columnName = readStringPropertyInitializer(firstArgument, "name");
63
+ if (columnName) return columnName;
64
+ }
65
+ return void 0;
66
+ }
67
+ function classHasUpdatedAtColumn(node) {
68
+ for (const member of node.members) {
69
+ if (!ts.isPropertyDeclaration(member) || !member.name) continue;
70
+ const propertyName = ts.isIdentifier(member.name) ? member.name.text : ts.isStringLiteralLike(member.name) ? member.name.text : void 0;
71
+ if (propertyName === "updatedAt") return true;
72
+ if (getPropertyDecoratorName(member) === "updated_at") return true;
73
+ }
74
+ return false;
75
+ }
76
+ function collectCustomFieldEntityIds(ceFilePath) {
77
+ const result = /* @__PURE__ */ new Set();
78
+ if (!ceFilePath) return result;
79
+ const sourceFile = readSourceFile(ceFilePath);
80
+ if (!sourceFile) return result;
81
+ const visit = (node) => {
82
+ if (ts.isObjectLiteralExpression(node)) {
83
+ const id = readStringPropertyInitializer(node, "id");
84
+ if (id && id.includes(":")) result.add(id);
85
+ }
86
+ node.forEachChild(visit);
87
+ };
88
+ sourceFile.forEachChild(visit);
89
+ return result;
90
+ }
91
+ function extractEntities(moduleId, entitiesFilePath, customFieldEntityIds) {
92
+ const sourceFile = entitiesFilePath ? readSourceFile(entitiesFilePath) : null;
93
+ if (!sourceFile) return [];
94
+ const facts = [];
95
+ sourceFile.forEachChild((node) => {
96
+ if (!ts.isClassDeclaration(node) || !node.name) return;
97
+ const isExported = node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
98
+ if (!isExported) return;
99
+ const entityDecorator = getClassDecoratorCall(node, "Entity");
100
+ if (!entityDecorator) return;
101
+ const className = node.name.text;
102
+ const table = readDecoratorTableName(entityDecorator);
103
+ if (!table) return;
104
+ const entityId = `${moduleId}:${toSnake(className)}`;
105
+ facts.push({
106
+ id: entityId,
107
+ class: className,
108
+ table,
109
+ editable: classHasUpdatedAtColumn(node),
110
+ customFields: customFieldEntityIds.has(entityId)
111
+ });
112
+ });
113
+ return facts;
114
+ }
115
+ function unwrapExpression(expression) {
116
+ let current = expression;
117
+ while (ts.isAsExpression(current) || ts.isParenthesizedExpression(current) || ts.isTypeAssertionExpression(current)) {
118
+ current = current.expression;
119
+ }
120
+ return current;
121
+ }
122
+ function unwrapArrayLiteral(expression) {
123
+ const current = unwrapExpression(expression);
124
+ return ts.isArrayLiteralExpression(current) ? current : null;
125
+ }
126
+ function getPropertyName(property) {
127
+ if (!ts.isPropertyAssignment(property) && !ts.isShorthandPropertyAssignment(property)) return void 0;
128
+ const name = property.name;
129
+ if (!name) return void 0;
130
+ if (ts.isIdentifier(name)) return name.text;
131
+ if (ts.isStringLiteralLike(name)) return name.text;
132
+ return void 0;
133
+ }
134
+ function getObjectPropertyInitializer(objectLiteral, propertyName) {
135
+ for (const property of objectLiteral.properties) {
136
+ if (!ts.isPropertyAssignment(property)) continue;
137
+ if (getPropertyName(property) === propertyName) return property.initializer;
138
+ }
139
+ return void 0;
140
+ }
141
+ function findObjectLiteralDeclaration(sourceFile, variableName) {
142
+ let result = null;
143
+ const visit = (node) => {
144
+ if (result) return;
145
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === variableName && node.initializer) {
146
+ const unwrapped = unwrapExpression(node.initializer);
147
+ if (ts.isObjectLiteralExpression(unwrapped)) {
148
+ result = unwrapped;
149
+ return;
150
+ }
151
+ }
152
+ node.forEachChild(visit);
153
+ };
154
+ sourceFile.forEachChild(visit);
155
+ return result;
156
+ }
157
+ function buildVariableInitializerMap(sourceFile) {
158
+ const initializers = /* @__PURE__ */ new Map();
159
+ const visit = (node) => {
160
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
161
+ if (!initializers.has(node.name.text)) initializers.set(node.name.text, node.initializer);
162
+ }
163
+ node.forEachChild(visit);
164
+ };
165
+ sourceFile.forEachChild(visit);
166
+ return initializers;
167
+ }
168
+ function resolveToObjectLiteral(expression, initializers) {
169
+ const unwrapped = unwrapExpression(expression);
170
+ if (ts.isObjectLiteralExpression(unwrapped)) return unwrapped;
171
+ if (ts.isIdentifier(unwrapped)) {
172
+ const referenced = initializers.get(unwrapped.text);
173
+ if (referenced) return resolveToObjectLiteral(referenced, initializers);
174
+ }
175
+ return null;
176
+ }
177
+ function resolveToArrayLiteral(expression, initializers) {
178
+ const unwrapped = unwrapExpression(expression);
179
+ if (ts.isArrayLiteralExpression(unwrapped)) return unwrapped;
180
+ if (ts.isIdentifier(unwrapped)) {
181
+ const referenced = initializers.get(unwrapped.text);
182
+ if (referenced) return resolveToArrayLiteral(referenced, initializers);
183
+ }
184
+ return null;
185
+ }
186
+ function findDefaultExportExpression(sourceFile) {
187
+ for (const statement of sourceFile.statements) {
188
+ if (ts.isExportAssignment(statement) && !statement.isExportEquals) return statement.expression;
189
+ }
190
+ return null;
191
+ }
192
+ function findArrayLiteralDeclaration(sourceFile, variableName) {
193
+ let result = null;
194
+ const visit = (node) => {
195
+ if (result) return;
196
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === variableName && node.initializer) {
197
+ const arrayLiteral = unwrapArrayLiteral(node.initializer);
198
+ if (arrayLiteral) {
199
+ result = arrayLiteral;
200
+ return;
201
+ }
202
+ }
203
+ node.forEachChild(visit);
204
+ };
205
+ sourceFile.forEachChild(visit);
206
+ return result;
207
+ }
208
+ function extractEvents(eventsFilePath) {
209
+ const sourceFile = eventsFilePath ? readSourceFile(eventsFilePath) : null;
210
+ if (!sourceFile) return [];
211
+ const eventsArray = findArrayLiteralDeclaration(sourceFile, "events");
212
+ if (!eventsArray) return [];
213
+ const facts = [];
214
+ for (const element of eventsArray.elements) {
215
+ if (!ts.isObjectLiteralExpression(element)) continue;
216
+ const id = readStringPropertyInitializer(element, "id");
217
+ if (!id) continue;
218
+ const label = readStringPropertyInitializer(element, "label");
219
+ const category = readStringPropertyInitializer(element, "category");
220
+ const entity = readStringPropertyInitializer(element, "entity");
221
+ const fact = {
222
+ id,
223
+ category: category ?? null,
224
+ entity: entity ?? null
225
+ };
226
+ if (label !== void 0) fact.label = label;
227
+ facts.push(fact);
228
+ }
229
+ return facts;
230
+ }
231
+ function extractAclFeatures(aclFilePath) {
232
+ const sourceFile = aclFilePath ? readSourceFile(aclFilePath) : null;
233
+ if (!sourceFile) return [];
234
+ const featuresArray = findArrayLiteralDeclaration(sourceFile, "features");
235
+ if (!featuresArray) return [];
236
+ const featureIds = [];
237
+ const seen = /* @__PURE__ */ new Set();
238
+ for (const element of featuresArray.elements) {
239
+ let featureId;
240
+ if (ts.isObjectLiteralExpression(element)) {
241
+ featureId = readStringPropertyInitializer(element, "id");
242
+ } else if (ts.isStringLiteralLike(element)) {
243
+ featureId = element.text;
244
+ }
245
+ if (!featureId || seen.has(featureId)) continue;
246
+ seen.add(featureId);
247
+ featureIds.push(featureId);
248
+ }
249
+ return featureIds;
250
+ }
251
+ function readBooleanPropertyInitializer(objectLiteral, propertyName) {
252
+ const initializer = getObjectPropertyInitializer(objectLiteral, propertyName);
253
+ if (!initializer) return void 0;
254
+ if (initializer.kind === ts.SyntaxKind.TrueKeyword) return true;
255
+ if (initializer.kind === ts.SyntaxKind.FalseKeyword) return false;
256
+ return void 0;
257
+ }
258
+ function readStringArrayPropertyInitializer(objectLiteral, propertyName) {
259
+ const initializer = getObjectPropertyInitializer(objectLiteral, propertyName);
260
+ if (!initializer) return void 0;
261
+ const arrayLiteral = unwrapArrayLiteral(initializer);
262
+ if (!arrayLiteral) return void 0;
263
+ const values = [];
264
+ for (const element of arrayLiteral.elements) {
265
+ if (ts.isStringLiteralLike(element)) values.push(element.text);
266
+ }
267
+ return values;
268
+ }
269
+ function parseApiRouteAuthRule(metadataLiteral) {
270
+ const rule = {};
271
+ const requireAuth = readBooleanPropertyInitializer(metadataLiteral, "requireAuth");
272
+ if (requireAuth !== void 0) rule.requireAuth = requireAuth;
273
+ const requireFeatures = readStringArrayPropertyInitializer(metadataLiteral, "requireFeatures");
274
+ if (requireFeatures && requireFeatures.length > 0) rule.requireFeatures = requireFeatures;
275
+ const requireRoles = readStringArrayPropertyInitializer(metadataLiteral, "requireRoles");
276
+ if (requireRoles && requireRoles.length > 0) rule.requireRoles = requireRoles;
277
+ return rule;
278
+ }
279
+ function parseApiRouteEntry(entryLiteral) {
280
+ const routePath = readStringPropertyInitializer(entryLiteral, "path");
281
+ if (!routePath) return null;
282
+ const methods = [];
283
+ const seenMethods = /* @__PURE__ */ new Set();
284
+ const handlersInitializer = getObjectPropertyInitializer(entryLiteral, "handlers");
285
+ if (handlersInitializer && ts.isObjectLiteralExpression(handlersInitializer)) {
286
+ for (const property of handlersInitializer.properties) {
287
+ const methodName = getPropertyName(property);
288
+ if (methodName && !seenMethods.has(methodName)) {
289
+ seenMethods.add(methodName);
290
+ methods.push(methodName);
291
+ }
292
+ }
293
+ } else {
294
+ const singleMethod = readStringPropertyInitializer(entryLiteral, "method");
295
+ if (singleMethod && !seenMethods.has(singleMethod)) {
296
+ seenMethods.add(singleMethod);
297
+ methods.push(singleMethod);
298
+ }
299
+ }
300
+ const auth = {};
301
+ const metadataInitializer = getObjectPropertyInitializer(entryLiteral, "metadata");
302
+ if (metadataInitializer && ts.isObjectLiteralExpression(metadataInitializer)) {
303
+ for (const property of metadataInitializer.properties) {
304
+ if (!ts.isPropertyAssignment(property)) continue;
305
+ const methodName = getPropertyName(property);
306
+ if (!methodName) continue;
307
+ const methodMetadata = unwrapExpression(property.initializer);
308
+ if (!ts.isObjectLiteralExpression(methodMetadata)) continue;
309
+ auth[methodName] = parseApiRouteAuthRule(methodMetadata);
310
+ if (!seenMethods.has(methodName)) {
311
+ seenMethods.add(methodName);
312
+ methods.push(methodName);
313
+ }
314
+ }
315
+ }
316
+ return { path: routePath, methods, auth };
317
+ }
318
+ function extractApiRoutes(moduleId, registrySource, registryDescription, warnings) {
319
+ if (registrySource == null) {
320
+ warnings.push(`[module-facts] module registry unavailable (${registryDescription}); API route auth omitted for ${moduleId}`);
321
+ return [];
322
+ }
323
+ const sourceFile = ts.createSourceFile(
324
+ "module-registry.generated.ts",
325
+ registrySource,
326
+ ts.ScriptTarget.ES2020,
327
+ true,
328
+ ts.ScriptKind.TS
329
+ );
330
+ const routes = [];
331
+ const seenPaths = /* @__PURE__ */ new Set();
332
+ const visit = (node) => {
333
+ if (ts.isObjectLiteralExpression(node) && readStringPropertyInitializer(node, "id") === moduleId) {
334
+ const apisInitializer = getObjectPropertyInitializer(node, "apis");
335
+ const apisArray = apisInitializer ? unwrapArrayLiteral(apisInitializer) : null;
336
+ if (apisArray) {
337
+ for (const element of apisArray.elements) {
338
+ if (!ts.isObjectLiteralExpression(element)) continue;
339
+ const route = parseApiRouteEntry(element);
340
+ if (route && !seenPaths.has(route.path)) {
341
+ seenPaths.add(route.path);
342
+ routes.push(route);
343
+ }
344
+ }
345
+ }
346
+ }
347
+ node.forEachChild(visit);
348
+ };
349
+ sourceFile.forEachChild(visit);
350
+ return routes;
351
+ }
352
+ function resolveRegistrySource(options) {
353
+ if (typeof options.registrySource === "string") {
354
+ return { source: options.registrySource, description: "registrySource" };
355
+ }
356
+ if (options.registryPath) {
357
+ if (!fs.existsSync(options.registryPath)) {
358
+ return { source: null, description: options.registryPath };
359
+ }
360
+ return { source: fs.readFileSync(options.registryPath, "utf8"), description: options.registryPath };
361
+ }
362
+ return { source: null, description: "registryPath not provided" };
363
+ }
364
+ function detectAwilixRegistrationKind(expression) {
365
+ let current = unwrapExpression(expression);
366
+ while (ts.isCallExpression(current)) {
367
+ const callee = current.expression;
368
+ if (ts.isIdentifier(callee)) return callee.text;
369
+ if (ts.isPropertyAccessExpression(callee)) {
370
+ current = callee.expression;
371
+ continue;
372
+ }
373
+ break;
374
+ }
375
+ return null;
376
+ }
377
+ function extractDiTokens(diFilePath) {
378
+ if (!diFilePath) return [];
379
+ const sourceFile = readSourceFile(diFilePath);
380
+ if (!sourceFile) return [];
381
+ const tokens = [];
382
+ const seen = /* @__PURE__ */ new Set();
383
+ const visit = (node) => {
384
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "register") {
385
+ const argument = node.arguments[0];
386
+ if (argument && ts.isObjectLiteralExpression(argument)) {
387
+ for (const property of argument.properties) {
388
+ if (!ts.isPropertyAssignment(property)) continue;
389
+ const kind = detectAwilixRegistrationKind(property.initializer);
390
+ if (kind !== "asFunction" && kind !== "asClass") continue;
391
+ const tokenName = getPropertyName(property);
392
+ if (tokenName && !seen.has(tokenName)) {
393
+ seen.add(tokenName);
394
+ tokens.push(tokenName);
395
+ }
396
+ }
397
+ }
398
+ }
399
+ node.forEachChild(visit);
400
+ };
401
+ sourceFile.forEachChild(visit);
402
+ return tokens;
403
+ }
404
+ function extractSearchEntities(searchFilePath, warnings) {
405
+ if (!searchFilePath) return [];
406
+ const sourceFile = readSourceFile(searchFilePath);
407
+ if (!sourceFile) return [];
408
+ const searchConfig = findObjectLiteralDeclaration(sourceFile, "searchConfig");
409
+ if (!searchConfig) {
410
+ warnings.push(`[module-facts] search.ts present but no searchConfig object literal: ${searchFilePath}`);
411
+ return [];
412
+ }
413
+ const entitiesInitializer = getObjectPropertyInitializer(searchConfig, "entities");
414
+ const entitiesArray = entitiesInitializer ? unwrapArrayLiteral(entitiesInitializer) : null;
415
+ if (!entitiesArray) {
416
+ warnings.push(`[module-facts] searchConfig.entities is not an array literal: ${searchFilePath}`);
417
+ return [];
418
+ }
419
+ const entityIds = [];
420
+ const seen = /* @__PURE__ */ new Set();
421
+ for (const element of entitiesArray.elements) {
422
+ if (!ts.isObjectLiteralExpression(element)) continue;
423
+ const entityId = readStringPropertyInitializer(element, "entityId");
424
+ if (entityId && !seen.has(entityId)) {
425
+ seen.add(entityId);
426
+ entityIds.push(entityId);
427
+ }
428
+ }
429
+ return entityIds;
430
+ }
431
+ function extractNotifications(notificationsFilePath, warnings) {
432
+ if (!notificationsFilePath) return [];
433
+ const sourceFile = readSourceFile(notificationsFilePath);
434
+ if (!sourceFile) return [];
435
+ const notificationsArray = findArrayLiteralDeclaration(sourceFile, "notificationTypes") ?? findArrayLiteralDeclaration(sourceFile, "notifications");
436
+ if (!notificationsArray) {
437
+ warnings.push(`[module-facts] notifications.ts present but no notificationTypes array literal: ${notificationsFilePath}`);
438
+ return [];
439
+ }
440
+ const notificationIds = [];
441
+ const seen = /* @__PURE__ */ new Set();
442
+ for (const element of notificationsArray.elements) {
443
+ if (!ts.isObjectLiteralExpression(element)) continue;
444
+ const notificationId = readStringPropertyInitializer(element, "type");
445
+ if (notificationId && !seen.has(notificationId)) {
446
+ seen.add(notificationId);
447
+ notificationIds.push(notificationId);
448
+ }
449
+ }
450
+ return notificationIds;
451
+ }
452
+ function extractCli(cliFilePath, warnings) {
453
+ if (!cliFilePath) return [];
454
+ const sourceFile = readSourceFile(cliFilePath);
455
+ if (!sourceFile) return [];
456
+ const defaultExport = findDefaultExportExpression(sourceFile);
457
+ if (!defaultExport) {
458
+ warnings.push(`[module-facts] cli.ts present but no default export: ${cliFilePath}`);
459
+ return [];
460
+ }
461
+ const initializers = buildVariableInitializerMap(sourceFile);
462
+ const collectCommand = (objectLiteral) => readStringPropertyInitializer(objectLiteral, "command");
463
+ const commands = [];
464
+ const seen = /* @__PURE__ */ new Set();
465
+ const pushCommand = (command) => {
466
+ if (command && !seen.has(command)) {
467
+ seen.add(command);
468
+ commands.push(command);
469
+ }
470
+ };
471
+ const arrayLiteral = resolveToArrayLiteral(defaultExport, initializers);
472
+ if (arrayLiteral) {
473
+ for (const element of arrayLiteral.elements) {
474
+ const objectLiteral = resolveToObjectLiteral(element, initializers);
475
+ if (objectLiteral) pushCommand(collectCommand(objectLiteral));
476
+ }
477
+ return commands;
478
+ }
479
+ const singleObject = resolveToObjectLiteral(defaultExport, initializers);
480
+ if (singleObject) {
481
+ pushCommand(collectCommand(singleObject));
482
+ return commands;
483
+ }
484
+ warnings.push(`[module-facts] cli.ts default export is neither an array nor an object literal: ${cliFilePath}`);
485
+ return commands;
486
+ }
487
+ function listSourceFilesRecursive(directory) {
488
+ const files = [];
489
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
490
+ if (entry.name === "__tests__" || entry.name === "node_modules") continue;
491
+ const fullPath = path.join(directory, entry.name);
492
+ if (entry.isDirectory()) {
493
+ files.push(...listSourceFilesRecursive(fullPath));
494
+ } else if (/\.tsx?$/.test(entry.name) && !entry.name.endsWith(".d.ts")) {
495
+ files.push(fullPath);
496
+ }
497
+ }
498
+ return files;
499
+ }
500
+ function extractTableIds(backendDir) {
501
+ if (!fs.existsSync(backendDir)) return [];
502
+ const tableIds = [];
503
+ const seen = /* @__PURE__ */ new Set();
504
+ for (const filePath of listSourceFilesRecursive(backendDir)) {
505
+ const sourceFile = readSourceFile(filePath);
506
+ if (!sourceFile) continue;
507
+ const visit = (node) => {
508
+ if (ts.isPropertyAssignment(node)) {
509
+ const propertyName = getPropertyName(node);
510
+ if ((propertyName === "tableId" || propertyName === "extensionTableId") && ts.isStringLiteralLike(node.initializer)) {
511
+ const value = node.initializer.text;
512
+ if (value && !seen.has(value)) {
513
+ seen.add(value);
514
+ tableIds.push(value);
515
+ }
516
+ }
517
+ }
518
+ node.forEachChild(visit);
519
+ };
520
+ sourceFile.forEachChild(visit);
521
+ }
522
+ tableIds.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
523
+ return tableIds;
524
+ }
525
+ function extractHostEntityIds(entities) {
526
+ return entities.filter((entity) => entity.id.endsWith("_entity")).map((entity) => entity.id);
527
+ }
528
+ function extractModuleMeta(indexFilePath) {
529
+ if (!indexFilePath) return { title: null, description: null };
530
+ const sourceFile = readSourceFile(indexFilePath);
531
+ if (!sourceFile) return { title: null, description: null };
532
+ const metadata = findObjectLiteralDeclaration(sourceFile, "metadata");
533
+ if (!metadata) return { title: null, description: null };
534
+ return {
535
+ title: readStringPropertyInitializer(metadata, "title") ?? null,
536
+ description: readStringPropertyInitializer(metadata, "description") ?? null
537
+ };
538
+ }
539
+ function extractModuleFacts(options) {
540
+ const { moduleId, coreSrcRoot, coreVersion = null } = options;
541
+ const moduleRoot = path.join(coreSrcRoot, moduleId);
542
+ const entitiesFilePath = resolveConventionFile(path.join(moduleRoot, "data"), "entities") ?? resolveConventionFile(path.join(moduleRoot, "db"), "entities") ?? resolveConventionFile(path.join(moduleRoot, "data"), "schema");
543
+ const ceFilePath = resolveConventionFile(moduleRoot, "ce");
544
+ const eventsFilePath = resolveConventionFile(moduleRoot, "events");
545
+ const aclFilePath = resolveConventionFile(moduleRoot, "acl");
546
+ const diFilePath = resolveConventionFile(moduleRoot, "di");
547
+ const searchFilePath = resolveConventionFile(moduleRoot, "search");
548
+ const notificationsFilePath = resolveConventionFile(moduleRoot, "notifications");
549
+ const cliFilePath = resolveConventionFile(moduleRoot, "cli");
550
+ const indexFilePath = resolveConventionFile(moduleRoot, "index");
551
+ const backendDir = path.join(moduleRoot, "backend");
552
+ const warnings = [];
553
+ const { title, description } = extractModuleMeta(indexFilePath);
554
+ const customFieldEntityIds = collectCustomFieldEntityIds(ceFilePath);
555
+ const entities = extractEntities(moduleId, entitiesFilePath, customFieldEntityIds);
556
+ const events = extractEvents(eventsFilePath);
557
+ const aclFeatures = extractAclFeatures(aclFilePath);
558
+ const { source: registrySource, description: registryDescription } = resolveRegistrySource(options);
559
+ const apiRoutes = extractApiRoutes(moduleId, registrySource, registryDescription, warnings);
560
+ const diTokens = extractDiTokens(diFilePath);
561
+ const searchEntities = extractSearchEntities(searchFilePath, warnings);
562
+ const notifications = extractNotifications(notificationsFilePath, warnings);
563
+ const cli = extractCli(cliFilePath, warnings);
564
+ const hostTokens = {
565
+ entityIds: extractHostEntityIds(entities),
566
+ tableIds: extractTableIds(backendDir)
567
+ };
568
+ return {
569
+ module: moduleId,
570
+ title,
571
+ description,
572
+ coreVersion,
573
+ entities,
574
+ events,
575
+ aclFeatures,
576
+ apiRoutes,
577
+ diTokens,
578
+ searchEntities,
579
+ hostTokens,
580
+ notifications,
581
+ cli,
582
+ warnings
583
+ };
584
+ }
585
+ const EMPTY_SECTION_MARKER = "_none_";
586
+ function renderVersionStamp(coreVersion) {
587
+ const version = coreVersion && coreVersion.length > 0 ? coreVersion : "<unknown>";
588
+ return `<!-- generated from @open-mercato/core ${version} \u2014 R1 staleness stamp -->`;
589
+ }
590
+ function renderEntitiesSection(entities) {
591
+ if (entities.length === 0) return `## Entities
592
+
593
+ ${EMPTY_SECTION_MARKER}`;
594
+ const header = "| Entity ID | Class | Table | Editable | CustomFields |";
595
+ const divider = "|---|---|---|---|---|";
596
+ const rows = entities.map(
597
+ (entity) => `| ${entity.id} | ${entity.class} | ${entity.table} | ${entity.editable ? "yes" : "no"} | ${entity.customFields ? "yes" : "no"} |`
598
+ );
599
+ return ["## Entities", "", header, divider, ...rows].join("\n");
600
+ }
601
+ function renderEventsSection(events) {
602
+ const heading = `## Events (${events.length})`;
603
+ if (events.length === 0) return `${heading}
604
+
605
+ ${EMPTY_SECTION_MARKER}`;
606
+ const header = "| ID | Category | Entity |";
607
+ const divider = "|---|---|---|";
608
+ const rows = events.map((event) => `| ${event.id} | ${event.category ?? "\u2014"} | ${event.entity ?? "\u2014"} |`);
609
+ return [heading, "", header, divider, ...rows].join("\n");
610
+ }
611
+ function renderInlineListSection(heading, values) {
612
+ if (values.length === 0) return `${heading}
613
+
614
+ ${EMPTY_SECTION_MARKER}`;
615
+ return `${heading}
616
+
617
+ ${values.join(" \xB7 ")}`;
618
+ }
619
+ function describeAuthRule(rule) {
620
+ if (!rule) return "public";
621
+ if (rule.requireFeatures && rule.requireFeatures.length > 0) return rule.requireFeatures.join(", ");
622
+ if (rule.requireRoles && rule.requireRoles.length > 0) return rule.requireRoles.join(", ");
623
+ if (rule.requireAuth) return "auth";
624
+ return "public";
625
+ }
626
+ function renderApiRouteAuthCell(route) {
627
+ if (route.methods.length === 0) return "\u2014";
628
+ const groups = [];
629
+ for (const method of route.methods) {
630
+ const label = describeAuthRule(route.auth[method]);
631
+ const existing = groups.find((group) => group.label === label);
632
+ if (existing) existing.methods.push(method);
633
+ else groups.push({ label, methods: [method] });
634
+ }
635
+ return groups.map((group) => `${group.methods.join("/")} \u2192 ${group.label}`).join(" \xB7 ");
636
+ }
637
+ function renderApiRoutesSection(routes) {
638
+ if (routes.length === 0) return `## API routes
639
+
640
+ ${EMPTY_SECTION_MARKER}`;
641
+ const header = "| Path | Methods | Auth (per-method requireFeatures) |";
642
+ const divider = "|---|---|---|";
643
+ const rows = routes.map(
644
+ (route) => `| ${route.path} | ${route.methods.join(" ")} | ${renderApiRouteAuthCell(route)} |`
645
+ );
646
+ return ["## API routes", "", header, divider, ...rows].join("\n");
647
+ }
648
+ function renderHostTokensSection(hostTokens) {
649
+ const entityIdsLine = hostTokens.entityIds.length > 0 ? hostTokens.entityIds.join(" \xB7 ") : EMPTY_SECTION_MARKER;
650
+ const tableIdsLine = hostTokens.tableIds.length > 0 ? hostTokens.tableIds.join(" \xB7 ") : EMPTY_SECTION_MARKER;
651
+ return ["## Host extension points", "", `- Entity IDs: ${entityIdsLine}`, `- Table IDs: ${tableIdsLine}`].join("\n");
652
+ }
653
+ function renderModuleFactsMarkdown(facts) {
654
+ const sections = [
655
+ `# ${facts.module} \u2014 module facts (generated, do not edit)`,
656
+ renderVersionStamp(facts.coreVersion),
657
+ "",
658
+ renderEntitiesSection(facts.entities),
659
+ "",
660
+ renderEventsSection(facts.events),
661
+ "",
662
+ renderInlineListSection(`## ACL features (${facts.aclFeatures.length})`, facts.aclFeatures),
663
+ "",
664
+ renderApiRoutesSection(facts.apiRoutes),
665
+ "",
666
+ renderInlineListSection("## DI service tokens", facts.diTokens),
667
+ "",
668
+ renderInlineListSection("## Search entities", facts.searchEntities),
669
+ "",
670
+ renderHostTokensSection(facts.hostTokens),
671
+ "",
672
+ renderInlineListSection("## Notifications", facts.notifications),
673
+ "",
674
+ renderInlineListSection("## CLI", facts.cli),
675
+ ""
676
+ ];
677
+ return sections.join("\n");
678
+ }
679
+ function toModuleFactsJsonEntry(facts) {
680
+ return {
681
+ title: facts.title,
682
+ description: facts.description,
683
+ coreVersion: facts.coreVersion,
684
+ entities: facts.entities,
685
+ events: facts.events.map((event) => ({ id: event.id, category: event.category, entity: event.entity })),
686
+ aclFeatures: facts.aclFeatures,
687
+ apiRoutes: facts.apiRoutes,
688
+ diTokens: facts.diTokens,
689
+ searchEntities: facts.searchEntities,
690
+ hostTokens: facts.hostTokens,
691
+ notifications: facts.notifications,
692
+ cli: facts.cli
693
+ };
694
+ }
695
+ function buildModuleFactsJsonObject(factsByModule) {
696
+ const result = {};
697
+ for (const moduleId of Object.keys(factsByModule).sort((a, b) => a < b ? -1 : a > b ? 1 : 0)) {
698
+ result[moduleId] = toModuleFactsJsonEntry(factsByModule[moduleId]);
699
+ }
700
+ return result;
701
+ }
702
+ function renderModuleFactsJson(factsByModule) {
703
+ return `${JSON.stringify(buildModuleFactsJsonObject(factsByModule), null, 2)}
704
+ `;
705
+ }
706
+ function extractAllModuleFacts(options) {
707
+ const moduleIds = options.moduleIds ?? MODULE_FACTS_ALLOWLIST;
708
+ const factsByModule = {};
709
+ const markdownByModule = {};
710
+ const warnings = [];
711
+ for (const moduleId of moduleIds) {
712
+ const facts = extractModuleFacts({
713
+ moduleId,
714
+ coreSrcRoot: options.coreSrcRoot,
715
+ coreVersion: options.coreVersion ?? null,
716
+ registryPath: options.registryPath ?? null,
717
+ registrySource: options.registrySource ?? null
718
+ });
719
+ factsByModule[moduleId] = facts;
720
+ markdownByModule[moduleId] = renderModuleFactsMarkdown(facts);
721
+ warnings.push(...facts.warnings);
722
+ }
723
+ return { factsByModule, markdownByModule, warnings };
724
+ }
725
+ export {
726
+ MODULE_FACTS_ALLOWLIST,
727
+ buildModuleFactsJsonObject,
728
+ extractAllModuleFacts,
729
+ extractModuleFacts,
730
+ renderModuleFactsJson,
731
+ renderModuleFactsMarkdown,
732
+ toModuleFactsJsonEntry
733
+ };
734
+ //# sourceMappingURL=module-facts.js.map