@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 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) {