@open-mercato/cli 0.4.9-develop-7afbe1e834 → 0.4.9-develop-94fb251ed3
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/AGENTS.md +34 -0
- package/dist/lib/generators/module-registry.js +144 -0
- package/dist/lib/generators/module-registry.js.map +3 -3
- package/dist/lib/testing/integration.js +109 -78
- package/dist/lib/testing/integration.js.map +2 -2
- package/package.json +4 -4
- package/src/lib/generators/__tests__/module-subset.test.ts +107 -1
- package/src/lib/generators/__tests__/scanner.test.ts +2 -1
- package/src/lib/generators/module-registry.ts +144 -0
- package/src/lib/testing/__tests__/integration.test.ts +106 -3
- package/src/lib/testing/integration.ts +137 -85
package/AGENTS.md
CHANGED
|
@@ -16,6 +16,7 @@ The CLI auto-discovers module files across all packages and `apps/mercato/src/mo
|
|
|
16
16
|
|
|
17
17
|
- `index.ts` (metadata), `cli.ts`, `di.ts`, `acl.ts`, `setup.ts`, `ce.ts`
|
|
18
18
|
- `search.ts`, `events.ts`, `notifications.ts`, `ai-tools.ts`
|
|
19
|
+
- `generators.ts` — module-level generator plugin declarations (see below)
|
|
19
20
|
- `data/entities.ts`, `data/extensions.ts`
|
|
20
21
|
- `api/`, `subscribers/`, `workers/`, `widgets/`
|
|
21
22
|
|
|
@@ -65,6 +66,39 @@ yarn build:packages # Rebuild with generated files
|
|
|
65
66
|
|
|
66
67
|
Each module has an optional `cli.ts` with a default export for module-specific CLI commands. These are auto-discovered and registered in `modules.cli.generated.ts`.
|
|
67
68
|
|
|
69
|
+
## Generator Plugins (`generators.ts`)
|
|
70
|
+
|
|
71
|
+
Modules can declare additional aggregated output files by exporting `generatorPlugins: GeneratorPlugin[]` from a `generators.ts` file at the module root. The CLI discovers these at generation time via dynamic import and drives the extra output files without any hardcoded knowledge in the CLI package.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// src/modules/<module>/generators.ts
|
|
75
|
+
import type { GeneratorPlugin } from '@open-mercato/shared/modules/generators'
|
|
76
|
+
|
|
77
|
+
function buildMyOutput({ importSection, entriesLiteral }: { importSection: string; entriesLiteral: string }): string {
|
|
78
|
+
return `// AUTO-GENERATED by mercato generate registry
|
|
79
|
+
${importSection ? `${importSection}\n` : ''}export const myItemEntries = [
|
|
80
|
+
${entriesLiteral ? ` ${entriesLiteral}\n` : ''}]
|
|
81
|
+
`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const generatorPlugins: GeneratorPlugin[] = [
|
|
85
|
+
{
|
|
86
|
+
id: 'mymodule.my-registry', // unique ID — first declaration wins
|
|
87
|
+
conventionFile: 'mymodule.items.ts', // file to look for in every module
|
|
88
|
+
importPrefix: 'MYMODULE_ITEMS',
|
|
89
|
+
configExpr: (importName: string, moduleId: string) =>
|
|
90
|
+
`{ moduleId: '${moduleId}', items: (${importName}.default ?? ${importName}.myItems ?? []) }`,
|
|
91
|
+
outputFileName: 'mymodule-items.generated.ts',
|
|
92
|
+
buildOutput: buildMyOutput,
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Rules:
|
|
98
|
+
- MUST only use `import type` (no runtime imports) so the file is safe to execute at generation time
|
|
99
|
+
- The `id` is deduped across modules; the first registered definition wins
|
|
100
|
+
- If a module is disabled, its `generators.ts` is never loaded, so its output files are not written
|
|
101
|
+
|
|
68
102
|
## Testing Generator Changes
|
|
69
103
|
|
|
70
104
|
After modifying generator logic, run the generator and verify the output files in `apps/mercato/.mercato/generated/`. Check that all expected modules, entities, and registrations appear correctly.
|
|
@@ -293,6 +293,8 @@ async function generateModuleRegistry(options) {
|
|
|
293
293
|
const eventsChecksumFile = path.join(outputDir, "events.generated.checksum");
|
|
294
294
|
const analyticsOutFile = path.join(outputDir, "analytics.generated.ts");
|
|
295
295
|
const analyticsChecksumFile = path.join(outputDir, "analytics.generated.checksum");
|
|
296
|
+
const bootstrapRegsOutFile = path.join(outputDir, "bootstrap-registrations.generated.ts");
|
|
297
|
+
const bootstrapRegsChecksumFile = path.join(outputDir, "bootstrap-registrations.generated.checksum");
|
|
296
298
|
const transFieldsOutFile = path.join(outputDir, "translations-fields.generated.ts");
|
|
297
299
|
const transFieldsChecksumFile = path.join(outputDir, "translations-fields.generated.checksum");
|
|
298
300
|
const enrichersOutFile = path.join(outputDir, "enrichers.generated.ts");
|
|
@@ -307,7 +309,33 @@ async function generateModuleRegistry(options) {
|
|
|
307
309
|
const guardsChecksumFile = path.join(outputDir, "guards.generated.checksum");
|
|
308
310
|
const commandInterceptorsOutFile = path.join(outputDir, "command-interceptors.generated.ts");
|
|
309
311
|
const commandInterceptorsChecksumFile = path.join(outputDir, "command-interceptors.generated.checksum");
|
|
312
|
+
const frontendMiddlewareOutFile = path.join(outputDir, "frontend-middleware.generated.ts");
|
|
313
|
+
const frontendMiddlewareChecksumFile = path.join(outputDir, "frontend-middleware.generated.checksum");
|
|
314
|
+
const backendMiddlewareOutFile = path.join(outputDir, "backend-middleware.generated.ts");
|
|
315
|
+
const backendMiddlewareChecksumFile = path.join(outputDir, "backend-middleware.generated.checksum");
|
|
310
316
|
const enabled = resolver.loadEnabledModules();
|
|
317
|
+
const pluginRegistry = /* @__PURE__ */ new Map();
|
|
318
|
+
const pluginState = /* @__PURE__ */ new Map();
|
|
319
|
+
for (const entry of enabled) {
|
|
320
|
+
const roots = resolver.getModulePaths(entry);
|
|
321
|
+
const rawImps = resolver.getModuleImportBase(entry);
|
|
322
|
+
const isAppMod = entry.from === "@app";
|
|
323
|
+
const appImportBase = isAppMod ? `../../src/modules/${entry.id}` : rawImps.appBase;
|
|
324
|
+
const imps = { appBase: appImportBase, pkgBase: rawImps.pkgBase };
|
|
325
|
+
const resolved = resolveModuleFile(roots, imps, "generators.ts");
|
|
326
|
+
if (!resolved) continue;
|
|
327
|
+
try {
|
|
328
|
+
const pluginMod = await import(resolved.absolutePath);
|
|
329
|
+
const plugins = pluginMod.generatorPlugins ?? pluginMod.default ?? [];
|
|
330
|
+
for (const plugin of plugins) {
|
|
331
|
+
if (!pluginRegistry.has(plugin.id)) {
|
|
332
|
+
pluginRegistry.set(plugin.id, plugin);
|
|
333
|
+
pluginState.set(plugin.id, { imports: [], configs: [] });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
}
|
|
311
339
|
const imports = [];
|
|
312
340
|
const moduleDecls = [];
|
|
313
341
|
const importIdRef = { value: 0 };
|
|
@@ -348,6 +376,10 @@ async function generateModuleRegistry(options) {
|
|
|
348
376
|
const guardImports = [];
|
|
349
377
|
const commandInterceptorConfigs = [];
|
|
350
378
|
const commandInterceptorImports = [];
|
|
379
|
+
const frontendMiddlewareConfigs = [];
|
|
380
|
+
const frontendMiddlewareImports = [];
|
|
381
|
+
const backendMiddlewareConfigs = [];
|
|
382
|
+
const backendMiddlewareImports = [];
|
|
351
383
|
const umesConflictSources = [];
|
|
352
384
|
for (const entry of enabled) {
|
|
353
385
|
const modId = entry.id;
|
|
@@ -599,6 +631,19 @@ async function generateModuleRegistry(options) {
|
|
|
599
631
|
sharedImports: imports,
|
|
600
632
|
configExpr: (n, id) => `{ moduleId: '${id}', fields: (${n}.default ?? ${n}.translatableFields ?? {}) as Record<string, string[]> }`
|
|
601
633
|
});
|
|
634
|
+
for (const plugin of pluginRegistry.values()) {
|
|
635
|
+
processStandaloneConfig({
|
|
636
|
+
roots,
|
|
637
|
+
imps,
|
|
638
|
+
modId,
|
|
639
|
+
importIdRef,
|
|
640
|
+
relativePath: plugin.conventionFile,
|
|
641
|
+
prefix: plugin.importPrefix,
|
|
642
|
+
standaloneImports: pluginState.get(plugin.id).imports,
|
|
643
|
+
standaloneConfigs: pluginState.get(plugin.id).configs,
|
|
644
|
+
configExpr: plugin.configExpr
|
|
645
|
+
});
|
|
646
|
+
}
|
|
602
647
|
{
|
|
603
648
|
const resolved = resolveModuleFile(roots, imps, "inbox-actions.ts");
|
|
604
649
|
if (resolved) {
|
|
@@ -632,6 +677,28 @@ async function generateModuleRegistry(options) {
|
|
|
632
677
|
standaloneConfigs: commandInterceptorConfigs,
|
|
633
678
|
configExpr: (n, id) => `{ moduleId: '${id}', interceptors: ((${n} as any).interceptors ?? (${n} as any).default ?? []) }`
|
|
634
679
|
});
|
|
680
|
+
processStandaloneConfig({
|
|
681
|
+
roots,
|
|
682
|
+
imps,
|
|
683
|
+
modId,
|
|
684
|
+
importIdRef,
|
|
685
|
+
relativePath: "frontend/middleware.ts",
|
|
686
|
+
prefix: "FRONTEND_MIDDLEWARE",
|
|
687
|
+
standaloneImports: frontendMiddlewareImports,
|
|
688
|
+
standaloneConfigs: frontendMiddlewareConfigs,
|
|
689
|
+
configExpr: (n, id) => `{ moduleId: '${id}', middleware: ((${n} as any).middleware ?? (${n} as any).default ?? []) }`
|
|
690
|
+
});
|
|
691
|
+
processStandaloneConfig({
|
|
692
|
+
roots,
|
|
693
|
+
imps,
|
|
694
|
+
modId,
|
|
695
|
+
importIdRef,
|
|
696
|
+
relativePath: "backend/middleware.ts",
|
|
697
|
+
prefix: "BACKEND_MIDDLEWARE",
|
|
698
|
+
standaloneImports: backendMiddlewareImports,
|
|
699
|
+
standaloneConfigs: backendMiddlewareConfigs,
|
|
700
|
+
configExpr: (n, id) => `{ moduleId: '${id}', middleware: ((${n} as any).middleware ?? (${n} as any).default ?? []) }`
|
|
701
|
+
});
|
|
635
702
|
{
|
|
636
703
|
const setup = resolveConventionFile(roots, imps, "setup.ts", "SETUP", modId, importIdRef, imports);
|
|
637
704
|
if (setup) setupImportName = setup.importName;
|
|
@@ -1269,6 +1336,39 @@ export const allAiTools = aiToolConfigEntries.flatMap(e => e.tools)
|
|
|
1269
1336
|
writeGeneratedFile({ outFile: eventsOutFile, checksumFile: eventsChecksumFile, content: eventsOutput, structureChecksum, result, quiet });
|
|
1270
1337
|
writeGeneratedFile({ outFile: analyticsOutFile, checksumFile: analyticsChecksumFile, content: analyticsOutput, structureChecksum, result, quiet });
|
|
1271
1338
|
writeGeneratedFile({ outFile: transFieldsOutFile, checksumFile: transFieldsChecksumFile, content: transFieldsOutput, structureChecksum, result, quiet });
|
|
1339
|
+
for (const [pluginId, plugin] of pluginRegistry) {
|
|
1340
|
+
const state = pluginState.get(pluginId);
|
|
1341
|
+
const importSection = state.imports.join("\n");
|
|
1342
|
+
const entriesLiteral = state.configs.join(",\n ");
|
|
1343
|
+
const content = plugin.buildOutput({ importSection, entriesLiteral });
|
|
1344
|
+
const outFile2 = path.join(outputDir, plugin.outputFileName);
|
|
1345
|
+
const checksumFile2 = outFile2.replace(".ts", ".checksum");
|
|
1346
|
+
writeGeneratedFile({ outFile: outFile2, checksumFile: checksumFile2, content, structureChecksum, result, quiet });
|
|
1347
|
+
}
|
|
1348
|
+
{
|
|
1349
|
+
const bootstrapPlugins = [...pluginRegistry.values()].filter((p) => p.bootstrapRegistration);
|
|
1350
|
+
const allEntryImports = [];
|
|
1351
|
+
const allRegImports = [];
|
|
1352
|
+
const allCalls = [];
|
|
1353
|
+
for (const plugin of bootstrapPlugins) {
|
|
1354
|
+
const reg = plugin.bootstrapRegistration;
|
|
1355
|
+
const outputBase = plugin.outputFileName.replace(".ts", "");
|
|
1356
|
+
allEntryImports.push(`import { ${reg.entriesExportName} } from './${outputBase}'`);
|
|
1357
|
+
allRegImports.push(...reg.registrationImports);
|
|
1358
|
+
allCalls.push(reg.buildCall(reg.entriesExportName));
|
|
1359
|
+
}
|
|
1360
|
+
const uniqueImports = [.../* @__PURE__ */ new Set([...allEntryImports, ...allRegImports])];
|
|
1361
|
+
const importSection = uniqueImports.join("\n");
|
|
1362
|
+
const body = allCalls.length ? ` ${allCalls.join("\n ")}` : "";
|
|
1363
|
+
const bootstrapRegsOutput = `// AUTO-GENERATED by mercato generate registry
|
|
1364
|
+
${importSection ? `${importSection}
|
|
1365
|
+
` : ""}
|
|
1366
|
+
export function runBootstrapRegistrations(): void {
|
|
1367
|
+
${body}
|
|
1368
|
+
}
|
|
1369
|
+
`;
|
|
1370
|
+
writeGeneratedFile({ outFile: bootstrapRegsOutFile, checksumFile: bootstrapRegsChecksumFile, content: bootstrapRegsOutput, structureChecksum, result, quiet });
|
|
1371
|
+
}
|
|
1272
1372
|
const enricherEntriesLiteral = enricherConfigs.join(",\n ");
|
|
1273
1373
|
const enricherImportSection = enricherImports.join("\n");
|
|
1274
1374
|
const enrichersOutput = `// AUTO-GENERATED by mercato generate registry
|
|
@@ -1368,6 +1468,50 @@ ${commandInterceptorEntriesLiteral ? ` ${commandInterceptorEntriesLiteral}
|
|
|
1368
1468
|
` : ""}]
|
|
1369
1469
|
`;
|
|
1370
1470
|
writeGeneratedFile({ outFile: commandInterceptorsOutFile, checksumFile: commandInterceptorsChecksumFile, content: commandInterceptorsOutput, structureChecksum, result, quiet });
|
|
1471
|
+
const frontendMiddlewareEntriesLiteral = frontendMiddlewareConfigs.join(",\n ");
|
|
1472
|
+
const frontendMiddlewareImportSection = frontendMiddlewareImports.join("\n");
|
|
1473
|
+
const frontendMiddlewareOutput = `// AUTO-GENERATED by mercato generate registry
|
|
1474
|
+
import type { PageMiddlewareRegistryEntry, PageRouteMiddleware } from '@open-mercato/shared/modules/middleware/page'
|
|
1475
|
+
${frontendMiddlewareImportSection ? `
|
|
1476
|
+
${frontendMiddlewareImportSection}
|
|
1477
|
+
` : "\n"}type FrontendMiddlewareEntry = { moduleId: string; middleware: PageRouteMiddleware[] }
|
|
1478
|
+
|
|
1479
|
+
const entriesRaw: FrontendMiddlewareEntry[] = [
|
|
1480
|
+
${frontendMiddlewareEntriesLiteral ? ` ${frontendMiddlewareEntriesLiteral}
|
|
1481
|
+
` : ""}]
|
|
1482
|
+
|
|
1483
|
+
export const frontendMiddlewareEntries: PageMiddlewareRegistryEntry[] = entriesRaw
|
|
1484
|
+
`;
|
|
1485
|
+
writeGeneratedFile({
|
|
1486
|
+
outFile: frontendMiddlewareOutFile,
|
|
1487
|
+
checksumFile: frontendMiddlewareChecksumFile,
|
|
1488
|
+
content: frontendMiddlewareOutput,
|
|
1489
|
+
structureChecksum,
|
|
1490
|
+
result,
|
|
1491
|
+
quiet
|
|
1492
|
+
});
|
|
1493
|
+
const backendMiddlewareEntriesLiteral = backendMiddlewareConfigs.join(",\n ");
|
|
1494
|
+
const backendMiddlewareImportSection = backendMiddlewareImports.join("\n");
|
|
1495
|
+
const backendMiddlewareOutput = `// AUTO-GENERATED by mercato generate registry
|
|
1496
|
+
import type { PageMiddlewareRegistryEntry, PageRouteMiddleware } from '@open-mercato/shared/modules/middleware/page'
|
|
1497
|
+
${backendMiddlewareImportSection ? `
|
|
1498
|
+
${backendMiddlewareImportSection}
|
|
1499
|
+
` : "\n"}type BackendMiddlewareEntry = { moduleId: string; middleware: PageRouteMiddleware[] }
|
|
1500
|
+
|
|
1501
|
+
const entriesRaw: BackendMiddlewareEntry[] = [
|
|
1502
|
+
${backendMiddlewareEntriesLiteral ? ` ${backendMiddlewareEntriesLiteral}
|
|
1503
|
+
` : ""}]
|
|
1504
|
+
|
|
1505
|
+
export const backendMiddlewareEntries: PageMiddlewareRegistryEntry[] = entriesRaw
|
|
1506
|
+
`;
|
|
1507
|
+
writeGeneratedFile({
|
|
1508
|
+
outFile: backendMiddlewareOutFile,
|
|
1509
|
+
checksumFile: backendMiddlewareChecksumFile,
|
|
1510
|
+
content: backendMiddlewareOutput,
|
|
1511
|
+
structureChecksum,
|
|
1512
|
+
result,
|
|
1513
|
+
quiet
|
|
1514
|
+
});
|
|
1371
1515
|
return result;
|
|
1372
1516
|
}
|
|
1373
1517
|
async function generateModuleRegistryCli(options) {
|