@open-mercato/cli 0.6.8-develop.7032.1.70d74d4bfe → 0.6.8-develop.7037.1.ea0277b01e

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 (101) hide show
  1. package/dist/agentic/guides/module-facts.json +126 -126
  2. package/dist/agentic/guides/module-facts.v2.json +126 -126
  3. package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
  4. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  5. package/dist/agentic/guides/modules/api_keys/index.md +1 -1
  6. package/dist/agentic/guides/modules/attachments/index.md +1 -1
  7. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  8. package/dist/agentic/guides/modules/auth/index.md +1 -1
  9. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  10. package/dist/agentic/guides/modules/catalog/index.md +1 -1
  11. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  12. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  13. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  14. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  15. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  16. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  17. package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
  18. package/dist/agentic/guides/modules/configs/index.md +1 -1
  19. package/dist/agentic/guides/modules/content/index.md +1 -1
  20. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  21. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  22. package/dist/agentic/guides/modules/customers/index.md +1 -1
  23. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  24. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  25. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  26. package/dist/agentic/guides/modules/devices/index.md +1 -1
  27. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  28. package/dist/agentic/guides/modules/directory/index.md +1 -1
  29. package/dist/agentic/guides/modules/documents/index.md +1 -1
  30. package/dist/agentic/guides/modules/entities/index.md +1 -1
  31. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  32. package/dist/agentic/guides/modules/events/index.md +1 -1
  33. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  34. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  35. package/dist/agentic/guides/modules/generators/index.md +1 -1
  36. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  37. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  38. package/dist/agentic/guides/modules/messages/index.md +1 -1
  39. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  40. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  41. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  42. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  43. package/dist/agentic/guides/modules/planner/index.md +1 -1
  44. package/dist/agentic/guides/modules/portal/index.md +1 -1
  45. package/dist/agentic/guides/modules/progress/index.md +1 -1
  46. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  47. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  48. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  49. package/dist/agentic/guides/modules/resources/index.md +1 -1
  50. package/dist/agentic/guides/modules/sales/index.md +1 -1
  51. package/dist/agentic/guides/modules/scheduler/index.md +1 -1
  52. package/dist/agentic/guides/modules/search/index.md +1 -1
  53. package/dist/agentic/guides/modules/security/index.md +1 -1
  54. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  55. package/dist/agentic/guides/modules/sso/index.md +1 -1
  56. package/dist/agentic/guides/modules/staff/index.md +1 -1
  57. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  58. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  59. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  60. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  61. package/dist/agentic/guides/modules/translations/index.md +1 -1
  62. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  63. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  64. package/dist/agentic/guides/modules/wms/index.md +1 -1
  65. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  66. package/dist/agentic/guides/reference-module-facts.json +1 -1
  67. package/dist/agentic/guides/upstream/manifest.json +1 -1
  68. package/dist/lib/generators/ast/index.js +4 -1
  69. package/dist/lib/generators/ast/index.js.map +2 -2
  70. package/dist/lib/generators/ast/source-file.js +8 -1
  71. package/dist/lib/generators/ast/source-file.js.map +2 -2
  72. package/dist/lib/generators/ast/writers.js +9 -1
  73. package/dist/lib/generators/ast/writers.js.map +2 -2
  74. package/dist/lib/generators/module-registry.js +343 -183
  75. package/dist/lib/generators/module-registry.js.map +2 -2
  76. package/dist/lib/generators/route-manifest-shards.js +4 -1
  77. package/dist/lib/generators/route-manifest-shards.js.map +2 -2
  78. package/dist/lib/spawn.js +3 -2
  79. package/dist/lib/spawn.js.map +2 -2
  80. package/dist/lib/testing/integration.js +162 -24
  81. package/dist/lib/testing/integration.js.map +2 -2
  82. package/package.json +6 -6
  83. package/src/lib/__tests__/spawn.test.ts +23 -0
  84. package/src/lib/generators/__tests__/__snapshots__/bootstrap-registrations.test.ts.snap +14 -0
  85. package/src/lib/generators/__tests__/__snapshots__/command-loaders.test.ts.snap +23 -0
  86. package/src/lib/generators/__tests__/ast-writers-escaping.test.ts +64 -0
  87. package/src/lib/generators/__tests__/bootstrap-registrations.test.ts +176 -0
  88. package/src/lib/generators/__tests__/command-loaders.test.ts +239 -0
  89. package/src/lib/generators/__tests__/module-subset.test.ts +96 -72
  90. package/src/lib/generators/__tests__/output-snapshots.test.ts +1 -1
  91. package/src/lib/generators/__tests__/registry-variant-parity.test.ts +258 -0
  92. package/src/lib/generators/__tests__/route-manifest-shards.test.ts +15 -0
  93. package/src/lib/generators/__tests__/structural-contracts.test.ts +3 -3
  94. package/src/lib/generators/ast/index.ts +2 -1
  95. package/src/lib/generators/ast/source-file.ts +13 -0
  96. package/src/lib/generators/ast/writers.ts +26 -1
  97. package/src/lib/generators/module-registry.ts +417 -236
  98. package/src/lib/generators/route-manifest-shards.ts +8 -1
  99. package/src/lib/spawn.ts +5 -4
  100. package/src/lib/testing/__tests__/integration.test.ts +435 -0
  101. package/src/lib/testing/integration.ts +254 -32
