@open-mercato/cli 0.6.8-develop.6891.1.4dca3f1ad3 → 0.6.8-develop.6892.1.f179677788

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 (105) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/AGENTS.md +6 -1
  3. package/dist/agentic/guides/module-facts.json +113 -113
  4. package/dist/agentic/guides/modules/ai_assistant.md +1 -1
  5. package/dist/agentic/guides/modules/api_docs.md +1 -1
  6. package/dist/agentic/guides/modules/api_keys.md +1 -1
  7. package/dist/agentic/guides/modules/attachments.md +1 -1
  8. package/dist/agentic/guides/modules/audit_logs.md +1 -1
  9. package/dist/agentic/guides/modules/auth.md +1 -1
  10. package/dist/agentic/guides/modules/business_rules.md +1 -1
  11. package/dist/agentic/guides/modules/catalog.md +1 -1
  12. package/dist/agentic/guides/modules/channel_gmail.md +1 -1
  13. package/dist/agentic/guides/modules/channel_imap.md +1 -1
  14. package/dist/agentic/guides/modules/checkout.md +1 -1
  15. package/dist/agentic/guides/modules/communication_channels.md +3 -3
  16. package/dist/agentic/guides/modules/configs.md +1 -1
  17. package/dist/agentic/guides/modules/content.md +1 -1
  18. package/dist/agentic/guides/modules/currencies.md +1 -1
  19. package/dist/agentic/guides/modules/customer_accounts.md +1 -1
  20. package/dist/agentic/guides/modules/customers.md +1 -1
  21. package/dist/agentic/guides/modules/dashboards.md +1 -1
  22. package/dist/agentic/guides/modules/data_sync.md +1 -1
  23. package/dist/agentic/guides/modules/design_system.md +1 -1
  24. package/dist/agentic/guides/modules/dictionaries.md +1 -1
  25. package/dist/agentic/guides/modules/directory.md +1 -1
  26. package/dist/agentic/guides/modules/entities.md +1 -1
  27. package/dist/agentic/guides/modules/events.md +1 -1
  28. package/dist/agentic/guides/modules/feature_toggles.md +1 -1
  29. package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
  30. package/dist/agentic/guides/modules/generators.md +1 -1
  31. package/dist/agentic/guides/modules/inbox_ops.md +1 -1
  32. package/dist/agentic/guides/modules/integrations.md +1 -1
  33. package/dist/agentic/guides/modules/messages.md +1 -1
  34. package/dist/agentic/guides/modules/notifications.md +1 -1
  35. package/dist/agentic/guides/modules/onboarding.md +1 -1
  36. package/dist/agentic/guides/modules/payment_gateways.md +1 -1
  37. package/dist/agentic/guides/modules/perspectives.md +1 -1
  38. package/dist/agentic/guides/modules/planner.md +1 -1
  39. package/dist/agentic/guides/modules/portal.md +1 -1
  40. package/dist/agentic/guides/modules/progress.md +1 -1
  41. package/dist/agentic/guides/modules/query_index.md +1 -1
  42. package/dist/agentic/guides/modules/record_locks.md +1 -1
  43. package/dist/agentic/guides/modules/resources.md +1 -1
  44. package/dist/agentic/guides/modules/sales.md +1 -1
  45. package/dist/agentic/guides/modules/scheduler.md +1 -1
  46. package/dist/agentic/guides/modules/search.md +1 -1
  47. package/dist/agentic/guides/modules/security.md +1 -1
  48. package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
  49. package/dist/agentic/guides/modules/sso.md +1 -1
  50. package/dist/agentic/guides/modules/staff.md +1 -1
  51. package/dist/agentic/guides/modules/storage_s3.md +1 -1
  52. package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
  53. package/dist/agentic/guides/modules/sync_excel.md +1 -1
  54. package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
  55. package/dist/agentic/guides/modules/translations.md +1 -1
  56. package/dist/agentic/guides/modules/webhooks.md +1 -1
  57. package/dist/agentic/guides/modules/wms.md +1 -1
  58. package/dist/agentic/guides/modules/workflows.md +1 -1
  59. package/dist/agentic/guides/upstream/manifest.json +1 -1
  60. package/dist/bin.js +20 -34
  61. package/dist/bin.js.map +2 -2
  62. package/dist/lib/cli-bootstrap-mode.js +32 -0
  63. package/dist/lib/cli-bootstrap-mode.js.map +7 -0
  64. package/dist/lib/dev-supervisor-manifest.js +105 -0
  65. package/dist/lib/dev-supervisor-manifest.js.map +7 -0
  66. package/dist/lib/generators/entity-ids.js +11 -24
  67. package/dist/lib/generators/entity-ids.js.map +2 -2
  68. package/dist/lib/generators/module-di.js +10 -23
  69. package/dist/lib/generators/module-di.js.map +2 -2
  70. package/dist/lib/generators/module-entities.js +10 -23
  71. package/dist/lib/generators/module-entities.js.map +2 -2
  72. package/dist/lib/generators/module-registry.js +394 -31
  73. package/dist/lib/generators/module-registry.js.map +2 -2
  74. package/dist/lib/generators/route-manifest-shards.js +170 -0
  75. package/dist/lib/generators/route-manifest-shards.js.map +7 -0
  76. package/dist/lib/queue-worker-supervisor.js.map +2 -2
  77. package/dist/lib/resolver.js +26 -3
  78. package/dist/lib/resolver.js.map +2 -2
  79. package/dist/lib/utils.js +15 -8
  80. package/dist/lib/utils.js.map +2 -2
  81. package/dist/mercato.js +9 -6
  82. package/dist/mercato.js.map +2 -2
  83. package/package.json +6 -6
  84. package/src/__tests__/mercato.test.ts +144 -0
  85. package/src/bin.ts +28 -42
  86. package/src/lib/__tests__/cli-bootstrap-mode.test.ts +22 -0
  87. package/src/lib/__tests__/content-stable-writes.test.ts +74 -0
  88. package/src/lib/__tests__/dev-supervisor-manifest.test.ts +131 -0
  89. package/src/lib/__tests__/queue-worker-supervisor.test.ts +33 -0
  90. package/src/lib/__tests__/resolver.enterprise.test.ts +50 -0
  91. package/src/lib/cli-bootstrap-mode.ts +31 -0
  92. package/src/lib/dev-supervisor-manifest.ts +142 -0
  93. package/src/lib/generators/__tests__/entity-ids.test.ts +32 -0
  94. package/src/lib/generators/__tests__/module-subset.test.ts +248 -0
  95. package/src/lib/generators/__tests__/output-snapshots.test.ts +3 -3
  96. package/src/lib/generators/__tests__/route-manifest-shards.test.ts +62 -0
  97. package/src/lib/generators/entity-ids.ts +10 -26
  98. package/src/lib/generators/module-di.ts +9 -25
  99. package/src/lib/generators/module-entities.ts +9 -25
  100. package/src/lib/generators/module-registry.ts +432 -29
  101. package/src/lib/generators/route-manifest-shards.ts +146 -0
  102. package/src/lib/queue-worker-supervisor.ts +7 -2
  103. package/src/lib/resolver.ts +31 -3
  104. package/src/lib/utils.ts +24 -16
  105. package/src/mercato.ts +15 -6
@@ -4,6 +4,12 @@ import { pathToFileURL } from 'node:url'
4
4
  import { VariableDeclarationKind, type WriterFunction } from 'ts-morph'
5
5
  import ts from 'typescript-js'
6
6
  import type { ModuleEntry, PackageResolver } from '../resolver'
7
+ import {
8
+ DEV_SUPERVISOR_MANIFEST_FILE,
9
+ DEV_SUPERVISOR_MANIFEST_VERSION,
10
+ type DevSupervisorSchedulerStartStatus,
11
+ type DevSupervisorWorkerDescriptor,
12
+ } from '../dev-supervisor-manifest'
7
13
  import {
8
14
  calculateStructureChecksum,
9
15
  toVar,
@@ -67,6 +73,12 @@ import {
67
73
  } from './scanner'
68
74
  import { loadGeneratorExtensions } from './extensions'
69
75
  import type { ModuleScanContext, StandaloneConfigOptions } from './extension'
76
+ import {
77
+ renderRouteManifestShardOutputs,
78
+ renderRouteMetadataOutput,
79
+ routeManifestShardFilePattern,
80
+ type RouteManifestShardEntry,
81
+ } from './route-manifest-shards'
70
82
 
71
83
  type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
72
84
 
@@ -128,6 +140,10 @@ type CommandLoaderGenerationEntry = {
128
140
  ids: string[]
129
141
  }
130
142
 
143
+ type WorkerGenerationEntry = DevSupervisorWorkerDescriptor & {
144
+ importPath: string
145
+ }
146
+
131
147
  type RuntimeApiMethodMetadata = {
132
148
  requireAuth?: boolean
133
149
  requireRoles?: string[]
@@ -144,6 +160,8 @@ type PageRouteGenerationResult = {
144
160
  eagerRoutes: string[]
145
161
  runtimeRoutes: string[]
146
162
  manifestRoutes: string[]
163
+ manifestShardRoutes: RouteManifestShardEntry[]
164
+ metadataRoutes: RouteManifestShardEntry[]
147
165
  routePatterns: string[]
148
166
  }
149
167
 
@@ -173,6 +191,8 @@ type ApiRouteGenerationResult = {
173
191
  eagerApis: string[]
174
192
  runtimeApis: string[]
175
193
  manifestApis: string[]
194
+ manifestShardApis: RouteManifestShardEntry[]
195
+ metadataApis: RouteManifestShardEntry[]
176
196
  }
177
197
 
178
198
  type SerializablePageMetadata = {
@@ -1631,12 +1651,16 @@ function discoverTranslations(roots: ModuleRoots): DiscoveredTranslation[] {
1631
1651
  const locales = new Set<string>()
1632
1652
  if (fs.existsSync(i18nCore)) {
1633
1653
  for (const entry of fs.readdirSync(i18nCore, { withFileTypes: true })) {
1634
- if (entry.isFile() && entry.name.endsWith('.json')) locales.add(entry.name.replace(/\.json$/, ''))
1654
+ if (entry.isFile() && /^[a-zA-Z0-9][a-zA-Z0-9_-]*\.json$/.test(entry.name)) {
1655
+ locales.add(entry.name.replace(/\.json$/, ''))
1656
+ }
1635
1657
  }
1636
1658
  }
1637
1659
  if (fs.existsSync(i18nApp)) {
1638
1660
  for (const entry of fs.readdirSync(i18nApp, { withFileTypes: true })) {
1639
- if (entry.isFile() && entry.name.endsWith('.json')) locales.add(entry.name.replace(/\.json$/, ''))
1661
+ if (entry.isFile() && /^[a-zA-Z0-9][a-zA-Z0-9_-]*\.json$/.test(entry.name)) {
1662
+ locales.add(entry.name.replace(/\.json$/, ''))
1663
+ }
1640
1664
  }
1641
1665
  }
1642
1666
  return Array.from(locales, (locale) => ({
@@ -1748,6 +1772,8 @@ async function processPageFiles(options: {
1748
1772
  const eagerRoutes: string[] = []
1749
1773
  const runtimeRoutes: string[] = []
1750
1774
  const manifestRoutes: string[] = []
1775
+ const manifestShardRoutes: RouteManifestShardEntry[] = []
1776
+ const metadataRoutes: RouteManifestShardEntry[] = []
1751
1777
  const routePatterns: string[] = []
1752
1778
 
1753
1779
  // Next-style page.* files
@@ -1819,7 +1845,19 @@ async function processPageFiles(options: {
1819
1845
  const manifestBaseProps = buildPageRouteManifestSpread(manifestMetaExpr, routePath)
1820
1846
  eagerRoutes.push(`{ ${baseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
1821
1847
  runtimeRoutes.push(`{ ${runtimeBaseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
1822
- manifestRoutes.push(`{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps}, load: async () => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod.default ?? mod) as any } }`)
1848
+ const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps} }`
1849
+ const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps}, load: async () => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod.default ?? mod) as any } }`
1850
+ manifestRoutes.push(manifestDeclaration)
1851
+ manifestShardRoutes.push({
1852
+ path: routePath,
1853
+ declaration: manifestDeclaration,
1854
+ imports: manifestImportStatement ? [manifestImportStatement] : [],
1855
+ })
1856
+ metadataRoutes.push({
1857
+ path: routePath,
1858
+ declaration: metadataDeclaration,
1859
+ imports: manifestImportStatement ? [manifestImportStatement] : [],
1860
+ })
1823
1861
  routePatterns.push(routePath)
1824
1862
  }
1825
1863
 
@@ -1898,7 +1936,19 @@ async function processPageFiles(options: {
1898
1936
  const manifestBaseProps = buildPageRouteManifestSpread(manifestMetaExpr, routePath)
1899
1937
  eagerRoutes.push(`{ ${baseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
1900
1938
  runtimeRoutes.push(`{ ${runtimeBaseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`)
1901
- manifestRoutes.push(`{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps}, load: async () => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod.default ?? mod) as any } }`)
1939
+ const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps} }`
1940
+ const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps}, load: async () => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod.default ?? mod) as any } }`
1941
+ manifestRoutes.push(manifestDeclaration)
1942
+ manifestShardRoutes.push({
1943
+ path: routePath,
1944
+ declaration: manifestDeclaration,
1945
+ imports: manifestImportStatement ? [manifestImportStatement] : [],
1946
+ })
1947
+ metadataRoutes.push({
1948
+ path: routePath,
1949
+ declaration: metadataDeclaration,
1950
+ imports: manifestImportStatement ? [manifestImportStatement] : [],
1951
+ })
1902
1952
  routePatterns.push(routePath)
1903
1953
  }
1904
1954
 
@@ -1906,6 +1956,8 @@ async function processPageFiles(options: {
1906
1956
  eagerRoutes,
1907
1957
  runtimeRoutes,
1908
1958
  manifestRoutes,
1959
+ manifestShardRoutes,
1960
+ metadataRoutes,
1909
1961
  routePatterns,
1910
1962
  }
1911
1963
  }
@@ -1922,12 +1974,14 @@ async function processApiRoutes(options: {
1922
1974
  const apiApp = path.join(roots.appBase, 'api')
1923
1975
  const apiPkg = path.join(roots.pkgBase, 'api')
1924
1976
  if (!fs.existsSync(apiApp) && !fs.existsSync(apiPkg)) {
1925
- return { eagerApis: [], runtimeApis: [], manifestApis: [] }
1977
+ return { eagerApis: [], runtimeApis: [], manifestApis: [], manifestShardApis: [], metadataApis: [] }
1926
1978
  }
1927
1979
 
1928
1980
  const eagerApis: string[] = []
1929
1981
  const runtimeApis: string[] = []
1930
1982
  const manifestApis: string[] = []
1983
+ const manifestShardApis: RouteManifestShardEntry[] = []
1984
+ const metadataApis: RouteManifestShardEntry[] = []
1931
1985
 
1932
1986
  // route.* aggregations
1933
1987
  const routeFiles = scanModuleDir(roots, SCAN_CONFIGS.apiRoutes)
@@ -1958,7 +2012,11 @@ async function processApiRoutes(options: {
1958
2012
  eagerImports.push(buildImportStatement(`* as ${importName}`, importPath))
1959
2013
  eagerApis.push(`{ path: ((${importName} as any).metadata?.path ?? ${toLiteral(routePath)}), metadata: (${importName} as any).metadata, handlers: ${importName} as any${docsPart} }`)
1960
2014
  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(', ')} } }`)
1961
- manifestApis.push(`{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('route-file')}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((method) => toLiteral(method)).join(', ')}], load: async () => ${buildDynamicImportExpression(importPath)} }`)
2015
+ const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('route-file')}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((method) => toLiteral(method)).join(', ')}] }`
2016
+ const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('route-file')}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((method) => toLiteral(method)).join(', ')}], load: async () => ${buildDynamicImportExpression(importPath)} }`
2017
+ manifestApis.push(manifestDeclaration)
2018
+ manifestShardApis.push({ path: resolvedPath, declaration: manifestDeclaration })
2019
+ metadataApis.push({ path: resolvedPath, declaration: metadataDeclaration })
1962
2020
  }
1963
2021
 
1964
2022
  // Single files (plain scripts, not route.*, not tests, skip method dirs)
@@ -1990,7 +2048,11 @@ async function processApiRoutes(options: {
1990
2048
  eagerImports.push(buildImportStatement(`* as ${importName}`, importPath))
1991
2049
  eagerApis.push(`{ path: ((${importName} as any).metadata?.path ?? ${toLiteral(routePath)}), metadata: (${importName} as any).metadata, handlers: ${importName} as any${docsPart} }`)
1992
2050
  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(', ')} } }`)
1993
- manifestApis.push(`{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('route-file')}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((entryMethod) => toLiteral(entryMethod)).join(', ')}], load: async () => ${buildDynamicImportExpression(importPath)} }`)
2051
+ const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('route-file')}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((entryMethod) => toLiteral(entryMethod)).join(', ')}] }`
2052
+ const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('route-file')}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((entryMethod) => toLiteral(entryMethod)).join(', ')}], load: async () => ${buildDynamicImportExpression(importPath)} }`
2053
+ manifestApis.push(manifestDeclaration)
2054
+ manifestShardApis.push({ path: resolvedPath, declaration: manifestDeclaration })
2055
+ metadataApis.push({ path: resolvedPath, declaration: metadataDeclaration })
1994
2056
  }
1995
2057
 
1996
2058
  // Legacy per-method
@@ -2032,7 +2094,11 @@ async function processApiRoutes(options: {
2032
2094
  eagerImports.push(buildImportStatement(`${importName}, * as ${metaName}`, importPath))
2033
2095
  eagerApis.push(`{ method: ${toLiteral(method)}, path: (${metaName}.metadata?.path ?? ${toLiteral(routePath)}), handler: ${importName}, metadata: ${metaName}.metadata${docsPart} }`)
2034
2096
  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} }`)