@@ -32,6 +32,7 @@ import {
32
32
  conditionalExpression,
33
33
  createGeneratedSourceFile,
34
34
  elementAccess,
35
+ escapeGeneratedJsonLiteral,
35
36
  returnStatement,
36
37
  getSourceText,
37
38
  identifier,
@@ -44,6 +45,7 @@ import {
44
45
  optionalPropertyAccess,
45
46
  parenthesized,
46
47
  propertyAccess,
48
+ renderGeneratedValue,
47
49
  spreadExpression,
48
50
  type GeneratedObjectEntry,
49
51
  variableStatement,
@@ -133,7 +135,7 @@ type DashboardWidgetEntry = {
133
135
  importPath: string
134
136
  }
135
137
 
136
- type CommandLoaderGenerationEntry = {
138
+ export type CommandLoaderGenerationEntry = {
137
139
  moduleId: string
138
140
  key: string
139
141
  importPath: string
@@ -159,7 +161,7 @@ type RuntimeApiMethodMetadata = {
159
161
  type PageRouteGenerationResult = {
160
162
  eagerRoutes: string[]
161
163
  runtimeRoutes: string[]
162
- manifestRoutes: string[]
164
+ manifestRoutes: WriterFunction[]
163
165
  manifestShardRoutes: RouteManifestShardEntry[]
164
166
  metadataRoutes: RouteManifestShardEntry[]
165
167
  routePatterns: string[]
@@ -190,7 +192,7 @@ function assertUniqueBackendRoutePattern(
190
192
  type ApiRouteGenerationResult = {
191
193
  eagerApis: string[]
192
194
  runtimeApis: string[]
193
- manifestApis: string[]
195
+ manifestApis: WriterFunction[]
194
196
  manifestShardApis: RouteManifestShardEntry[]
195
197
  metadataApis: RouteManifestShardEntry[]
196
198
  }
@@ -1194,11 +1196,7 @@ function requiresRuntimePageMetadataFromSourceFile(sourceFile: string): boolean
1194
1196
  }
1195
1197
 
1196
1198
  function toLiteral(value: unknown): string {
1197
- return JSON.stringify(value)
1198
- .replace(/</g, '\\u003c')
1199
- .replace(/>/g, '\\u003e')
1200
- .replace(/\u2028/g, '\\u2028')
1201
- .replace(/\u2029/g, '\\u2029')
1199
+ return escapeGeneratedJsonLiteral(JSON.stringify(value))
1202
1200
  }
1203
1201
 
1204
1202
  const GENERATED_MODULE_RELATIVE_PREFIXES = ['@/', '../../src/modules/', './'] as const
@@ -1440,32 +1438,81 @@ function collectCommandLoaderEntries(
1440
1438
  return entries
1441
1439
  }
1442
1440
 
1443
- function renderCommandLoadersFile(entries: CommandLoaderGenerationEntry[]): string {
1441
+ export function renderCommandLoadersFile(entries: CommandLoaderGenerationEntry[]): string {
1444
1442
  const seenCommandIds = new Map<string, string>()
1445
- const rendered: string[] = []
1443
+ const loaderEntries: WriterFunction[] = []
1446
1444
 
1447
1445
  for (const entry of entries) {
1448
- const loadExpr = `() => ${buildDynamicImportExpression(entry.importPath)}`
1446
+ const loadValue = arrowFunction({
1447
+ body: importExpression(sanitizeGeneratedModuleSpecifier(entry.importPath)),
1448
+ })
1449
1449
  for (const id of entry.ids) {
1450
1450
  const previous = seenCommandIds.get(id)
1451
1451
  if (previous && previous !== entry.key) {
1452
1452
  throw new Error(`[generate] Duplicate command id "${id}" discovered in "${previous}" and "${entry.key}"`)
1453
1453
  }
1454
1454
  seenCommandIds.set(id, entry.key)
1455
- rendered.push(` { moduleId: ${toLiteral(entry.moduleId)}, id: ${toLiteral(id)}, key: ${toLiteral(entry.key)}, load: ${loadExpr} },`)
1455
+ loaderEntries.push(objectLiteral([
1456
+ { name: 'moduleId', value: entry.moduleId },
1457
+ { name: 'id', value: id },
1458
+ { name: 'key', value: entry.key },
1459
+ { name: 'load', value: loadValue },
1460
+ ]))
1456
1461
  }
1457
- rendered.push(` { moduleId: ${toLiteral(entry.moduleId)}, key: ${toLiteral(entry.key)}, load: ${loadExpr} },`)
1462
+ loaderEntries.push(objectLiteral([
1463
+ { name: 'moduleId', value: entry.moduleId },
1464
+ { name: 'key', value: entry.key },
1465
+ { name: 'load', value: loadValue },
1466
+ ]))
1458
1467
  }
1459
1468
 
1460
- return `// AUTO-GENERATED by mercato generate command-loaders
1461
- import type { CommandLoader } from '@open-mercato/shared/lib/commands'
1469
+ const sourceFile = createGeneratedSourceFile('command-loaders.generated.ts')
1470
+ addAutoGeneratedComment(sourceFile, 'command-loaders')
1471
+
1472
+ addImportSpec(sourceFile, {
1473
+ moduleSpecifier: '@open-mercato/shared/lib/commands',
1474
+ isTypeOnly: true,
1475
+ namedImports: [{ name: 'CommandLoader' }],
1476
+ })
1477
+
1478
+ sourceFile.addVariableStatement({
1479
+ declarationKind: VariableDeclarationKind.Const,
1480
+ isExported: true,
1481
+ declarations: [
1482
+ {
1483
+ name: 'commandLoaderEntries',
1484
+ type: 'CommandLoader[]',
1485
+ initializer: arrayLiteral(loaderEntries, writeValue),
1486
+ },
1487
+ ],
1488
+ })
1489
+ sourceFile.addExportAssignment({
1490
+ isExportEquals: false,
1491
+ expression: 'commandLoaderEntries',
1492
+ })
1493
+
1494
+ return getSourceText(sourceFile)
1495
+ }
1462
1496
 
1463
- export const commandLoaderEntries: CommandLoader[] = [
1464
- ${rendered.join('\n')}
1465
- ]
1497
+ export function renderBootstrapRegistrationsFile(options: {
1498
+ entryImports: string[]
1499
+ registrationImports: string[]
1500
+ calls: string[]
1501
+ }): string {
1502
+ const uniqueImports = [...new Set([...options.entryImports, ...options.registrationImports])]
1466
1503
 
1467
- export default commandLoaderEntries
1468
- `
1504
+ const sourceFile = createGeneratedSourceFile('bootstrap-registrations.generated.ts')
1505
+ addAutoGeneratedComment(sourceFile, 'registry')
1506
+ addImportStatements(sourceFile, uniqueImports)
1507
+
1508
+ sourceFile.addFunction({
1509
+ name: 'runBootstrapRegistrations',
1510
+ isExported: true,
1511
+ returnType: 'void',
1512
+ statements: options.calls,
1513
+ })
1514
+
1515
+ return getSourceText(sourceFile)
1469
1516
  }
1470
1517
 
1471
1518
  function serializeGeneratedImport(statement: GeneratedImportStatement): string {
@@ -1721,8 +1768,38 @@ function buildPageRouteProps(metaExpr: string, routePath: string): string {
1721
1768
  return `pattern: ${toLiteral(routePath || '/')}, requireAuth: (${metaExpr})?.requireAuth, requireRoles: (${metaExpr})?.requireRoles, requireFeatures: (${metaExpr})?.requireFeatures, requireCustomerAuth: (${metaExpr})?.requireCustomerAuth, requireCustomerFeatures: (${metaExpr})?.requireCustomerFeatures, nav: (${metaExpr})?.nav, title: (${metaExpr})?.pageTitle ?? (${metaExpr})?.title, titleKey: (${metaExpr})?.pageTitleKey ?? (${metaExpr})?.titleKey, group: (${metaExpr})?.pageGroup ?? (${metaExpr})?.group, groupKey: (${metaExpr})?.pageGroupKey ?? (${metaExpr})?.groupKey, icon: (${metaExpr})?.icon, order: (${metaExpr})?.pageOrder ?? (${metaExpr})?.order, priority: (${metaExpr})?.pagePriority ?? (${metaExpr})?.priority, navHidden: (${metaExpr})?.navHidden, visible: (${metaExpr})?.visible, enabled: (${metaExpr})?.enabled, breadcrumb: (${metaExpr})?.breadcrumb, pageContext: (${metaExpr})?.pageContext, placement: (${metaExpr})?.placement`
1722
1769
  }
1723
1770
 
1724
- function buildPageRouteManifestSpread(metaExpr: string, routePath: string): string {
1725
- return `...resolvePageRouteMetadata(${toLiteral(routePath || '/')}, ${metaExpr})`
1771
+ function buildPageRouteManifestSpread(metaExpr: string, routePath: string): WriterFunction {
1772
+ return callExpression(identifier('resolvePageRouteMetadata'), [routePath || '/', identifier(metaExpr)])
1773
+ }
1774
+
1775
+ function buildManifestRouteLoader(importPath: string): WriterFunction {
1776
+ return arrowFunction({
1777
+ async: true,
1778
+ body: block((writer) => {
1779
+ writeStatements(writer, [
1780
+ variableStatement({
1781
+ name: 'mod',
1782
+ initializer: awaitExpression(importExpression(sanitizeGeneratedModuleSpecifier(importPath))),
1783
+ }),
1784
+ returnStatement(
1785
+ asExpression(
1786
+ parenthesized(nullishCoalesce([
1787
+ propertyAccess(identifier('mod'), 'default'),
1788
+ identifier('mod'),
1789
+ ])),
1790
+ 'any',
1791
+ ),
1792
+ ),
1793
+ ])
1794
+ }),
1795
+ })
1796
+ }
1797
+
1798
+ function buildManifestApiLoader(importPath: string): WriterFunction {
1799
+ return arrowFunction({
1800
+ async: true,
1801
+ body: importExpression(sanitizeGeneratedModuleSpecifier(importPath)),
1802
+ })
1726
1803
  }
1727
1804
 
1728
1805
  function normalizeBreadcrumb(raw: unknown): SerializablePageMetadata['breadcrumb'] {
@@ -2041,7 +2118,7 @@ async function processPageFiles(options: {
2041
2118
  const metaPrefix = type === 'frontend' ? 'M' : 'BM'
2042
2119
  const eagerRoutes: string[] = []
2043
2120
  const runtimeRoutes: string[] = []
2044
- const manifestRoutes: string[] = []
2121
+ const manifestRoutes: WriterFunction[] = []
2045
2122
  const manifestShardRoutes: RouteManifestShardEntry[] = []
2046
2123
  const metadataRoutes: RouteManifestShardEntry[] = []
2047
2124
  const routePatterns: string[] = []
@@ -2116,18 +2193,25 @@ async function processPageFiles(options: {
2116
2193
  const manifestBaseProps = buildPageRouteManifestSpread(manifestMetaExpr, routePath)
2117
2194
  eagerRoutes.push(`{ ${baseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
2118
2195
  runtimeRoutes.push(`{ ${runtimeBaseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
2119
- const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps} }`
2120
- const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps}, load: async () => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod.default ?? mod) as any } }`
2121
- manifestRoutes.push(manifestDeclaration)
2196
+ const metadataEntries: GeneratedObjectEntry[] = [
2197
+ { name: 'moduleId', value: modId },
2198
+ { kind: 'spread', value: manifestBaseProps },
2199
+ ]
2200
+ const manifestEntry = objectLiteral([
2201
+ ...metadataEntries,
2202
+ { name: 'load', value: buildManifestRouteLoader(importPath) },
2203
+ ])
2204
+ const shardImports = manifestImportStatement ? [manifestImportStatement] : []
2205
+ manifestRoutes.push(manifestEntry)
2122
2206
  manifestShardRoutes.push({
2123
2207
  path: routePath,
2124
- declaration: manifestDeclaration,
2125
- imports: manifestImportStatement ? [manifestImportStatement] : [],
2208
+ declaration: renderGeneratedValue(manifestEntry),
2209
+ imports: shardImports,
2126
2210
  })
2127
2211
  metadataRoutes.push({
2128
2212
  path: routePath,
2129
- declaration: metadataDeclaration,
2130
- imports: manifestImportStatement ? [manifestImportStatement] : [],
2213
+ declaration: renderGeneratedValue(objectLiteral(metadataEntries)),
2214
+ imports: shardImports,
2131
2215
  })
2132
2216
  routePatterns.push(routePath)
2133
2217
  }
@@ -2207,18 +2291,25 @@ async function processPageFiles(options: {
2207
2291
  const manifestBaseProps = buildPageRouteManifestSpread(manifestMetaExpr, routePath)
2208
2292
  eagerRoutes.push(`{ ${baseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
2209
2293
  runtimeRoutes.push(`{ ${runtimeBaseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
2210
- const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps} }`
2211
- const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps}, load: async () => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod.default ?? mod) as any } }`
2212
- manifestRoutes.push(manifestDeclaration)
2294
+ const metadataEntries: GeneratedObjectEntry[] = [
2295
+ { name: 'moduleId', value: modId },
2296
+ { kind: 'spread', value: manifestBaseProps },
2297
+ ]
2298
+ const manifestEntry = objectLiteral([
2299
+ ...metadataEntries,
2300
+ { name: 'load', value: buildManifestRouteLoader(importPath) },
2301
+ ])
2302
+ const shardImports = manifestImportStatement ? [manifestImportStatement] : []
2303
+ manifestRoutes.push(manifestEntry)
2213
2304
  manifestShardRoutes.push({
2214
2305
  path: routePath,
2215
- declaration: manifestDeclaration,
2216
- imports: manifestImportStatement ? [manifestImportStatement] : [],
2306
+ declaration: renderGeneratedValue(manifestEntry),
2307
+ imports: shardImports,
2217
2308
  })
2218
2309
  metadataRoutes.push({
2219
2310
  path: routePath,
2220
- declaration: metadataDeclaration,
2221
- imports: manifestImportStatement ? [manifestImportStatement] : [],
2311
+ declaration: renderGeneratedValue(objectLiteral(metadataEntries)),
2312
+ imports: shardImports,
2222
2313
  })
2223
2314
  routePatterns.push(routePath)
2224
2315
  }
@@ -2250,7 +2341,7 @@ async function processApiRoutes(options: {
2250
2341
 
2251
2342
  const eagerApis: string[] = []
2252
2343
  const runtimeApis: string[] = []
2253
- const manifestApis: string[] = []
2344
+ const manifestApis: WriterFunction[] = []
2254
2345
  const manifestShardApis: RouteManifestShardEntry[] = []
2255
2346
  const metadataApis: RouteManifestShardEntry[] = []
2256
2347
 
@@ -2283,11 +2374,19 @@ async function processApiRoutes(options: {
2283
2374
  eagerImports.push(buildImportStatement(`* as ${importName}`, importPath))
2284
2375
  eagerApis.push(`{ path: ((${importName} as any).metadata?.path ?? ${toLiteral(routePath)}), metadata: (${importName} as any).metadata, handlers: ${importName} as any${docsPart} }`)
2285
2376
  runtimeApis.push(`{ path: ${toLiteral(resolvedPath)}, metadata: ${metadataLiteral}, handlers: { ${exportedMethods.map((method) => `${method}: async (req: Request, ctx?: any) => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod as any).${method}(req, ctx) }`).join(', ')} } }`)
2286
- const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('route-file')}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((method) => toLiteral(method)).join(', ')}] }`
2287
- const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('route-file')}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((method) => toLiteral(method)).join(', ')}], load: async () => ${buildDynamicImportExpression(importPath)} }`
2288
- manifestApis.push(manifestDeclaration)
2289
- manifestShardApis.push({ path: resolvedPath, declaration: manifestDeclaration })
2290
- metadataApis.push({ path: resolvedPath, declaration: metadataDeclaration })
2377
+ const metadataEntries: GeneratedObjectEntry[] = [
2378
+ { name: 'moduleId', value: modId },
2379
+ { name: 'kind', value: 'route-file' },
2380
+ { name: 'path', value: resolvedPath },
2381
+ { name: 'methods', value: arrayLiteral(exportedMethods, writeValue) },
2382
+ ]
2383
+ const manifestEntry = objectLiteral([
2384
+ ...metadataEntries,
2385
+ { name: 'load', value: buildManifestApiLoader(importPath) },
2386
+ ])
2387
+ manifestApis.push(manifestEntry)
2388
+ manifestShardApis.push({ path: resolvedPath, declaration: renderGeneratedValue(manifestEntry) })
2389
+ metadataApis.push({ path: resolvedPath, declaration: renderGeneratedValue(objectLiteral(metadataEntries)) })
2291
2390
  }
2292
2391
 
2293
2392
  // Single files (plain scripts, not route.*, not tests, skip method dirs)
@@ -2319,11 +2418,19 @@ async function processApiRoutes(options: {
2319
2418
  eagerImports.push(buildImportStatement(`* as ${importName}`, importPath))
2320
2419
  eagerApis.push(`{ path: ((${importName} as any).metadata?.path ?? ${toLiteral(routePath)}), metadata: (${importName} as any).metadata, handlers: ${importName} as any${docsPart} }`)
2321
2420
  runtimeApis.push(`{ path: ${toLiteral(resolvedPath)}, metadata: ${metadataLiteral}, handlers: { ${exportedMethods.map((entryMethod) => `${entryMethod}: async (req: Request, ctx?: any) => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod as any).${entryMethod}(req, ctx) }`).join(', ')} } }`)
2322
- const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('route-file')}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((entryMethod) => toLiteral(entryMethod)).join(', ')}] }`
2323
- const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('route-file')}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((entryMethod) => toLiteral(entryMethod)).join(', ')}], load: async () => ${buildDynamicImportExpression(importPath)} }`
2324
- manifestApis.push(manifestDeclaration)
2325
- manifestShardApis.push({ path: resolvedPath, declaration: manifestDeclaration })
2326
- metadataApis.push({ path: resolvedPath, declaration: metadataDeclaration })
2421
+ const metadataEntries: GeneratedObjectEntry[] = [
2422
+ { name: 'moduleId', value: modId },
2423
+ { name: 'kind', value: 'route-file' },
2424
+ { name: 'path', value: resolvedPath },
2425
+ { name: 'methods', value: arrayLiteral(exportedMethods, writeValue) },
2426
+ ]
2427
+ const manifestEntry = objectLiteral([
2428
+ ...metadataEntries,
2429
+ { name: 'load', value: buildManifestApiLoader(importPath) },
2430
+ ])
2431
+ manifestApis.push(manifestEntry)
2432
+ manifestShardApis.push({ path: resolvedPath, declaration: renderGeneratedValue(manifestEntry) })
2433
+ metadataApis.push({ path: resolvedPath, declaration: renderGeneratedValue(objectLiteral(metadataEntries)) })
2327
2434
  }
2328
2435
 
2329
2436
  // Legacy per-method
@@ -2365,11 +2472,20 @@ async function processApiRoutes(options: {
2365
2472
  eagerImports.push(buildImportStatement(`${importName}, * as ${metaName}`, importPath))
2366
2473
  eagerApis.push(`{ method: ${toLiteral(method)}, path: (${metaName}.metadata?.path ?? ${toLiteral(routePath)}), handler: ${importName}, metadata: ${metaName}.metadata${docsPart} }`)
2367
2474
  runtimeApis.push(`{ method: ${toLiteral(method)}, path: ${toLiteral(resolvedPath)}, handler: async (req: Request, ctx?: any) => { const mod = await ${buildDynamicImportExpression(importPath)}; const handler = ((mod as any).default ?? (mod as any).${method} ?? (mod as any).handler) as any; return handler(req, ctx) }, metadata: ${metadataLiteral} }`)
2368
- const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('legacy')}, method: ${toLiteral(method)}, path: ${toLiteral(resolvedPath)}, methods: [${toLiteral(method)}] }`
2369
- const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('legacy')}, method: ${toLiteral(method)}, path: ${toLiteral(resolvedPath)}, methods: [${toLiteral(method)}], load: async () => ${buildDynamicImportExpression(importPath)} }`
2370
- manifestApis.push(manifestDeclaration)
2371
- manifestShardApis.push({ path: resolvedPath, declaration: manifestDeclaration })
2372
- metadataApis.push({ path: resolvedPath, declaration: metadataDeclaration })
2475
+ const metadataEntries: GeneratedObjectEntry[] = [
2476
+ { name: 'moduleId', value: modId },
2477
+ { name: 'kind', value: 'legacy' },
2478
+ { name: 'method', value: method },
2479
+ { name: 'path', value: resolvedPath },
2480
+ { name: 'methods', value: arrayLiteral([method], writeValue) },
2481
+ ]
2482
+ const manifestEntry = objectLiteral([
2483
+ ...metadataEntries,
2484
+ { name: 'load', value: buildManifestApiLoader(importPath) },
2485
+ ])
2486
+ manifestApis.push(manifestEntry)
2487
+ manifestShardApis.push({ path: resolvedPath, declaration: renderGeneratedValue(manifestEntry) })
2488
+ metadataApis.push({ path: resolvedPath, declaration: renderGeneratedValue(objectLiteral(metadataEntries)) })
2373
2489
  }
2374
2490
  }
2375
2491
 
@@ -2382,17 +2498,6 @@ async function processApiRoutes(options: {
2382
2498
  }
2383
2499
  }
2384
2500
 
2385
- function processSubscribers(discovered: DiscoveredSubscriber[]): string[] {
2386
- const subscribers: string[] = []
2387
- for (const { id, importPath, metadata } of discovered) {
2388
- const subscriberId = metadata?.id ?? id
2389
- subscribers.push(
2390
- `{ id: ${toLiteral(subscriberId)}, event: ${toLiteral(metadata?.event ?? '')}, persistent: ${metadata?.persistent === undefined ? 'undefined' : toLiteral(metadata.persistent)}, sync: ${metadata?.sync === undefined ? 'undefined' : toLiteral(metadata.sync)}, priority: ${metadata?.priority === undefined ? 'undefined' : toLiteral(metadata.priority)}, handler: createLazyModuleSubscriber(() => ${buildDynamicImportExpression(importPath)}, ${toLiteral(subscriberId)}) }`
2391
- )
2392
- }
2393
- return subscribers
2394
- }
2395
-
2396
2501
  /**
2397
2502
  * Registry helper emitted for a worker that declares `metadata.onJobAbandoned`.
2398
2503
  *
@@ -2406,53 +2511,6 @@ function workersDeclareAbandonHook(discovered: DiscoveredWorker[]): boolean {
2406
2511
  return discovered.some(({ metadata }) => metadata.hasJobAbandonedHook === true)
2407
2512
  }
2408
2513
 
2409
- function processWorkers(discovered: DiscoveredWorker[]): string[] {
2410
- const workers: string[] = []
2411
- for (const { id, importPath, metadata } of discovered) {
2412
- const workerId = metadata.id ?? id
2413
- workers.push(
2414
- `{ id: ${toLiteral(workerId)}, queue: ${toLiteral(metadata.queue)}, concurrency: ${toLiteral(metadata.concurrency ?? 1)}${metadata.lockDuration === undefined ? '' : `, lockDuration: ${toLiteral(metadata.lockDuration)}`}${metadata.maxStalledCount === undefined ? '' : `, maxStalledCount: ${toLiteral(metadata.maxStalledCount)}`}${metadata.hasJobAbandonedHook ? `, onJobAbandoned: ${ABANDON_HOOK_FACTORY}(() => ${buildDynamicImportExpression(importPath)}, ${toLiteral(workerId)})` : ''}, handler: createLazyModuleWorker(() => ${buildDynamicImportExpression(importPath)}, ${toLiteral(workerId)}) }`
2415
- )
2416
- }
2417
- return workers
2418
- }
2419
-
2420
- function processTranslations(options: {
2421
- discovered: DiscoveredTranslation[]
2422
- modId: string
2423
- appImportBase: string
2424
- pkgImportBase: string
2425
- imports: string[]
2426
- extraImports?: string[]
2427
- }): string[] {
2428
- const { discovered, modId, appImportBase, pkgImportBase, imports, extraImports } = options
2429
- const translations: string[] = []
2430
- for (const { locale, coreHas, appHas } of discovered) {
2431
- if (coreHas && appHas) {
2432
- const cName = `T_${toVar(modId)}_${toVar(locale)}_C`
2433
- const aName = `T_${toVar(modId)}_${toVar(locale)}_A`
2434
- imports.push(buildImportStatement(cName, `${pkgImportBase}/i18n/${locale}.json`))
2435
- imports.push(buildImportStatement(aName, `${appImportBase}/i18n/${locale}.json`))
2436
- extraImports?.push(buildImportStatement(cName, `${pkgImportBase}/i18n/${locale}.json`))
2437
- extraImports?.push(buildImportStatement(aName, `${appImportBase}/i18n/${locale}.json`))
2438
- translations.push(
2439
- `'${locale}': { ...( ${cName} as unknown as Record<string,string> ), ...( ${aName} as unknown as Record<string,string> ) }`
2440
- )
2441
- } else if (appHas) {
2442
- const aName = `T_${toVar(modId)}_${toVar(locale)}_A`
2443
- imports.push(buildImportStatement(aName, `${appImportBase}/i18n/${locale}.json`))
2444
- extraImports?.push(buildImportStatement(aName, `${appImportBase}/i18n/${locale}.json`))
2445
- translations.push(`'${locale}': ${aName} as unknown as Record<string,string>`)
2446
- } else if (coreHas) {
2447
- const cName = `T_${toVar(modId)}_${toVar(locale)}_C`
2448
- imports.push(buildImportStatement(cName, `${pkgImportBase}/i18n/${locale}.json`))
2449
- extraImports?.push(buildImportStatement(cName, `${pkgImportBase}/i18n/${locale}.json`))
2450
- translations.push(`'${locale}': ${cName} as unknown as Record<string,string>`)
2451
- }
2452
- }
2453
- return translations
2454
- }
2455
-
2456
2514
  /**
2457
2515
  * Resolves a convention file and pushes its import + config entry to standalone arrays.
2458
2516
  * Used for files that produce their own generated output (notifications, AI tools, events, analytics, enrichers, etc.).
@@ -2597,6 +2655,64 @@ function buildModuleDashboardWidgetsValue(entries: DashboardWidgetEntry[]): Writ
2597
2655
  )
2598
2656
  }
2599
2657
 
2658
+ /**
2659
+ * Wraps expressions that are still produced as pre-rendered strings by collaborators this
2660
+ * change does not convert (page routes, api handlers, dashboard widgets) so they can sit
2661
+ * inside an AST-built array without being re-quoted as string literals. It disappears once
2662
+ * those producers emit writers themselves — see the follow-up noted in the spec's Non-Goals.
2663
+ */
2664
+ function preRenderedExpressionList(expressions: readonly string[]): WriterFunction {
2665
+ return arrayLiteral(expressions.map((expression) => identifier(expression)), writeValue)
2666
+ }
2667
+
2668
+ /**
2669
+ * `(X.<first> ?? X.<second>)` — the member-fallback shape the main registry path emits.
2670
+ * It is deliberately not `namespaceFallback`, which the app path uses: that helper walks
2671
+ * the members in an IIFE and treats only `!= null` as present, which is a different
2672
+ * expression. Converging the two is a behaviour change, not a formatting one.
2673
+ */
2674
+ function legacyNamespaceMemberFallback(importName: string, members: readonly [string, string]): WriterFunction {
2675
+ return parenthesized(nullishCoalesce([
2676
+ propertyAccess(identifier(importName), members[0]),
2677
+ propertyAccess(identifier(importName), members[1]),
2678
+ ]))
2679
+ }
2680
+
2681
+ function buildLegacyModuleListValue(options: {
2682
+ importName: string
2683
+ members: readonly [string, string]
2684
+ castType: string
2685
+ }): WriterFunction {
2686
+ return logicalOr([
2687
+ asExpression(legacyNamespaceMemberFallback(options.importName, options.members), options.castType),
2688
+ emptyArray(),
2689
+ ])
2690
+ }
2691
+
2692
+ function buildLegacyModuleSetupValue(importName: string): WriterFunction {
2693
+ return logicalOr([
2694
+ legacyNamespaceMemberFallback(importName, ['default', 'setup']),
2695
+ identifier('undefined'),
2696
+ ])
2697
+ }
2698
+
2699
+ function buildLegacyIntegrationListValue(
2700
+ importName: string,
2701
+ pluralMember: string,
2702
+ singularMember: string,
2703
+ castType: string,
2704
+ ): WriterFunction {
2705
+ const singular = propertyAccess(identifier(importName), singularMember)
2706
+
2707
+ return asExpression(
2708
+ parenthesized(nullishCoalesce([
2709
+ propertyAccess(identifier(importName), pluralMember),
2710
+ parenthesized(conditionalExpression(singular, arrayLiteral([singular], writeValue), emptyArray())),
2711
+ ])),
2712
+ castType,
2713
+ )
2714
+ }
2715
+
2600
2716
  function buildModuleIntegrationListValue(
2601
2717
  importName: string,
2602
2718
  pluralMember: string,
@@ -2644,7 +2760,12 @@ function buildModulesInfoExpression(): WriterFunction {
2644
2760
  function renderAstModuleRegistryFile(options: {
2645
2761
  fileName: string
2646
2762
  generator: string
2647
- imports: string[]
2763
+ /**
2764
+ * Import statements, or the structured specs generator extensions push onto the shared
2765
+ * import array via `processStandaloneConfig`. Both shapes reach this emitter at runtime,
2766
+ * so both are normalised here rather than at each call site.
2767
+ */
2768
+ imports: GeneratedImportStatement[]
2648
2769
  moduleEntries: WriterFunction[]
2649
2770
  includeCreateElementImport?: boolean
2650
2771
  includeAbandonHookImport?: boolean
@@ -2667,7 +2788,7 @@ function renderAstModuleRegistryFile(options: {
2667
2788
  { name: 'Module', isTypeOnly: true },
2668
2789
  ],
2669
2790
  })
2670
- addImportStatements(sourceFile, options.imports)
2791
+ addImportStatements(sourceFile, options.imports.map((entry) => serializeGeneratedImport(entry)))
2671
2792
 
2672
2793
  sourceFile.addVariableStatement({
2673
2794
  declarationKind: VariableDeclarationKind.Const,
@@ -2698,6 +2819,52 @@ function renderAstModuleRegistryFile(options: {
2698
2819
  return getSourceText(sourceFile)
2699
2820
  }
2700
2821
 
2822
+ function renderAstManifestFile(options: {
2823
+ fileName: string
2824
+ typeName: string
2825
+ exportName: string
2826
+ imports?: string[]
2827
+ entries: WriterFunction[]
2828
+ }): string {
2829
+ const sourceFile = createGeneratedSourceFile(options.fileName)
2830
+ addAutoGeneratedComment(sourceFile, 'registry')
2831
+
2832
+ const usesPageRouteMetadataHelper = options.typeName === 'FrontendRouteManifestEntry'
2833
+ || options.typeName === 'BackendRouteManifestEntry'
2834
+ addImportSpec(sourceFile, usesPageRouteMetadataHelper
2835
+ ? {
2836
+ moduleSpecifier: '@open-mercato/shared/modules/registry',
2837
+ namedImports: [
2838
+ { name: 'resolvePageRouteMetadata' },
2839
+ { name: options.typeName, isTypeOnly: true },
2840
+ ],
2841
+ }
2842
+ : {
2843
+ moduleSpecifier: '@open-mercato/shared/modules/registry',
2844
+ isTypeOnly: true,
2845
+ namedImports: [{ name: options.typeName }],
2846
+ })
2847
+ addImportStatements(sourceFile, options.imports ?? [])
2848
+
2849
+ sourceFile.addVariableStatement({
2850
+ declarationKind: VariableDeclarationKind.Const,
2851
+ isExported: true,
2852
+ declarations: [
2853
+ {
2854
+ name: options.exportName,
2855
+ type: `${options.typeName}[]`,
2856
+ initializer: arrayLiteral(options.entries, writeValue),
2857
+ },
2858
+ ],
2859
+ })
2860
+ sourceFile.addExportAssignment({
2861
+ isExportEquals: false,
2862
+ expression: options.exportName,
2863
+ })
2864
+
2865
+ return getSourceText(sourceFile)
2866
+ }
2867
+
2701
2868
  function renderAstLegacyAliasFile(options: {
2702
2869
  fileName: string
2703
2870
  exportName: string
@@ -2761,70 +2928,10 @@ function renderEnabledModuleIdsFile(moduleIds: readonly string[]): string {
2761
2928
  return getSourceText(sourceFile)
2762
2929
  }
2763
2930
 
2764
- function renderLegacyCompatibleArray(entries: readonly string[]): string {
2765
- return `[
2766
- ${entries.join(',\n ')}
2767
- ]`
2768
- }
2769
-
2770
2931
  function buildLazyRouteComponentExpression(importPath: string): string {
2771
2932
  return `async (props: any) => { const mod = await ${buildDynamicImportExpression(importPath)}; const Component = (mod.default ?? mod) as any; return createElement(Component, props) }`
2772
2933
  }
2773
2934
 
2774
- function renderAstLegacyModuleRegistryOutput(options: {
2775
- fileName: string
2776
- imports: GeneratedImportStatement[]
2777
- moduleEntries: string[]
2778
- includeCreateElementImport?: boolean
2779
- includeAbandonHookImport?: boolean
2780
- }): string {
2781
- // Derived from what is actually being written, not from a flag the caller has to remember: an
2782
- // import that must be opted into at every render site produces a file referencing an undeclared
2783
- // name the first time someone forgets, and the file only fails at `build:app`.
2784
- const needsAbandonHookImport = options.includeAbandonHookImport
2785
- || options.moduleEntries.some((entry) => entry.includes(ABANDON_HOOK_FACTORY))
2786
- const importSection = [
2787
- ...(options.includeCreateElementImport ? ["import { createElement } from 'react'"] : []),
2788
- needsAbandonHookImport
2789
- ? "import { createLazyModuleSubscriber, createLazyModuleWorker, createLazyModuleWorkerAbandonHook, type Module } from '@open-mercato/shared/modules/registry'"
2790
- : "import { createLazyModuleSubscriber, createLazyModuleWorker, type Module } from '@open-mercato/shared/modules/registry'",
2791
- ...options.imports.map((entry) => serializeGeneratedImport(entry)),
2792
- ].join('\n')
2793
-
2794
- return `// AUTO-GENERATED by mercato generate registry
2795
- ${importSection}
2796
-
2797
- export const modules: Module[] = ${renderLegacyCompatibleArray(options.moduleEntries)}
2798
- export const modulesInfo = modules.map(m => ({ id: m.id, ...(m.info || {}) }))
2799
- export default modules
2800
- `
2801
- }
2802
-
2803
- function renderAstLegacyManifestOutput(options: {
2804
- fileName: string
2805
- typeName: string
2806
- exportName: string
2807
- imports?: GeneratedImportStatement[]
2808
- entries: string[]
2809
- }): string {
2810
- const usesPageRouteMetadataHelper = options.typeName === 'FrontendRouteManifestEntry'
2811
- || options.typeName === 'BackendRouteManifestEntry'
2812
- const importSection = [
2813
- usesPageRouteMetadataHelper
2814
- ? `import { resolvePageRouteMetadata, type ${options.typeName} } from '@open-mercato/shared/modules/registry'`
2815
- : `import type { ${options.typeName} } from '@open-mercato/shared/modules/registry'`,
2816
- ...(options.imports ?? []).map((entry) => serializeGeneratedImport(entry)),
2817
- ].join('\n')
2818
-
2819
- return `// AUTO-GENERATED by mercato generate registry
2820
- ${importSection}
2821
-
2822
- export const ${options.exportName}: ${options.typeName}[] = ${renderLegacyCompatibleArray(options.entries)}
2823
-
2824
- export default ${options.exportName}
2825
- `
2826
- }
2827
-
2828
2935
  function buildRuntimeRouteComponent(importPath: string): WriterFunction {
2829
2936
  return arrowFunction({
2830
2937
  async: true,
@@ -3469,11 +3576,11 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
3469
3576
  const runtimeImports: string[] = []
3470
3577
  const frontendRouteManifestImports: string[] = []
3471
3578
  const backendRouteManifestImports: string[] = []
3472
- const moduleDecls: string[] = []
3473
- const runtimeModuleDecls: string[] = []
3474
- const frontendRouteManifestDecls: string[] = []
3475
- const backendRouteManifestDecls: string[] = []
3476
- const apiRouteManifestDecls: string[] = []
3579
+ const moduleDecls: WriterFunction[] = []
3580
+ const runtimeModuleDecls: WriterFunction[] = []
3581
+ const frontendRouteManifestDecls: WriterFunction[] = []
3582
+ const backendRouteManifestDecls: WriterFunction[] = []
3583
+ const apiRouteManifestDecls: WriterFunction[] = []
3477
3584
  const backendRouteShardEntries: RouteManifestShardEntry[] = []
3478
3585
  const backendRouteMetadataEntries: RouteManifestShardEntry[] = []
3479
3586
  const apiRouteShardEntries: RouteManifestShardEntry[] = []
@@ -3506,9 +3613,9 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
3506
3613
  const runtimeBackendRoutes: string[] = []
3507
3614
  const runtimeApis: string[] = []
3508
3615
  let cliImportName: string | null = null
3509
- const translations: string[] = []
3510
- const subscribers: string[] = []
3511
- const workers: string[] = []
3616
+ const translations: GeneratedObjectEntry[] = []
3617
+ const subscribers: WriterFunction[] = []
3618
+ const workers: WriterFunction[] = []
3512
3619
  let infoImportName: string | null = null
3513
3620
  let extensionsImportName: string | null = null
3514
3621
  let fieldsImportName: string | null = null
@@ -3722,7 +3829,7 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
3722
3829
  }
3723
3830
 
3724
3831
  // 16. Translations
3725
- translations.push(...processTranslations({
3832
+ translations.push(...processTranslationsAst({
3726
3833
  discovered: discovered.translations,
3727
3834
  modId,
3728
3835
  appImportBase,
@@ -3732,12 +3839,12 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
3732
3839
  }))
3733
3840
 
3734
3841
  // 17. Subscribers
3735
- subscribers.push(...processSubscribers(await discovered.getSubscribers()))
3842
+ subscribers.push(...processSubscribersAst(await discovered.getSubscribers()))
3736
3843
 
3737
3844
  // 18. Workers
3738
3845
  const discoveredWorkersForModule = await discovered.getWorkers()
3739
3846
  if (workersDeclareAbandonHook(discoveredWorkersForModule)) hasAbandonHookWorkers = true
3740
- workers.push(...processWorkers(discoveredWorkersForModule))
3847
+ workers.push(...processWorkersAst(discoveredWorkersForModule))
3741
3848
 
3742
3849
  // Build combined customFieldSets expression
3743
3850
  {
@@ -3760,44 +3867,120 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
3760
3867
  }
3761
3868
  }
3762
3869
 
3763
- moduleDecls.push(`{
3764
- id: ${toLiteral(modId)},
3765
- ${infoImportName ? `info: ${infoImportName}.metadata,` : ''}
3766
- ${frontendRoutes.length ? `frontendRoutes: [${frontendRoutes.join(', ')}],` : ''}
3767
- ${backendRoutes.length ? `backendRoutes: [${backendRoutes.join(', ')}],` : ''}
3768
- ${apis.length ? `apis: [${apis.join(', ')}],` : ''}
3769
- ${cliImportName ? `cli: ${cliImportName},` : ''}
3770
- ${translations.length ? `translations: { ${translations.join(', ')} },` : ''}
3771
- ${subscribers.length ? `subscribers: [${subscribers.join(', ')}],` : ''}
3772
- ${workers.length ? `workers: [${workers.join(', ')}],` : ''}
3773
- ${extensionsImportName ? `entityExtensions: ((${extensionsImportName}.default ?? ${extensionsImportName}.extensions) as import('@open-mercato/shared/modules/entities').EntityExtension[]) || [],` : ''}
3774
- customFieldSets: ${customFieldSetsExpr},
3775
- ${featuresImportName ? `features: ((${featuresImportName}.default ?? ${featuresImportName}.features) as any) || [],` : ''}
3776
- ${customEntitiesImportName ? `customEntities: ((${customEntitiesImportName}.default ?? ${customEntitiesImportName}.entities) as any) || [],` : ''}
3777
- ${dashboardWidgets.length ? `dashboardWidgets: [${dashboardWidgets.join(', ')}],` : ''}
3778
- ${setupImportName ? `setup: (${setupImportName}.default ?? ${setupImportName}.setup) || undefined,` : ''}
3779
- ${encryptionImportName ? `defaultEncryptionMaps: ((${encryptionImportName}.default ?? ${encryptionImportName}.defaultEncryptionMaps) as import('@open-mercato/shared/modules/encryption').ModuleEncryptionMap[]) || [],` : ''}
3780
- ${integrationExports ? `integrations: ((${integrationExports}.integrations ?? (${integrationExports}.integration ? [${integrationExports}.integration] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]),` : ''}
3781
- ${integrationExports ? `bundles: ((${integrationExports}.bundles ?? (${integrationExports}.bundle ? [${integrationExports}.bundle] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]),` : ''}
3782
- }`)
3783
- runtimeModuleDecls.push(`{
3784
- id: ${toLiteral(modId)},
3785
- ${infoImportName ? `info: ${infoImportName}.metadata,` : ''}
3786
- ${runtimeFrontendRoutes.length ? `frontendRoutes: [${runtimeFrontendRoutes.join(', ')}],` : ''}
3787
- ${runtimeBackendRoutes.length ? `backendRoutes: [${runtimeBackendRoutes.join(', ')}],` : ''}
3788
- ${runtimeApis.length ? `apis: [${runtimeApis.join(', ')}],` : ''}
3789
- ${translations.length ? `translations: { ${translations.join(', ')} },` : ''}
3790
- ${subscribers.length ? `subscribers: [${subscribers.join(', ')}],` : ''}
3791
- ${workers.length ? `workers: [${workers.join(', ')}],` : ''}
3792
- ${extensionsImportName ? `entityExtensions: ((${extensionsImportName}.default ?? ${extensionsImportName}.extensions) as import('@open-mercato/shared/modules/entities').EntityExtension[]) || [],` : ''}
3793
- customFieldSets: ${customFieldSetsExpr},
3794
- ${featuresImportName ? `features: ((${featuresImportName}.default ?? ${featuresImportName}.features) as any) || [],` : ''}
3795
- ${customEntitiesImportName ? `customEntities: ((${customEntitiesImportName}.default ?? ${customEntitiesImportName}.entities) as any) || [],` : ''}
3796
- ${setupImportName ? `setup: (${setupImportName}.default ?? ${setupImportName}.setup) || undefined,` : ''}
3797
- ${encryptionImportName ? `defaultEncryptionMaps: ((${encryptionImportName}.default ?? ${encryptionImportName}.defaultEncryptionMaps) as import('@open-mercato/shared/modules/encryption').ModuleEncryptionMap[]) || [],` : ''}
3798
- ${integrationExports ? `integrations: ((${integrationExports}.integrations ?? (${integrationExports}.integration ? [${integrationExports}.integration] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]),` : ''}
3799
- ${integrationExports ? `bundles: ((${integrationExports}.bundles ?? (${integrationExports}.bundle ? [${integrationExports}.bundle] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]),` : ''}
3800
- }`)
3870
+ const identityEntries: GeneratedObjectEntry[] = [{ name: 'id', value: modId }]
3871
+ if (infoImportName) {
3872
+ identityEntries.push({ name: 'info', value: propertyAccess(identifier(infoImportName), 'metadata') })
3873
+ }
3874
+
3875
+ const sharedTailEntries: GeneratedObjectEntry[] = []
3876
+ if (translations.length > 0) {
3877
+ sharedTailEntries.push({ name: 'translations', value: objectLiteral(translations) })
3878
+ }
3879
+ if (subscribers.length > 0) {
3880
+ sharedTailEntries.push({ name: 'subscribers', value: arrayLiteral(subscribers, writeValue) })
3881
+ }
3882
+ if (workers.length > 0) {
3883
+ sharedTailEntries.push({ name: 'workers', value: arrayLiteral(workers, writeValue) })
3884
+ }
3885
+ if (extensionsImportName) {
3886
+ sharedTailEntries.push({
3887
+ name: 'entityExtensions',
3888
+ value: buildLegacyModuleListValue({
3889
+ importName: extensionsImportName,
3890
+ members: ['default', 'extensions'],
3891
+ castType: "import('@open-mercato/shared/modules/entities').EntityExtension[]",
3892
+ }),
3893
+ })
3894
+ }
3895
+ sharedTailEntries.push({ name: 'customFieldSets', value: identifier(customFieldSetsExpr) })
3896
+ if (featuresImportName) {
3897
+ sharedTailEntries.push({
3898
+ name: 'features',
3899
+ value: buildLegacyModuleListValue({
3900
+ importName: featuresImportName,
3901
+ members: ['default', 'features'],
3902
+ castType: 'any',
3903
+ }),
3904
+ })
3905
+ }
3906
+ if (customEntitiesImportName) {
3907
+ sharedTailEntries.push({
3908
+ name: 'customEntities',
3909
+ value: buildLegacyModuleListValue({
3910
+ importName: customEntitiesImportName,
3911
+ members: ['default', 'entities'],
3912
+ castType: 'any',
3913
+ }),
3914
+ })
3915
+ }
3916
+
3917
+ const setupAndBeyondEntries: GeneratedObjectEntry[] = []
3918
+ if (setupImportName) {
3919
+ setupAndBeyondEntries.push({ name: 'setup', value: buildLegacyModuleSetupValue(setupImportName) })
3920
+ }
3921
+ if (encryptionImportName) {
3922
+ setupAndBeyondEntries.push({
3923
+ name: 'defaultEncryptionMaps',
3924
+ value: buildLegacyModuleListValue({
3925
+ importName: encryptionImportName,
3926
+ members: ['default', 'defaultEncryptionMaps'],
3927
+ castType: "import('@open-mercato/shared/modules/encryption').ModuleEncryptionMap[]",
3928
+ }),
3929
+ })
3930
+ }
3931
+ if (integrationExports) {
3932
+ setupAndBeyondEntries.push({
3933
+ name: 'integrations',
3934
+ value: buildLegacyIntegrationListValue(
3935
+ integrationExports,
3936
+ 'integrations',
3937
+ 'integration',
3938
+ "import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]",
3939
+ ),
3940
+ })
3941
+ setupAndBeyondEntries.push({
3942
+ name: 'bundles',
3943
+ value: buildLegacyIntegrationListValue(
3944
+ integrationExports,
3945
+ 'bundles',
3946
+ 'bundle',
3947
+ "import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]",
3948
+ ),
3949
+ })
3950
+ }
3951
+
3952
+ const eagerEntries: GeneratedObjectEntry[] = [...identityEntries]
3953
+ if (frontendRoutes.length > 0) {
3954
+ eagerEntries.push({ name: 'frontendRoutes', value: preRenderedExpressionList(frontendRoutes) })
3955
+ }
3956
+ if (backendRoutes.length > 0) {
3957
+ eagerEntries.push({ name: 'backendRoutes', value: preRenderedExpressionList(backendRoutes) })
3958
+ }
3959
+ if (apis.length > 0) {
3960
+ eagerEntries.push({ name: 'apis', value: preRenderedExpressionList(apis) })
3961
+ }
3962
+ if (cliImportName) {
3963
+ eagerEntries.push({ name: 'cli', value: identifier(cliImportName) })
3964
+ }
3965
+ eagerEntries.push(...sharedTailEntries)
3966
+ if (dashboardWidgets.length > 0) {
3967
+ eagerEntries.push({ name: 'dashboardWidgets', value: preRenderedExpressionList(dashboardWidgets) })
3968
+ }
3969
+ eagerEntries.push(...setupAndBeyondEntries)
3970
+ moduleDecls.push(objectLiteral(eagerEntries))
3971
+
3972
+ const runtimeEntries: GeneratedObjectEntry[] = [...identityEntries]
3973
+ if (runtimeFrontendRoutes.length > 0) {
3974
+ runtimeEntries.push({ name: 'frontendRoutes', value: preRenderedExpressionList(runtimeFrontendRoutes) })
3975
+ }
3976
+ if (runtimeBackendRoutes.length > 0) {
3977
+ runtimeEntries.push({ name: 'backendRoutes', value: preRenderedExpressionList(runtimeBackendRoutes) })
3978
+ }
3979
+ if (runtimeApis.length > 0) {
3980
+ runtimeEntries.push({ name: 'apis', value: preRenderedExpressionList(runtimeApis) })
3981
+ }
3982
+ runtimeEntries.push(...sharedTailEntries, ...setupAndBeyondEntries)
3983
+ runtimeModuleDecls.push(objectLiteral(runtimeEntries))
3801
3984
  }
3802
3985
 
3803
3986
  // === UMES Conflict Detection ===
@@ -3898,35 +4081,37 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
3898
4081
  }
3899
4082
  }
3900
4083
 
3901
- const output = renderAstLegacyModuleRegistryOutput({
4084
+ const output = renderAstModuleRegistryFile({
3902
4085
  fileName: 'modules.generated.ts',
4086
+ generator: 'registry',
3903
4087
  imports,
3904
4088
  moduleEntries: moduleDecls,
3905
4089
  includeCreateElementImport: hasRouteComponents,
3906
4090
  includeAbandonHookImport: hasAbandonHookWorkers,
3907
4091
  })
3908
- const runtimeOutput = renderAstLegacyModuleRegistryOutput({
4092
+ const runtimeOutput = renderAstModuleRegistryFile({
3909
4093
  fileName: 'modules.runtime.generated.ts',
4094
+ generator: 'registry',
3910
4095
  imports: runtimeImports,
3911
4096
  moduleEntries: runtimeModuleDecls,
3912
4097
  includeCreateElementImport: true,
3913
4098
  includeAbandonHookImport: hasAbandonHookWorkers,
3914
4099
  })
3915
- const frontendRoutesOutput = renderAstLegacyManifestOutput({
4100
+ const frontendRoutesOutput = renderAstManifestFile({
3916
4101
  fileName: 'frontend-routes.generated.ts',
3917
4102
  typeName: 'FrontendRouteManifestEntry',
3918
4103
  exportName: 'frontendRoutes',
3919
4104
  imports: frontendRouteManifestImports,
3920
4105
  entries: frontendRouteManifestDecls,
3921
4106
  })
3922
- const backendRoutesOutput = renderAstLegacyManifestOutput({
4107
+ const backendRoutesOutput = renderAstManifestFile({
3923
4108
  fileName: 'backend-routes.generated.ts',
3924
4109
  typeName: 'BackendRouteManifestEntry',
3925
4110
  exportName: 'backendRoutes',
3926
4111
  imports: backendRouteManifestImports,
3927
4112
  entries: backendRouteManifestDecls,
3928
4113
  })
3929
- const apiRoutesOutput = renderAstLegacyManifestOutput({
4114
+ const apiRoutesOutput = renderAstManifestFile({
3930
4115
  fileName: 'api-routes.generated.ts',
3931
4116
  typeName: 'ApiRouteManifestEntry',
3932
4117
  exportName: 'apiRoutes',
@@ -4066,15 +4251,11 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
4066
4251
  allRegImports.push(...reg.registrationImports)
4067
4252
  allCalls.push(reg.buildCall(reg.entriesExportName))
4068
4253
  }
4069
- const uniqueImports = [...new Set([...allEntryImports, ...allRegImports])]
4070
- const importSection = uniqueImports.join('\n')
4071
- const body = ` ${allCalls.join('\n ')}`
4072
- const bootstrapRegsOutput = `// AUTO-GENERATED by mercato generate registry
4073
- ${importSection ? `${importSection}\n` : ''}
4074
- export function runBootstrapRegistrations(): void {
4075
- ${body}
4076
- }
4077
- `
4254
+ const bootstrapRegsOutput = renderBootstrapRegistrationsFile({
4255
+ entryImports: allEntryImports,
4256
+ registrationImports: allRegImports,
4257
+ calls: allCalls,
4258
+ })
4078
4259
  writeGeneratedFile({ outFile: bootstrapRegsOutFile, checksumFile: bootstrapRegsChecksumFile, content: bootstrapRegsOutput, structureChecksum, result, quiet })
4079
4260
  }
4080
4261