2035
- manifestApis.push(`{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('legacy')}, method: ${toLiteral(method)}, path: ${toLiteral(resolvedPath)}, methods: [${toLiteral(method)}], load: async () => ${buildDynamicImportExpression(importPath)} }`)
2097
+ const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('legacy')}, method: ${toLiteral(method)}, path: ${toLiteral(resolvedPath)}, methods: [${toLiteral(method)}] }`
2098
+ const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral('legacy')}, method: ${toLiteral(method)}, path: ${toLiteral(resolvedPath)}, methods: [${toLiteral(method)}], load: async () => ${buildDynamicImportExpression(importPath)} }`
2099
+ manifestApis.push(manifestDeclaration)
2100
+ manifestShardApis.push({ path: resolvedPath, declaration: manifestDeclaration })
2101
+ metadataApis.push({ path: resolvedPath, declaration: metadataDeclaration })
2036
2102
  }
2037
2103
  }
2038
2104
 
@@ -2040,6 +2106,8 @@ async function processApiRoutes(options: {
2040
2106
  eagerApis,
2041
2107
  runtimeApis,
2042
2108
  manifestApis,
2109
+ manifestShardApis,
2110
+ metadataApis,
2043
2111
  }
2044
2112
  }
2045
2113
 
@@ -2751,6 +2819,24 @@ function processWorkersAst(discovered: DiscoveredWorker[]): WriterFunction[] {
2751
2819
  return workers
2752
2820
  }
2753
2821
 
2822
+ function collectWorkerGenerationEntries(
2823
+ discovered: DiscoveredWorker[],
2824
+ moduleId: string,
2825
+ ): WorkerGenerationEntry[] {
2826
+ const workers: WorkerGenerationEntry[] = []
2827
+ for (const { id, importPath, metadata } of discovered) {
2828
+ if (!metadata.queue) continue
2829
+ workers.push({
2830
+ id: metadata.id ?? id,
2831
+ moduleId,
2832
+ queue: metadata.queue,
2833
+ concurrency: metadata.concurrency ?? 1,
2834
+ importPath,
2835
+ })
2836
+ }
2837
+ return workers
2838
+ }
2839
+
2754
2840
  function processTranslationsAst(options: {
2755
2841
  discovered: DiscoveredTranslation[]
2756
2842
  modId: string
@@ -2758,8 +2844,9 @@ function processTranslationsAst(options: {
2758
2844
  pkgImportBase: string
2759
2845
  imports: string[]
2760
2846
  extraImports?: string[]
2847
+ localeOutputs?: Map<string, { imports: string[]; value: WriterFunction }>
2761
2848
  }): GeneratedObjectEntry[] {
2762
- const { discovered, modId, appImportBase, pkgImportBase, imports, extraImports } = options
2849
+ const { discovered, modId, appImportBase, pkgImportBase, imports, extraImports, localeOutputs } = options
2763
2850
 
2764
2851
  const translations: GeneratedObjectEntry[] = []
2765
2852
 
@@ -2768,45 +2855,236 @@ function processTranslationsAst(options: {
2768
2855
  if (coreHas && appHas) {
2769
2856
  const coreName = `T_${toVar(modId)}_${toVar(locale)}_C`
2770
2857
  const appName = `T_${toVar(modId)}_${toVar(locale)}_A`
2771
- imports.push(buildImportStatement(coreName, `${pkgImportBase}/i18n/${locale}.json`))
2772
- imports.push(buildImportStatement(appName, `${appImportBase}/i18n/${locale}.json`))
2773
- extraImports?.push(buildImportStatement(coreName, `${pkgImportBase}/i18n/${locale}.json`))
2774
- extraImports?.push(buildImportStatement(appName, `${appImportBase}/i18n/${locale}.json`))
2858
+ const localeImports = [
2859
+ buildImportStatement(coreName, `${pkgImportBase}/i18n/${locale}.json`),
2860
+ buildImportStatement(appName, `${appImportBase}/i18n/${locale}.json`),
2861
+ ]
2862
+ imports.push(...localeImports)
2863
+ extraImports?.push(...localeImports)
2864
+ const value = objectLiteral([
2865
+ { kind: 'spread', value: asExpression(identifier(coreName), 'unknown as Record<string, string>') },
2866
+ { kind: 'spread', value: asExpression(identifier(appName), 'unknown as Record<string, string>') },
2867
+ ])
2775
2868
  translations.push({
2776
2869
  name: JSON.stringify(locale),
2777
- value: objectLiteral([
2778
- { kind: 'spread', value: asExpression(identifier(coreName), 'unknown as Record<string, string>') },
2779
- { kind: 'spread', value: asExpression(identifier(appName), 'unknown as Record<string, string>') },
2780
- ]),
2870
+ value,
2781
2871
  })
2872
+ localeOutputs?.set(locale, { imports: localeImports, value })
2782
2873
  continue
2783
2874
  }
2784
2875
 
2785
2876
  if (appHas) {
2786
2877
  const appName = `T_${toVar(modId)}_${toVar(locale)}_A`
2787
- imports.push(buildImportStatement(appName, `${appImportBase}/i18n/${locale}.json`))
2788
- extraImports?.push(buildImportStatement(appName, `${appImportBase}/i18n/${locale}.json`))
2878
+ const localeImports = [buildImportStatement(appName, `${appImportBase}/i18n/${locale}.json`)]
2879
+ imports.push(...localeImports)
2880
+ extraImports?.push(...localeImports)
2881
+ const value = asExpression(identifier(appName), 'unknown as Record<string, string>')
2789
2882
  translations.push({
2790
2883
  name: JSON.stringify(locale),
2791
- value: asExpression(identifier(appName), 'unknown as Record<string, string>'),
2884
+ value,
2792
2885
  })
2886
+ localeOutputs?.set(locale, { imports: localeImports, value })
2793
2887
  continue
2794
2888
  }
2795
2889
 
2796
2890
  if (coreHas) {
2797
2891
  const coreName = `T_${toVar(modId)}_${toVar(locale)}_C`
2798
- imports.push(buildImportStatement(coreName, `${pkgImportBase}/i18n/${locale}.json`))
2799
- extraImports?.push(buildImportStatement(coreName, `${pkgImportBase}/i18n/${locale}.json`))
2892
+ const localeImports = [buildImportStatement(coreName, `${pkgImportBase}/i18n/${locale}.json`)]
2893
+ imports.push(...localeImports)
2894
+ extraImports?.push(...localeImports)
2895
+ const value = asExpression(identifier(coreName), 'unknown as Record<string, string>')
2800
2896
  translations.push({
2801
2897
  name: JSON.stringify(locale),
2802
- value: asExpression(identifier(coreName), 'unknown as Record<string, string>'),
2898
+ value,
2803
2899
  })
2900
+ localeOutputs?.set(locale, { imports: localeImports, value })
2804
2901
  }
2805
2902
  }
2806
2903
 
2807
2904
  return translations
2808
2905
  }
2809
2906
 
2907
+ function sourceDeclaresCliCommand(filePath: string, commandName: string): boolean {
2908
+ let sourceText: string
2909
+ try {
2910
+ sourceText = fs.readFileSync(filePath, 'utf8')
2911
+ } catch {
2912
+ return false
2913
+ }
2914
+ const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true)
2915
+ const declarations = new Map<string, ts.Expression>()
2916
+ for (const statement of sourceFile.statements) {
2917
+ if (!ts.isVariableStatement(statement)) continue
2918
+ for (const declaration of statement.declarationList.declarations) {
2919
+ if (ts.isIdentifier(declaration.name) && declaration.initializer) {
2920
+ declarations.set(declaration.name.text, declaration.initializer)
2921
+ }
2922
+ }
2923
+ }
2924
+
2925
+ const expressionDeclaresCommand = (expression: ts.Expression, seen = new Set<string>()): boolean => {
2926
+ if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression) || ts.isSatisfiesExpression(expression)) {
2927
+ return expressionDeclaresCommand(expression.expression, seen)
2928
+ }
2929
+ if (ts.isIdentifier(expression)) {
2930
+ if (seen.has(expression.text)) return false
2931
+ const initializer = declarations.get(expression.text)
2932
+ if (!initializer) return false
2933
+ const nextSeen = new Set(seen)
2934
+ nextSeen.add(expression.text)
2935
+ return expressionDeclaresCommand(initializer, nextSeen)
2936
+ }
2937
+ if (ts.isArrayLiteralExpression(expression)) {
2938
+ return expression.elements.some((element) => (
2939
+ ts.isSpreadElement(element)
2940
+ ? expressionDeclaresCommand(element.expression, seen)
2941
+ : expressionDeclaresCommand(element, seen)
2942
+ ))
2943
+ }
2944
+ if (!ts.isObjectLiteralExpression(expression)) return false
2945
+ return expression.properties.some((property) => {
2946
+ if (!ts.isPropertyAssignment(property)) return false
2947
+ const propertyName = ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)
2948
+ ? property.name.text
2949
+ : null
2950
+ return propertyName === 'command'
2951
+ && (ts.isStringLiteralLike(property.initializer) || ts.isNoSubstitutionTemplateLiteral(property.initializer))
2952
+ && property.initializer.text === commandName
2953
+ })
2954
+ }
2955
+
2956
+ return sourceFile.statements.some((statement) => (
2957
+ ts.isExportAssignment(statement)
2958
+ && !statement.isExportEquals
2959
+ && expressionDeclaresCommand(statement.expression)
2960
+ ))
2961
+ }
2962
+
2963
+ function appDeclaresProgrammaticDevSupervisorOverrides(appDir: string): boolean {
2964
+ const srcDir = path.join(appDir, 'src')
2965
+ const pending = [srcDir]
2966
+ while (pending.length > 0) {
2967
+ const current = pending.pop()!
2968
+ let entries: fs.Dirent[]
2969
+ try {
2970
+ entries = fs.readdirSync(current, { withFileTypes: true })
2971
+ } catch {
2972
+ continue
2973
+ }
2974
+ for (const entry of entries) {
2975
+ const entryPath = path.join(current, entry.name)
2976
+ if (entry.isDirectory()) {
2977
+ if (entry.name !== '__tests__' && entry.name !== 'node_modules') pending.push(entryPath)
2978
+ continue
2979
+ }
2980
+ if (!/\.[cm]?tsx?$/.test(entry.name) || /(?:^|\.)test\.[cm]?tsx?$/.test(entry.name)) continue
2981
+
2982
+ let sourceText: string
2983
+ try {
2984
+ sourceText = fs.readFileSync(entryPath, 'utf8')
2985
+ } catch {
2986
+ continue
2987
+ }
2988
+ const sourceFile = ts.createSourceFile(entryPath, sourceText, ts.ScriptTarget.Latest, true)
2989
+ const directImports = new Set<string>()
2990
+ const namespaceImports = new Set<string>()
2991
+ for (const statement of sourceFile.statements) {
2992
+ if (
2993
+ !ts.isImportDeclaration(statement)
2994
+ || !ts.isStringLiteralLike(statement.moduleSpecifier)
2995
+ || statement.moduleSpecifier.text !== '@open-mercato/shared/modules/overrides'
2996
+ ) continue
2997
+ const bindings = statement.importClause?.namedBindings
2998
+ if (bindings && ts.isNamespaceImport(bindings)) namespaceImports.add(bindings.name.text)
2999
+ if (bindings && ts.isNamedImports(bindings)) {
3000
+ for (const element of bindings.elements) {
3001
+ const importedName = element.propertyName?.text ?? element.name.text
3002
+ if (importedName === 'applyWorkerOverrides' || importedName === 'applyCliOverrides') {
3003
+ directImports.add(element.name.text)
3004
+ }
3005
+ }
3006
+ }
3007
+ }
3008
+ let found = false
3009
+ const visit = (node: ts.Node): void => {
3010
+ if (found) return
3011
+ if (ts.isCallExpression(node)) {
3012
+ if (ts.isIdentifier(node.expression) && directImports.has(node.expression.text)) {
3013
+ found = true
3014
+ return
3015
+ }
3016
+ if (
3017
+ ts.isPropertyAccessExpression(node.expression)
3018
+ && ts.isIdentifier(node.expression.expression)
3019
+ && namespaceImports.has(node.expression.expression.text)
3020
+ && ['applyWorkerOverrides', 'applyCliOverrides'].includes(node.expression.name.text)
3021
+ ) {
3022
+ found = true
3023
+ return
3024
+ }
3025
+ }
3026
+ ts.forEachChild(node, visit)
3027
+ }
3028
+ visit(sourceFile)
3029
+ if (found) return true
3030
+ }
3031
+ }
3032
+ return false
3033
+ }
3034
+
3035
+ function renderAstI18nLocaleRegistryFile(options: {
3036
+ fileName: string
3037
+ imports: string[]
3038
+ moduleEntries: WriterFunction[]
3039
+ }): string {
3040
+ const sourceFile = createGeneratedSourceFile(options.fileName)
3041
+ addAutoGeneratedComment(sourceFile, 'registry (locale-specific i18n version)')
3042
+ addImportSpec(sourceFile, {
3043
+ moduleSpecifier: '@open-mercato/shared/modules/registry',
3044
+ namedImports: [{ name: 'Module', isTypeOnly: true }],
3045
+ })
3046
+ addImportStatements(sourceFile, options.imports)
3047
+ sourceFile.addVariableStatement({
3048
+ declarationKind: VariableDeclarationKind.Const,
3049
+ isExported: true,
3050
+ declarations: [{
3051
+ name: 'modules',
3052
+ type: 'Module[]',
3053
+ initializer: arrayLiteral(options.moduleEntries, writeValue),
3054
+ }],
3055
+ })
3056
+ sourceFile.addExportAssignment({
3057
+ isExportEquals: false,
3058
+ expression: 'modules',
3059
+ })
3060
+ return getSourceText(sourceFile)
3061
+ }
3062
+
3063
+ function buildI18nLocaleShardFileName(locale: string): string {
3064
+ const safeLocale = locale.replace(/[^a-zA-Z0-9_-]/g, '_')
3065
+ return `modules.i18n.${safeLocale}.generated.ts`
3066
+ }
3067
+
3068
+ function renderI18nLocaleLoadersFile(locales: string[]): string {
3069
+ const cases = locales.map((locale) => {
3070
+ const moduleSpecifier = `./${buildI18nLocaleShardFileName(locale).replace(/\.ts$/, '')}`
3071
+ return ` case ${JSON.stringify(locale)}:\n return import(${JSON.stringify(moduleSpecifier)}).then((entry) => entry.modules)`
3072
+ }).join('\n')
3073
+
3074
+ return `// AUTO-GENERATED by mercato generate registry (locale-specific i18n loaders)\nimport type { Module } from '@open-mercato/shared/modules/registry'\n\nexport async function loadI18nModules(locale: string): Promise<Module[]> {\n switch (locale) {\n${cases ? `${cases}\n` : ''} default:\n return []\n }\n}\n`
3075
+ }
3076
+
3077
+ function removeStaleI18nLocaleShards(outputDir: string, expectedFileNames: Set<string>): void {
3078
+ if (!fs.existsSync(outputDir)) return
3079
+ for (const entry of fs.readdirSync(outputDir, { withFileTypes: true })) {
3080
+ if (!entry.isFile()) continue
3081
+ if (!/^modules\.i18n\..+\.generated\.(?:ts|checksum)$/.test(entry.name)) continue
3082
+ if (entry.name.startsWith('modules.i18n.loaders.generated.')) continue
3083
+ if (expectedFileNames.has(entry.name)) continue
3084
+ fs.unlinkSync(path.join(outputDir, entry.name))
3085
+ }
3086
+ }
3087
+
2810
3088
  function resolveConventionFile(
2811
3089
  resolved: ResolvedFile | null,
2812
3090
  prefix: string,
@@ -2887,6 +3165,10 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
2887
3165
  const frontendRouteManifestDecls: string[] = []
2888
3166
  const backendRouteManifestDecls: string[] = []
2889
3167
  const apiRouteManifestDecls: string[] = []
3168
+ const backendRouteShardEntries: RouteManifestShardEntry[] = []
3169
+ const backendRouteMetadataEntries: RouteManifestShardEntry[] = []
3170
+ const apiRouteShardEntries: RouteManifestShardEntry[] = []
3171
+ const apiRouteMetadataEntries: RouteManifestShardEntry[] = []
2890
3172
  // Mutable ref so extracted helper functions can increment the shared counter
2891
3173
  const importIdRef = { value: 0 }
2892
3174
  const requiresByModule = new Map<string, string[]>()
@@ -3091,6 +3373,8 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
3091
3373
  backendRoutes.push(...generatedBackendRoutes.eagerRoutes)
3092
3374
  runtimeBackendRoutes.push(...generatedBackendRoutes.runtimeRoutes)
3093
3375
  backendRouteManifestDecls.push(...generatedBackendRoutes.manifestRoutes)
3376
+ backendRouteShardEntries.push(...generatedBackendRoutes.manifestShardRoutes)
3377
+ backendRouteMetadataEntries.push(...generatedBackendRoutes.metadataRoutes)
3094
3378
  for (const pattern of generatedBackendRoutes.routePatterns) {
3095
3379
  assertUniqueBackendRoutePattern(seenBackendRoutePatterns, modId, pattern)
3096
3380
  }
@@ -3111,6 +3395,8 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
3111
3395
  apis.push(...generatedApis.eagerApis)
3112
3396
  runtimeApis.push(...generatedApis.runtimeApis)
3113
3397
  apiRouteManifestDecls.push(...generatedApis.manifestApis)
3398
+ apiRouteShardEntries.push(...generatedApis.manifestShardApis)
3399
+ apiRouteMetadataEntries.push(...generatedApis.metadataApis)
3114
3400
  }
3115
3401
 
3116
3402
  // 15. CLI
@@ -3330,6 +3616,12 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
3330
3616
  exportName: 'apiRoutes',
3331
3617
  entries: apiRouteManifestDecls,
3332
3618
  })
3619
+ const routeManifestOutputs = [
3620
+ ...renderRouteManifestShardOutputs('backend', backendRouteShardEntries),
3621
+ ...renderRouteManifestShardOutputs('api', apiRouteShardEntries),
3622
+ renderRouteMetadataOutput('backend', backendRouteMetadataEntries),
3623
+ renderRouteMetadataOutput('api', apiRouteMetadataEntries),
3624
+ ]
3333
3625
  const legacySubscribersOutput = renderAstLegacyAliasFile({
3334
3626
  fileName: 'subscribers.generated.ts',
3335
3627
  exportName: 'moduleSubscribers',
@@ -3377,6 +3669,29 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
3377
3669
  writeGeneratedFile({ outFile: frontendRoutesOutFile, checksumFile: frontendRoutesChecksumFile, content: frontendRoutesOutput, structureChecksum, result, quiet })
3378
3670
  writeGeneratedFile({ outFile: backendRoutesOutFile, checksumFile: backendRoutesChecksumFile, content: backendRoutesOutput, structureChecksum, result, quiet })
3379
3671
  writeGeneratedFile({ outFile: apiRoutesOutFile, checksumFile: apiRoutesChecksumFile, content: apiRoutesOutput, structureChecksum, result, quiet })
3672
+ const expectedRouteManifestFiles = new Set(routeManifestOutputs.flatMap(({ fileName }) => [
3673
+ fileName,
3674
+ fileName.replace(/\.ts$/, '.checksum'),
3675
+ ]))
3676
+ if (fs.existsSync(outputDir)) {
3677
+ for (const entry of fs.readdirSync(outputDir, { withFileTypes: true })) {
3678
+ if (!entry.isFile() || !routeManifestShardFilePattern.test(entry.name)) continue
3679
+ if (expectedRouteManifestFiles.has(entry.name)) continue
3680
+ const stalePath = path.join(outputDir, entry.name)
3681
+ fs.unlinkSync(stalePath)
3682
+ result.filesWritten.push(stalePath)
3683
+ }
3684
+ }
3685
+ for (const routeOutput of routeManifestOutputs) {
3686
+ writeGeneratedFile({
3687
+ outFile: path.join(outputDir, routeOutput.fileName),
3688
+ checksumFile: path.join(outputDir, routeOutput.fileName.replace(/\.ts$/, '.checksum')),
3689
+ content: routeOutput.content,
3690
+ structureChecksum,
3691
+ result,
3692
+ quiet,
3693
+ })
3694
+ }
3380
3695
  writeGeneratedFile({ outFile: legacySubscribersOutFile, checksumFile: legacySubscribersChecksumFile, content: legacySubscribersOutput, structureChecksum, result, quiet })
3381
3696
  const commandLoadersOutput = renderCommandLoadersFile(commandLoaderEntries)
3382
3697
  writeGeneratedFile({ outFile: commandLoadersOutFile, checksumFile: commandLoadersChecksumFile, content: commandLoadersOutput, structureChecksum, result, quiet })
@@ -3413,14 +3728,14 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
3413
3728
  {
3414
3729
  const bootstrapPlugins = [...pluginRegistry.values()].filter((p) => p.bootstrapRegistration)
3415
3730
  const allEntryImports: string[] = [
3416
- buildImportStatement(`{ backendRoutes }`, `./backend-routes.generated`),
3731
+ buildImportStatement(`{ backendRouteFacades }`, `./backend-route-shards.generated`),
3417
3732
  buildImportStatement(`{ frontendRoutes }`, `./frontend-routes.generated`),
3418
3733
  ]
3419
3734
  const allRegImports: string[] = [
3420
3735
  `import { registerBackendRouteManifests, registerFrontendRouteManifests } from '@open-mercato/shared/modules/registry'`,
3421
3736
  ]
3422
3737
  const allCalls: string[] = [
3423
- `registerBackendRouteManifests(backendRoutes)`,
3738
+ `registerBackendRouteManifests(backendRouteFacades)`,
3424
3739
  `registerFrontendRouteManifests(frontendRoutes)`,
3425
3740
  ]
3426
3741
  for (const plugin of bootstrapPlugins) {
@@ -3457,6 +3772,8 @@ async function generateModuleRegistryAppFromDiscovery(options: ModuleRegistryRen
3457
3772
  const bootstrapChecksumFile = path.join(outputDir, 'modules.bootstrap.generated.checksum')
3458
3773
  const i18nOutFile = path.join(outputDir, 'modules.i18n.generated.ts')
3459
3774
  const i18nChecksumFile = path.join(outputDir, 'modules.i18n.generated.checksum')
3775
+ const i18nLoadersOutFile = path.join(outputDir, 'modules.i18n.loaders.generated.ts')
3776
+ const i18nLoadersChecksumFile = path.join(outputDir, 'modules.i18n.loaders.generated.checksum')
3460
3777
  const legacyOutFile = path.join(outputDir, 'bootstrap-modules.generated.ts')
3461
3778
  const legacyChecksumFile = path.join(outputDir, 'bootstrap-modules.generated.checksum')
3462
3779
  const enabledIdsOutFile = path.join(outputDir, 'enabled-module-ids.generated.ts')
@@ -3471,6 +3788,7 @@ async function generateModuleRegistryAppFromDiscovery(options: ModuleRegistryRen
3471
3788
  const moduleDecls: WriterFunction[] = []
3472
3789
  const bootstrapModuleDecls: WriterFunction[] = []
3473
3790
  const i18nModuleDecls: WriterFunction[] = []
3791
+ const i18nLocaleShards = new Map<string, { imports: string[]; moduleDecls: WriterFunction[] }>()
3474
3792
  const importIdRef = { value: 0 }
3475
3793
  const requiresByModule = new Map<string, string[]>()
3476
3794
  const commandLoaderEntries: CommandLoaderGenerationEntry[] = []
@@ -3484,6 +3802,7 @@ async function generateModuleRegistryAppFromDiscovery(options: ModuleRegistryRen
3484
3802
  const frontendRoutes: WriterFunction[] = []
3485
3803
  const backendRoutes: WriterFunction[] = []
3486
3804
  const translations: GeneratedObjectEntry[] = []
3805
+ const localeTranslations = new Map<string, { imports: string[]; value: WriterFunction }>()
3487
3806
  const subscribers: WriterFunction[] = []
3488
3807
  const workers: WriterFunction[] = []
3489
3808
  let infoImportName: string | null = null
@@ -3612,9 +3931,24 @@ async function generateModuleRegistryAppFromDiscovery(options: ModuleRegistryRen
3612
3931
  pkgImportBase: imps.pkgBase,
3613
3932
  imports,
3614
3933
  extraImports: translationImports,
3934
+ localeOutputs: localeTranslations,
3615
3935
  }))
3616
- bootstrapImports.push(...translationImports)
3617
3936
  i18nImports.push(...translationImports)
3937
+ for (const [locale, localeTranslation] of localeTranslations) {
3938
+ const shard = i18nLocaleShards.get(locale) ?? { imports: [], moduleDecls: [] }
3939
+ shard.imports.push(...localeTranslation.imports)
3940
+ shard.moduleDecls.push(objectLiteral([
3941
+ { name: 'id', value: modId },
3942
+ {
3943
+ name: 'translations',
3944
+ value: objectLiteral([{
3945
+ name: JSON.stringify(locale),
3946
+ value: localeTranslation.value,
3947
+ }]),
3948
+ },
3949
+ ]))
3950
+ i18nLocaleShards.set(locale, shard)
3951
+ }
3618
3952
 
3619
3953
  subscribers.push(...processSubscribersAst(await discovered.getSubscribers()))
3620
3954
 
@@ -3730,7 +4064,9 @@ async function generateModuleRegistryAppFromDiscovery(options: ModuleRegistryRen
3730
4064
  }
3731
4065
 
3732
4066
  moduleDecls.push(objectLiteral(routeAwareModuleEntries))
3733
- bootstrapModuleDecls.push(objectLiteral(moduleEntries))
4067
+ bootstrapModuleDecls.push(objectLiteral(moduleEntries.filter(
4068
+ (entry) => entry.kind === 'spread' || entry.name !== 'translations',
4069
+ )))
3734
4070
  i18nModuleDecls.push(objectLiteral(i18nModuleEntries))
3735
4071
  }
3736
4072
 
@@ -3787,6 +4123,41 @@ async function generateModuleRegistryAppFromDiscovery(options: ModuleRegistryRen
3787
4123
  writeGeneratedFile({ outFile, checksumFile, content: output, structureChecksum, result, quiet })
3788
4124
  writeGeneratedFile({ outFile: bootstrapOutFile, checksumFile: bootstrapChecksumFile, content: bootstrapOutput, structureChecksum, result, quiet })
3789
4125
  writeGeneratedFile({ outFile: i18nOutFile, checksumFile: i18nChecksumFile, content: i18nOutput, structureChecksum, result, quiet })
4126
+ const i18nLocales = [...i18nLocaleShards.keys()].sort((a, b) => a.localeCompare(b))
4127
+ const shardFileNames = new Set<string>()
4128
+ const expectedShardArtifacts = new Set<string>()
4129
+ for (const locale of i18nLocales) {
4130
+ const fileName = buildI18nLocaleShardFileName(locale)
4131
+ if (shardFileNames.has(fileName)) {
4132
+ throw new Error(`Locale names produce the same generated i18n shard file: ${locale}`)
4133
+ }
4134
+ shardFileNames.add(fileName)
4135
+ expectedShardArtifacts.add(fileName)
4136
+ expectedShardArtifacts.add(fileName.replace(/\.ts$/, '.checksum'))
4137
+ const shard = i18nLocaleShards.get(locale)!
4138
+ const content = renderAstI18nLocaleRegistryFile({
4139
+ fileName,
4140
+ imports: shard.imports,
4141
+ moduleEntries: shard.moduleDecls,
4142
+ })
4143
+ writeGeneratedFile({
4144
+ outFile: path.join(outputDir, fileName),
4145
+ checksumFile: path.join(outputDir, fileName.replace(/\.ts$/, '.checksum')),
4146
+ content,
4147
+ structureChecksum,
4148
+ result,
4149
+ quiet,
4150
+ })
4151
+ }
4152
+ removeStaleI18nLocaleShards(outputDir, expectedShardArtifacts)
4153
+ writeGeneratedFile({
4154
+ outFile: i18nLoadersOutFile,
4155
+ checksumFile: i18nLoadersChecksumFile,
4156
+ content: renderI18nLocaleLoadersFile(i18nLocales),
4157
+ structureChecksum,
4158
+ result,
4159
+ quiet,
4160
+ })
3790
4161
  writeGeneratedFile({ outFile: legacyOutFile, checksumFile: legacyChecksumFile, content: legacyOutput, structureChecksum, result, quiet })
3791
4162
 
3792
4163
  const enabledIdsOutput = renderEnabledModuleIdsFile(enabled.map((entry) => entry.id))
@@ -3818,6 +4189,8 @@ async function generateModuleRegistryCliFromDiscovery(options: ModuleRegistryRen
3818
4189
  const legacyChecksumFile = path.join(outputDir, 'cli-modules.generated.checksum')
3819
4190
  const commandLoadersOutFile = path.join(outputDir, 'command-loaders.generated.ts')
3820
4191
  const commandLoadersChecksumFile = path.join(outputDir, 'command-loaders.generated.checksum')
4192
+ const devSupervisorOutFile = path.join(outputDir, DEV_SUPERVISOR_MANIFEST_FILE)
4193
+ const devSupervisorChecksumFile = path.join(outputDir, 'dev-supervisor.generated.checksum')
3821
4194
 
3822
4195
  const enabled = discovery.enabled
3823
4196
  const imports: string[] = []
@@ -3826,9 +4199,13 @@ async function generateModuleRegistryCliFromDiscovery(options: ModuleRegistryRen
3826
4199
  const importIdRef = { value: 0 }
3827
4200
  const requiresByModule = new Map<string, string[]>()
3828
4201
  const commandLoaderEntries: CommandLoaderGenerationEntry[] = []
4202
+ const devSupervisorWorkers: DevSupervisorWorkerDescriptor[] = []
4203
+ let schedulerStartStatus: DevSupervisorSchedulerStartStatus = 'missing-module'
4204
+ let requiresFullBootstrap = enabled.some((entry) => entry.devSupervisorRequiresFullBootstrap === true)
4205
+ || appDeclaresProgrammaticDevSupervisorOverrides(resolver.getAppDir())
3829
4206
 
3830
4207
  for (const discovered of discovery.modules) {
3831
- const { modId, roots, imps, appImportBase } = discovered
4208
+ const { modId, imps, appImportBase } = discovered
3832
4209
  commandLoaderEntries.push(...discovered.commandLoaderEntries)
3833
4210
 
3834
4211
  let cliImportName: string | null = null
@@ -3920,11 +4297,20 @@ async function generateModuleRegistryCliFromDiscovery(options: ModuleRegistryRen
3920
4297
 
3921
4298
  // CLI
3922
4299
  {
4300
+ if (modId === 'scheduler') schedulerStartStatus = 'missing-cli'
3923
4301
  const cliResolved = discovered.resolve('cli.ts')
3924
4302
  if (cliResolved) {
3925
4303
  const importName = `CLI_${toVar(modId)}`
3926
4304
  imports.push(buildImportStatement(importName, sanitizeGeneratedModuleSpecifier(cliResolved.importPath)))
3927
4305
  cliImportName = importName
4306
+ if (modId === 'scheduler') {
4307
+ if (sourceDeclaresCliCommand(cliResolved.absolutePath, 'start')) {
4308
+ schedulerStartStatus = 'ok'
4309
+ } else {
4310
+ schedulerStartStatus = 'missing-command'
4311
+ requiresFullBootstrap = true
4312
+ }
4313
+ }
3928
4314
  }
3929
4315
  }
3930
4316
 
@@ -3941,7 +4327,10 @@ async function generateModuleRegistryCliFromDiscovery(options: ModuleRegistryRen
3941
4327
  subscribers.push(...processSubscribersAst(await discovered.getSubscribers()))
3942
4328
 
3943
4329
  // Workers
3944
- workers.push(...processWorkersAst(await discovered.getWorkers()))
4330
+ const discoveredWorkers = await discovered.getWorkers()
4331
+ const workerGenerationEntries = collectWorkerGenerationEntries(discoveredWorkers, modId)
4332
+ workers.push(...processWorkersAst(discoveredWorkers))
4333
+ devSupervisorWorkers.push(...workerGenerationEntries.map(({ importPath: _importPath, ...worker }) => worker))
3945
4334
 
3946
4335
  // Dashboard widgets
3947
4336
  {
@@ -4106,6 +4495,20 @@ async function generateModuleRegistryCliFromDiscovery(options: ModuleRegistryRen
4106
4495
  const structureChecksum = discovery.getStructureChecksum()
4107
4496
  writeGeneratedFile({ outFile, checksumFile, content: output, structureChecksum, result, quiet })
4108
4497
  writeGeneratedFile({ outFile: legacyOutFile, checksumFile: legacyChecksumFile, content: legacyOutput, structureChecksum, result, quiet })
4498
+ const devSupervisorOutput = `${JSON.stringify({
4499
+ version: DEV_SUPERVISOR_MANIFEST_VERSION,
4500
+ workers: devSupervisorWorkers,
4501
+ schedulerStartStatus,
4502
+ requiresFullBootstrap,
4503
+ }, null, 2)}\n`
4504
+ writeGeneratedFile({
4505
+ outFile: devSupervisorOutFile,
4506
+ checksumFile: devSupervisorChecksumFile,
4507
+ content: devSupervisorOutput,
4508
+ structureChecksum,
4509
+ result,
4510
+ quiet,
4511
+ })
4109
4512
  const commandLoadersOutput = renderCommandLoadersFile(commandLoaderEntries)
4110
4513
  writeGeneratedFile({ outFile: commandLoadersOutFile, checksumFile: commandLoadersChecksumFile, content: commandLoadersOutput, structureChecksum, result, quiet })
4111
4514