@open-mercato/cli 0.6.6-develop.6457.1.41b39a465a → 0.6.6-develop.6460.1.f94c74174c

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 (75) hide show
  1. package/.turbo/turbo-build.log +45 -1
  2. package/build.mjs +25 -15
  3. package/dist/agentic/guides/module-facts.json +3375 -349
  4. package/dist/agentic/guides/modules/ai_assistant.md +57 -0
  5. package/dist/agentic/guides/modules/api_docs.md +39 -0
  6. package/dist/agentic/guides/modules/api_keys.md +41 -0
  7. package/dist/agentic/guides/modules/attachments.md +46 -0
  8. package/dist/agentic/guides/modules/audit_logs.md +42 -0
  9. package/dist/agentic/guides/modules/auth.md +1 -1
  10. package/dist/agentic/guides/modules/business_rules.md +44 -0
  11. package/dist/agentic/guides/modules/catalog.md +1 -1
  12. package/dist/agentic/guides/modules/channel_gmail.md +39 -0
  13. package/dist/agentic/guides/modules/channel_imap.md +39 -0
  14. package/dist/agentic/guides/modules/checkout.md +60 -0
  15. package/dist/agentic/guides/modules/communication_channels.md +65 -0
  16. package/dist/agentic/guides/modules/configs.md +42 -0
  17. package/dist/agentic/guides/modules/content.md +39 -0
  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 +43 -0
  22. package/dist/agentic/guides/modules/data_sync.md +1 -1
  23. package/dist/agentic/guides/modules/dictionaries.md +46 -0
  24. package/dist/agentic/guides/modules/directory.md +49 -0
  25. package/dist/agentic/guides/modules/entities.md +46 -0
  26. package/dist/agentic/guides/modules/events.md +39 -0
  27. package/dist/agentic/guides/modules/feature_toggles.md +42 -0
  28. package/dist/agentic/guides/modules/gateway_stripe.md +39 -0
  29. package/dist/agentic/guides/modules/generators.md +39 -0
  30. package/dist/agentic/guides/modules/inbox_ops.md +59 -0
  31. package/dist/agentic/guides/modules/integrations.md +1 -1
  32. package/dist/agentic/guides/modules/messages.md +55 -0
  33. package/dist/agentic/guides/modules/notifications.md +41 -0
  34. package/dist/agentic/guides/modules/onboarding.md +41 -0
  35. package/dist/agentic/guides/modules/payment_gateways.md +52 -0
  36. package/dist/agentic/guides/modules/perspectives.md +42 -0
  37. package/dist/agentic/guides/modules/planner.md +49 -0
  38. package/dist/agentic/guides/modules/portal.md +39 -0
  39. package/dist/agentic/guides/modules/progress.md +48 -0
  40. package/dist/agentic/guides/modules/query_index.md +46 -0
  41. package/dist/agentic/guides/modules/record_locks.md +53 -0
  42. package/dist/agentic/guides/modules/resources.md +62 -0
  43. package/dist/agentic/guides/modules/sales.md +1 -1
  44. package/dist/agentic/guides/modules/scheduler.md +46 -0
  45. package/dist/agentic/guides/modules/search.md +39 -0
  46. package/dist/agentic/guides/modules/security.md +67 -0
  47. package/dist/agentic/guides/modules/shipping_carriers.md +51 -0
  48. package/dist/agentic/guides/modules/sso.md +59 -0
  49. package/dist/agentic/guides/modules/staff.md +88 -0
  50. package/dist/agentic/guides/modules/storage_s3.md +39 -0
  51. package/dist/agentic/guides/modules/sync_akeneo.md +39 -0
  52. package/dist/agentic/guides/modules/sync_excel.md +41 -0
  53. package/dist/agentic/guides/modules/system_status_overlays.md +39 -0
  54. package/dist/agentic/guides/modules/translations.md +44 -0
  55. package/dist/agentic/guides/modules/webhooks.md +54 -0
  56. package/dist/agentic/guides/modules/workflows.md +1 -1
  57. package/dist/lib/generators/index.js +0 -2
  58. package/dist/lib/generators/index.js.map +2 -2
  59. package/dist/lib/generators/module-facts-discovery.js +46 -0
  60. package/dist/lib/generators/module-facts-discovery.js.map +7 -0
  61. package/dist/lib/generators/module-facts.js +14 -8
  62. package/dist/lib/generators/module-facts.js.map +2 -2
  63. package/dist/mercato.js +1 -3
  64. package/dist/mercato.js.map +2 -2
  65. package/package.json +5 -5
  66. package/src/__tests__/mercato.test.ts +0 -5
  67. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +36 -23
  68. package/src/lib/generators/__tests__/module-facts.discovery.test.ts +88 -0
  69. package/src/lib/generators/index.ts +0 -1
  70. package/src/lib/generators/module-facts-discovery.ts +58 -0
  71. package/src/lib/generators/module-facts.ts +50 -10
  72. package/src/mercato.ts +0 -2
  73. package/dist/lib/generators/module-facts-generate.js +0 -52
  74. package/dist/lib/generators/module-facts-generate.js.map +0 -7
  75. package/src/lib/generators/module-facts-generate.ts +0 -68
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.6.6-develop.6457.1.41b39a465a",
3
+ "version": "0.6.6-develop.6460.1.f94c74174c",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,8 +60,8 @@
60
60
  "@mikro-orm/decorators": "^7.1.5",
61
61
  "@mikro-orm/migrations": "^7.1.5",
62
62
  "@mikro-orm/postgresql": "^7.1.5",
63
- "@open-mercato/queue": "0.6.6-develop.6457.1.41b39a465a",
64
- "@open-mercato/shared": "0.6.6-develop.6457.1.41b39a465a",
63
+ "@open-mercato/queue": "0.6.6-develop.6460.1.f94c74174c",
64
+ "@open-mercato/shared": "0.6.6-develop.6460.1.f94c74174c",
65
65
  "cross-spawn": "^7.0.6",
66
66
  "pg": "8.22.0",
67
67
  "semver": "^7.8.5",
@@ -71,10 +71,10 @@
71
71
  "typescript": "^6.0.3"
72
72
  },
73
73
  "peerDependencies": {
74
- "@open-mercato/shared": "0.6.6-develop.6457.1.41b39a465a"
74
+ "@open-mercato/shared": "0.6.6-develop.6460.1.f94c74174c"
75
75
  },
76
76
  "devDependencies": {
77
- "@open-mercato/shared": "0.6.6-develop.6457.1.41b39a465a",
77
+ "@open-mercato/shared": "0.6.6-develop.6460.1.f94c74174c",
78
78
  "@types/jest": "^30.0.0",
79
79
  "jest": "^30.4.2",
80
80
  "ts-jest": "^29.4.11"
@@ -359,7 +359,6 @@ describe('init command failure output', () => {
359
359
  generateModuleDi: jest.fn().mockResolvedValue(undefined),
360
360
  generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
361
361
  generateOpenApi: jest.fn().mockResolvedValue(undefined),
362
- generateModuleFacts: jest.fn().mockResolvedValue(undefined),
363
362
  }))
364
363
  jest.doMock('../lib/resolver', () => ({
365
364
  createResolver: () => ({
@@ -408,7 +407,6 @@ describe('init command failure output', () => {
408
407
  generateModuleDi: jest.fn().mockResolvedValue(undefined),
409
408
  generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
410
409
  generateOpenApi: jest.fn().mockResolvedValue(undefined),
411
- generateModuleFacts: jest.fn().mockResolvedValue(undefined),
412
410
  }))
413
411
  jest.doMock('../lib/resolver', () => ({
414
412
  createResolver: () => ({
@@ -460,7 +458,6 @@ describe('init command failure output', () => {
460
458
  generateModuleDi: jest.fn().mockResolvedValue(undefined),
461
459
  generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
462
460
  generateOpenApi: jest.fn().mockResolvedValue(undefined),
463
- generateModuleFacts: jest.fn().mockResolvedValue(undefined),
464
461
  }))
465
462
  jest.doMock('../lib/db', () => ({
466
463
  dbMigrate: jest.fn().mockResolvedValue(undefined),
@@ -569,7 +566,6 @@ describe('generate post-step structural cache purge', () => {
569
566
  generateModuleDi,
570
567
  generateModulePackageSources,
571
568
  generateOpenApi,
572
- generateModuleFacts: jest.fn().mockResolvedValue(undefined),
573
569
  }))
574
570
  jest.doMock('../lib/resolver', () => ({
575
571
  createResolver: () => ({
@@ -619,7 +615,6 @@ describe('generate post-step structural cache purge', () => {
619
615
  generateModuleDi,
620
616
  generateModulePackageSources,
621
617
  generateOpenApi,
622
- generateModuleFacts: jest.fn().mockResolvedValue(undefined),
623
618
  }))
624
619
  jest.doMock('../lib/resolver', () => ({
625
620
  createResolver: () => ({
@@ -1,51 +1,64 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
- import { extractAllModuleFacts, MODULE_FACTS_ALLOWLIST } from '../module-facts'
3
+ import { extractAllModuleFacts } from '../module-facts'
4
+ import { discoverPackageModuleSources } from '../module-facts-discovery'
5
+ import { createResolver } from '../../resolver'
4
6
 
5
- function findCoreSrcRoot(): string {
7
+ function findRepoRoot(): string {
6
8
  let dir = __dirname
7
9
  for (let depth = 0; depth < 10; depth += 1) {
8
- const candidate = path.join(dir, 'packages', 'core', 'src', 'modules')
9
- if (fs.existsSync(candidate)) return candidate
10
+ if (fs.existsSync(path.join(dir, 'packages', 'core', 'src', 'modules'))) return dir
10
11
  dir = path.dirname(dir)
11
12
  }
12
- throw new Error('[internal] could not locate packages/core/src/modules from the test directory')
13
+ throw new Error('[internal] could not locate repo root from the test directory')
13
14
  }
14
15
 
15
16
  function isUnique(values: string[]): boolean {
16
17
  return values.length === new Set(values).size
17
18
  }
18
19
 
19
- describe('module-facts BC resolve guard (T3)', () => {
20
- const coreSrcRoot = findCoreSrcRoot()
21
- const { factsByModule } = extractAllModuleFacts({ coreSrcRoot })
20
+ describe('module-facts BC resolve guard (T2)', () => {
21
+ const repoRoot = findRepoRoot()
22
+ const sources = discoverPackageModuleSources(createResolver(repoRoot))
23
+ const { factsByModule } = extractAllModuleFacts({ sources })
22
24
 
23
- it('emits facts for every allowlisted module', () => {
24
- expect(Object.keys(factsByModule).sort()).toEqual([...MODULE_FACTS_ALLOWLIST].sort())
25
+ it('discovers a superset of the historical core modules', () => {
26
+ const discovered = new Set(Object.keys(factsByModule))
27
+ for (const moduleId of ['auth', 'catalog', 'customers', 'sales', 'workflows']) {
28
+ expect(discovered.has(moduleId)).toBe(true)
29
+ }
30
+ expect(discovered.size).toBeGreaterThan(9)
25
31
  })
26
32
 
27
- for (const moduleId of MODULE_FACTS_ALLOWLIST) {
33
+ for (const source of sources) {
34
+ const moduleId = source.moduleId
28
35
  describe(`${moduleId}`, () => {
29
- it('namespaces entity / event / acl / notification ids under the module and keeps them unique', () => {
30
- const facts = factsByModule[moduleId]
31
- const entityIds = facts.entities.map((entity) => entity.id)
36
+ const facts = factsByModule[moduleId]
32
37
 
38
+ // Entity / search / host ids are colon-namespaced under the module by construction
39
+ // and convention; drift here means the builder or a module's data model broke.
40
+ it('colon-namespaces entity / search / host ids under the module and keeps ids unique', () => {
41
+ const entityIds = facts.entities.map((entity) => entity.id)
33
42
  expect(entityIds.every((id) => id.startsWith(`${moduleId}:`))).toBe(true)
34
43
  expect(isUnique(entityIds)).toBe(true)
35
- expect(facts.events.every((event) => event.id.startsWith(`${moduleId}.`))).toBe(true)
44
+ expect(facts.searchEntities.every((id) => id.startsWith(`${moduleId}:`))).toBe(true)
45
+ expect(isUnique(facts.searchEntities)).toBe(true)
46
+ expect(facts.hostTokens.entityIds.every((id) => id.startsWith(`${moduleId}:`))).toBe(true)
47
+ })
48
+
49
+ // Event / ACL / notification ids must be unique, but are NOT asserted to be
50
+ // dot-prefixed by the module id: some modules intentionally use a different
51
+ // namespace (e.g. ai_assistant -> `ai.*`, dashboards -> `analytics.*`,
52
+ // storage_s3 -> `storage_providers.*`). The meaningful invariant is uniqueness,
53
+ // not folder-name prefixing (spec 2026-07-06 R1).
54
+ it('keeps event / acl / notification ids unique', () => {
36
55
  expect(isUnique(facts.events.map((event) => event.id))).toBe(true)
37
- expect(facts.aclFeatures.every((feature) => feature.startsWith(`${moduleId}.`))).toBe(true)
38
56
  expect(isUnique(facts.aclFeatures)).toBe(true)
39
- expect(facts.notifications.every((notification) => notification.startsWith(`${moduleId}.`))).toBe(true)
57
+ expect(isUnique(facts.notifications)).toBe(true)
40
58
  })
41
59
 
42
- it('resolves search and host-token references against the module entity set', () => {
43
- const facts = factsByModule[moduleId]
60
+ it('resolves host-token entity ids against the module entity set', () => {
44
61
  const entityIds = new Set(facts.entities.map((entity) => entity.id))
45
-
46
- for (const searchEntity of facts.searchEntities) {
47
- expect(entityIds.has(searchEntity)).toBe(true)
48
- }
49
62
  for (const hostEntityId of facts.hostTokens.entityIds) {
50
63
  expect(entityIds.has(hostEntityId)).toBe(true)
51
64
  expect(hostEntityId.endsWith('_entity')).toBe(true)
@@ -0,0 +1,88 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import type { PackageResolver } from '../../resolver'
5
+ import { discoverPackageModuleSources, hasReadableModuleSource } from '../module-facts-discovery'
6
+
7
+ type FakePackage = { name: string; path: string; modulesPath: string }
8
+
9
+ function makeResolver(packages: FakePackage[]): PackageResolver {
10
+ return {
11
+ discoverPackages: () => packages,
12
+ } as unknown as PackageResolver
13
+ }
14
+
15
+ function writeModule(modulesDir: string, moduleId: string, file: string): void {
16
+ const moduleRoot = path.join(modulesDir, moduleId)
17
+ fs.mkdirSync(moduleRoot, { recursive: true })
18
+ fs.writeFileSync(path.join(moduleRoot, file), '// fixture\n')
19
+ }
20
+
21
+ describe('module-facts discovery (T1)', () => {
22
+ let tmp: string
23
+
24
+ beforeAll(() => {
25
+ tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'module-facts-discovery-'))
26
+ })
27
+
28
+ afterAll(() => {
29
+ fs.rmSync(tmp, { recursive: true, force: true })
30
+ })
31
+
32
+ it('hasReadableModuleSource is true for a .ts module and false for a .js-only module', () => {
33
+ const srcModules = path.join(tmp, 'readable', 'src', 'modules')
34
+ writeModule(srcModules, 'alpha', 'index.ts')
35
+ const distModules = path.join(tmp, 'readable', 'dist', 'modules')
36
+ writeModule(distModules, 'compiled', 'index.js')
37
+
38
+ expect(hasReadableModuleSource(path.join(srcModules, 'alpha'))).toBe(true)
39
+ expect(hasReadableModuleSource(path.join(distModules, 'compiled'))).toBe(false)
40
+ expect(hasReadableModuleSource(path.join(srcModules, 'missing'))).toBe(false)
41
+ })
42
+
43
+ it('returns package-provided .ts modules and skips .js-only installs (A1 + A3)', () => {
44
+ const pkgSrc = path.join(tmp, 'pkg-a')
45
+ writeModule(path.join(pkgSrc, 'src', 'modules'), 'alpha', 'index.ts')
46
+ writeModule(path.join(pkgSrc, 'src', 'modules'), 'beta', 'acl.ts')
47
+
48
+ const pkgDist = path.join(tmp, 'pkg-compiled')
49
+ writeModule(path.join(pkgDist, 'dist', 'modules'), 'compiled', 'index.js')
50
+
51
+ const resolver = makeResolver([
52
+ { name: '@open-mercato/core', path: pkgSrc, modulesPath: path.join(pkgSrc, 'src', 'modules') },
53
+ { name: '@open-mercato/compiled', path: pkgDist, modulesPath: path.join(pkgDist, 'dist', 'modules') },
54
+ ])
55
+
56
+ const ids = discoverPackageModuleSources(resolver).map((source) => source.moduleId)
57
+ expect(ids.sort()).toEqual(['alpha', 'beta'])
58
+ })
59
+
60
+ it('tags each source with its providing package name', () => {
61
+ const pkgSrc = path.join(tmp, 'pkg-tagged')
62
+ writeModule(path.join(pkgSrc, 'src', 'modules'), 'gamma', 'events.ts')
63
+
64
+ const resolver = makeResolver([
65
+ { name: '@open-mercato/tagged', path: pkgSrc, modulesPath: path.join(pkgSrc, 'src', 'modules') },
66
+ ])
67
+
68
+ const [source] = discoverPackageModuleSources(resolver)
69
+ expect(source).toMatchObject({ moduleId: 'gamma', from: '@open-mercato/tagged' })
70
+ expect(source.moduleRoot).toBe(path.join(pkgSrc, 'src', 'modules', 'gamma'))
71
+ })
72
+
73
+ it('dedupes duplicate module ids first-wins', () => {
74
+ const pkgA = path.join(tmp, 'dup-a')
75
+ writeModule(path.join(pkgA, 'src', 'modules'), 'shared', 'index.ts')
76
+ const pkgB = path.join(tmp, 'dup-b')
77
+ writeModule(path.join(pkgB, 'src', 'modules'), 'shared', 'index.ts')
78
+
79
+ const resolver = makeResolver([
80
+ { name: '@open-mercato/dup-a', path: pkgA, modulesPath: path.join(pkgA, 'src', 'modules') },
81
+ { name: '@open-mercato/dup-b', path: pkgB, modulesPath: path.join(pkgB, 'src', 'modules') },
82
+ ])
83
+
84
+ const sources = discoverPackageModuleSources(resolver)
85
+ expect(sources).toHaveLength(1)
86
+ expect(sources[0].moduleRoot).toBe(path.join(pkgA, 'src', 'modules', 'shared'))
87
+ })
88
+ })
@@ -4,4 +4,3 @@ export { generateModuleEntities, type ModuleEntitiesOptions } from './module-ent
4
4
  export { generateModuleDi, type ModuleDiOptions } from './module-di'
5
5
  export { generateModulePackageSources, type ModulePackageSourcesOptions } from './module-package-sources'
6
6
  export { generateOpenApi, type GenerateOpenApiOptions } from './openapi'
7
- export { generateModuleFacts, type ModuleFactsOptions } from './module-facts-generate'
@@ -0,0 +1,58 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import type { PackageResolver } from '../resolver'
4
+ import { discoverModulesInPackage } from '../module-package'
5
+ import type { ModuleFactSource } from './module-facts'
6
+
7
+ const READABLE_CONVENTION_FILES = [
8
+ 'index',
9
+ 'acl',
10
+ 'events',
11
+ 'di',
12
+ path.join('data', 'entities'),
13
+ path.join('db', 'entities'),
14
+ ]
15
+
16
+ /**
17
+ * True when the module directory exposes at least one recognised convention file as
18
+ * TypeScript source. This is the auto-discovery boundary that skips `.js`-only
19
+ * installs (standalone `node_modules/@open-mercato/<pkg>/dist/modules`) — the extractor's
20
+ * ts-morph reader only parses `.ts`/`.tsx`, so a `.js`-only root would yield empty facts.
21
+ */
22
+ export function hasReadableModuleSource(moduleRoot: string): boolean {
23
+ if (!fs.existsSync(moduleRoot)) return false
24
+ for (const basename of READABLE_CONVENTION_FILES) {
25
+ for (const extension of ['.ts', '.tsx']) {
26
+ if (fs.existsSync(path.join(moduleRoot, `${basename}${extension}`))) return true
27
+ }
28
+ }
29
+ return false
30
+ }
31
+
32
+ function dedupeById(sources: ModuleFactSource[]): ModuleFactSource[] {
33
+ const seen = new Set<string>()
34
+ const result: ModuleFactSource[] = []
35
+ for (const source of sources) {
36
+ if (seen.has(source.moduleId)) continue
37
+ seen.add(source.moduleId)
38
+ result.push(source)
39
+ }
40
+ return result
41
+ }
42
+
43
+ /**
44
+ * Package-scan discovery for the create-app build: every package-provided module
45
+ * across `@open-mercato/*` workspace packages (core plus others), never `apps/*`
46
+ * demo modules. Routes through the resolver rather than hardcoded paths.
47
+ */
48
+ export function discoverPackageModuleSources(resolver: PackageResolver): ModuleFactSource[] {
49
+ const sources: ModuleFactSource[] = []
50
+ for (const pkg of resolver.discoverPackages()) {
51
+ for (const discovered of discoverModulesInPackage(pkg.path)) {
52
+ const moduleRoot = path.join(pkg.modulesPath, discovered.moduleId)
53
+ if (!hasReadableModuleSource(moduleRoot)) continue
54
+ sources.push({ moduleId: discovered.moduleId, moduleRoot, from: pkg.name })
55
+ }
56
+ }
57
+ return dedupeById(sources)
58
+ }
@@ -54,12 +54,32 @@ export interface ModuleFacts {
54
54
 
55
55
  export interface ExtractModuleFactsOptions {
56
56
  moduleId: string
57
- coreSrcRoot: string
57
+ /**
58
+ * Parent modules directory joined with `moduleId` to locate the module source.
59
+ * Legacy input; prefer the explicit per-module `moduleRoot` for modules that do
60
+ * not live under a single shared root (auto-discovery). One of `moduleRoot` /
61
+ * `coreSrcRoot` must be provided.
62
+ */
63
+ coreSrcRoot?: string
64
+ /** Explicit module source directory. When set it overrides `coreSrcRoot + moduleId`. */
65
+ moduleRoot?: string
58
66
  coreVersion?: string | null
59
67
  registryPath?: string | null
60
68
  registrySource?: string | null
61
69
  }
62
70
 
71
+ /** A discovered module and the source directory its facts are extracted from. */
72
+ export interface ModuleFactSource {
73
+ moduleId: string
74
+ moduleRoot: string
75
+ from?: string
76
+ }
77
+
78
+ /**
79
+ * @deprecated Superseded by auto-discovery (`discoverPackageModuleSources` in
80
+ * `module-facts-discovery.ts`). Retained as a stable export for one minor; no
81
+ * longer gates which modules receive fact-sheets.
82
+ */
63
83
  export const MODULE_FACTS_ALLOWLIST = [
64
84
  'auth',
65
85
  'catalog',
@@ -72,6 +92,7 @@ export const MODULE_FACTS_ALLOWLIST = [
72
92
  'workflows',
73
93
  ] as const
74
94
 
95
+ /** @deprecated See {@link MODULE_FACTS_ALLOWLIST}. */
75
96
  export type ModuleFactsModuleId = (typeof MODULE_FACTS_ALLOWLIST)[number]
76
97
 
77
98
  function readSourceFile(filePath: string): ts.SourceFile | null {
@@ -723,8 +744,12 @@ function extractModuleMeta(indexFilePath: string | null): { title: string | null
723
744
  }
724
745
 
725
746
  export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFacts {
726
- const { moduleId, coreSrcRoot, coreVersion = null } = options
727
- const moduleRoot = path.join(coreSrcRoot, moduleId)
747
+ const { moduleId, coreVersion = null } = options
748
+ const moduleRoot = options.moduleRoot
749
+ ?? (options.coreSrcRoot ? path.join(options.coreSrcRoot, moduleId) : null)
750
+ if (!moduleRoot) {
751
+ throw new Error(`[internal] extractModuleFacts requires moduleRoot or coreSrcRoot for module "${moduleId}"`)
752
+ }
728
753
 
729
754
  const entitiesFilePath =
730
755
  resolveConventionFile(path.join(moduleRoot, 'data'), 'entities') ??
@@ -924,10 +949,17 @@ export function renderModuleFactsJson(factsByModule: Record<string, ModuleFacts>
924
949
  }
925
950
 
926
951
  export interface ExtractAllModuleFactsOptions {
927
- coreSrcRoot: string
952
+ /**
953
+ * Discovered module sources (auto-discovery path). When provided, each entry's
954
+ * explicit `moduleRoot` is used and `coreSrcRoot`/`moduleIds` are ignored.
955
+ */
956
+ sources?: readonly ModuleFactSource[]
957
+ /** Legacy shared-root path. Used only when `sources` is not provided. */
958
+ coreSrcRoot?: string
928
959
  registryPath?: string | null
929
960
  registrySource?: string | null
930
961
  coreVersion?: string | null
962
+ /** @deprecated Legacy allowlist iteration; only consulted when `sources` is absent. */
931
963
  moduleIds?: readonly string[]
932
964
  }
933
965
 
@@ -938,20 +970,28 @@ export interface ExtractAllModuleFactsResult {
938
970
  }
939
971
 
940
972
  export function extractAllModuleFacts(options: ExtractAllModuleFactsOptions): ExtractAllModuleFactsResult {
941
- const moduleIds = options.moduleIds ?? MODULE_FACTS_ALLOWLIST
973
+ const sources: ModuleFactSource[] = options.sources
974
+ ? [...options.sources]
975
+ : (options.coreSrcRoot
976
+ ? (options.moduleIds ?? MODULE_FACTS_ALLOWLIST).map((moduleId) => ({
977
+ moduleId,
978
+ moduleRoot: path.join(options.coreSrcRoot as string, moduleId),
979
+ }))
980
+ : [])
981
+
942
982
  const factsByModule: Record<string, ModuleFacts> = {}
943
983
  const markdownByModule: Record<string, string> = {}
944
984
  const warnings: string[] = []
945
- for (const moduleId of moduleIds) {
985
+ for (const source of sources) {
946
986
  const facts = extractModuleFacts({
947
- moduleId,
948
- coreSrcRoot: options.coreSrcRoot,
987
+ moduleId: source.moduleId,
988
+ moduleRoot: source.moduleRoot,
949
989
  coreVersion: options.coreVersion ?? null,
950
990
  registryPath: options.registryPath ?? null,
951
991
  registrySource: options.registrySource ?? null,
952
992
  })
953
- factsByModule[moduleId] = facts
954
- markdownByModule[moduleId] = renderModuleFactsMarkdown(facts)
993
+ factsByModule[source.moduleId] = facts
994
+ markdownByModule[source.moduleId] = renderModuleFactsMarkdown(facts)
955
995
  warnings.push(...facts.warnings)
956
996
  }
957
997
  return { factsByModule, markdownByModule, warnings }
package/src/mercato.ts CHANGED
@@ -756,7 +756,6 @@ async function runGeneratorSuite(quiet: boolean): Promise<void> {
756
756
  generateModuleDi,
757
757
  generateModulePackageSources,
758
758
  generateOpenApi,
759
- generateModuleFacts,
760
759
  } = await import('./lib/generators')
761
760
  const resolver = createResolver()
762
761
  await generateEntityIds({ resolver, quiet })
@@ -767,7 +766,6 @@ async function runGeneratorSuite(quiet: boolean): Promise<void> {
767
766
  await generateModuleDi({ resolver, quiet })
768
767
  await generateModulePackageSources({ resolver, quiet })
769
768
  await generateOpenApi({ resolver, quiet })
770
- await generateModuleFacts({ resolver, quiet })
771
769
  }
772
770
 
773
771
  /**
@@ -1,52 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import {
4
- createGeneratorResult,
5
- ensureDir,
6
- logGenerationResult
7
- } from "../utils.js";
8
- import { extractAllModuleFacts, renderModuleFactsJson } from "./module-facts.js";
9
- function readCoreVersion(coreSrcRoot) {
10
- const corePackageJsonPath = path.resolve(coreSrcRoot, "..", "..", "package.json");
11
- if (!fs.existsSync(corePackageJsonPath)) return null;
12
- try {
13
- const parsed = JSON.parse(fs.readFileSync(corePackageJsonPath, "utf8"));
14
- return typeof parsed.version === "string" ? parsed.version : null;
15
- } catch {
16
- return null;
17
- }
18
- }
19
- async function generateModuleFacts(options) {
20
- const { resolver, quiet } = options;
21
- const result = createGeneratorResult();
22
- const rootDir = resolver.getRootDir();
23
- const coreSrcRoot = path.join(rootDir, "packages", "core", "src", "modules");
24
- if (!fs.existsSync(coreSrcRoot)) {
25
- if (!quiet) {
26
- console.warn(`[module-facts] core module sources not found at ${coreSrcRoot}; skipping module-facts generation`);
27
- }
28
- return result;
29
- }
30
- const registryPath = path.join(resolver.getOutputDir(), "modules.runtime.generated.ts");
31
- const coreVersion = readCoreVersion(coreSrcRoot);
32
- const { factsByModule, warnings } = extractAllModuleFacts({ coreSrcRoot, registryPath, coreVersion });
33
- for (const warning of warnings) {
34
- console.warn(warning);
35
- }
36
- const content = renderModuleFactsJson(factsByModule);
37
- const outFile = path.join(resolver.getAppDir(), "src", "module-facts.generated.json");
38
- const existing = fs.existsSync(outFile) ? fs.readFileSync(outFile, "utf8") : null;
39
- if (existing === content) {
40
- result.filesUnchanged.push(outFile);
41
- return result;
42
- }
43
- ensureDir(outFile);
44
- fs.writeFileSync(outFile, content);
45
- result.filesWritten.push(outFile);
46
- logGenerationResult(path.relative(process.cwd(), outFile), true);
47
- return result;
48
- }
49
- export {
50
- generateModuleFacts
51
- };
52
- //# sourceMappingURL=module-facts-generate.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/lib/generators/module-facts-generate.ts"],
4
- "sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport type { PackageResolver } from '../resolver'\nimport {\n createGeneratorResult,\n ensureDir,\n logGenerationResult,\n type GeneratorResult,\n} from '../utils'\nimport { extractAllModuleFacts, renderModuleFactsJson } from './module-facts'\n\nexport interface ModuleFactsOptions {\n resolver: PackageResolver\n quiet?: boolean\n}\n\nfunction readCoreVersion(coreSrcRoot: string): string | null {\n const corePackageJsonPath = path.resolve(coreSrcRoot, '..', '..', 'package.json')\n if (!fs.existsSync(corePackageJsonPath)) return null\n try {\n const parsed = JSON.parse(fs.readFileSync(corePackageJsonPath, 'utf8')) as { version?: unknown }\n return typeof parsed.version === 'string' ? parsed.version : null\n } catch {\n return null\n }\n}\n\n/**\n * Emits the versioned `apps/mercato/src/module-facts.generated.json` artifact from\n * monorepo core module sources. Must run AFTER `generateModuleRegistry` because\n * per-route API auth is read from the generated `modules.runtime.generated.ts`\n * registry (`apis[].metadata`). Registry-derived warnings are non-fatal.\n */\nexport async function generateModuleFacts(options: ModuleFactsOptions): Promise<GeneratorResult> {\n const { resolver, quiet } = options\n const result = createGeneratorResult()\n\n const rootDir = resolver.getRootDir()\n const coreSrcRoot = path.join(rootDir, 'packages', 'core', 'src', 'modules')\n if (!fs.existsSync(coreSrcRoot)) {\n if (!quiet) {\n console.warn(`[module-facts] core module sources not found at ${coreSrcRoot}; skipping module-facts generation`)\n }\n return result\n }\n\n const registryPath = path.join(resolver.getOutputDir(), 'modules.runtime.generated.ts')\n const coreVersion = readCoreVersion(coreSrcRoot)\n\n const { factsByModule, warnings } = extractAllModuleFacts({ coreSrcRoot, registryPath, coreVersion })\n for (const warning of warnings) {\n console.warn(warning)\n }\n\n const content = renderModuleFactsJson(factsByModule)\n const outFile = path.join(resolver.getAppDir(), 'src', 'module-facts.generated.json')\n const existing = fs.existsSync(outFile) ? fs.readFileSync(outFile, 'utf8') : null\n if (existing === content) {\n result.filesUnchanged.push(outFile)\n return result\n }\n\n ensureDir(outFile)\n fs.writeFileSync(outFile, content)\n result.filesWritten.push(outFile)\n logGenerationResult(path.relative(process.cwd(), outFile), true)\n return result\n}\n"],
5
- "mappings": "AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,uBAAuB,6BAA6B;AAO7D,SAAS,gBAAgB,aAAoC;AAC3D,QAAM,sBAAsB,KAAK,QAAQ,aAAa,MAAM,MAAM,cAAc;AAChF,MAAI,CAAC,GAAG,WAAW,mBAAmB,EAAG,QAAO;AAChD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG,aAAa,qBAAqB,MAAM,CAAC;AACtE,WAAO,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;AAAA,EAC/D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,oBAAoB,SAAuD;AAC/F,QAAM,EAAE,UAAU,MAAM,IAAI;AAC5B,QAAM,SAAS,sBAAsB;AAErC,QAAM,UAAU,SAAS,WAAW;AACpC,QAAM,cAAc,KAAK,KAAK,SAAS,YAAY,QAAQ,OAAO,SAAS;AAC3E,MAAI,CAAC,GAAG,WAAW,WAAW,GAAG;AAC/B,QAAI,CAAC,OAAO;AACV,cAAQ,KAAK,mDAAmD,WAAW,oCAAoC;AAAA,IACjH;AACA,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,KAAK,KAAK,SAAS,aAAa,GAAG,8BAA8B;AACtF,QAAM,cAAc,gBAAgB,WAAW;AAE/C,QAAM,EAAE,eAAe,SAAS,IAAI,sBAAsB,EAAE,aAAa,cAAc,YAAY,CAAC;AACpG,aAAW,WAAW,UAAU;AAC9B,YAAQ,KAAK,OAAO;AAAA,EACtB;AAEA,QAAM,UAAU,sBAAsB,aAAa;AACnD,QAAM,UAAU,KAAK,KAAK,SAAS,UAAU,GAAG,OAAO,6BAA6B;AACpF,QAAM,WAAW,GAAG,WAAW,OAAO,IAAI,GAAG,aAAa,SAAS,MAAM,IAAI;AAC7E,MAAI,aAAa,SAAS;AACxB,WAAO,eAAe,KAAK,OAAO;AAClC,WAAO;AAAA,EACT;AAEA,YAAU,OAAO;AACjB,KAAG,cAAc,SAAS,OAAO;AACjC,SAAO,aAAa,KAAK,OAAO;AAChC,sBAAoB,KAAK,SAAS,QAAQ,IAAI,GAAG,OAAO,GAAG,IAAI;AAC/D,SAAO;AACT;",
6
- "names": []
7
- }
@@ -1,68 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import type { PackageResolver } from '../resolver'
4
- import {
5
- createGeneratorResult,
6
- ensureDir,
7
- logGenerationResult,
8
- type GeneratorResult,
9
- } from '../utils'
10
- import { extractAllModuleFacts, renderModuleFactsJson } from './module-facts'
11
-
12
- export interface ModuleFactsOptions {
13
- resolver: PackageResolver
14
- quiet?: boolean
15
- }
16
-
17
- function readCoreVersion(coreSrcRoot: string): string | null {
18
- const corePackageJsonPath = path.resolve(coreSrcRoot, '..', '..', 'package.json')
19
- if (!fs.existsSync(corePackageJsonPath)) return null
20
- try {
21
- const parsed = JSON.parse(fs.readFileSync(corePackageJsonPath, 'utf8')) as { version?: unknown }
22
- return typeof parsed.version === 'string' ? parsed.version : null
23
- } catch {
24
- return null
25
- }
26
- }
27
-
28
- /**
29
- * Emits the versioned `apps/mercato/src/module-facts.generated.json` artifact from
30
- * monorepo core module sources. Must run AFTER `generateModuleRegistry` because
31
- * per-route API auth is read from the generated `modules.runtime.generated.ts`
32
- * registry (`apis[].metadata`). Registry-derived warnings are non-fatal.
33
- */
34
- export async function generateModuleFacts(options: ModuleFactsOptions): Promise<GeneratorResult> {
35
- const { resolver, quiet } = options
36
- const result = createGeneratorResult()
37
-
38
- const rootDir = resolver.getRootDir()
39
- const coreSrcRoot = path.join(rootDir, 'packages', 'core', 'src', 'modules')
40
- if (!fs.existsSync(coreSrcRoot)) {
41
- if (!quiet) {
42
- console.warn(`[module-facts] core module sources not found at ${coreSrcRoot}; skipping module-facts generation`)
43
- }
44
- return result
45
- }
46
-
47
- const registryPath = path.join(resolver.getOutputDir(), 'modules.runtime.generated.ts')
48
- const coreVersion = readCoreVersion(coreSrcRoot)
49
-
50
- const { factsByModule, warnings } = extractAllModuleFacts({ coreSrcRoot, registryPath, coreVersion })
51
- for (const warning of warnings) {
52
- console.warn(warning)
53
- }
54
-
55
- const content = renderModuleFactsJson(factsByModule)
56
- const outFile = path.join(resolver.getAppDir(), 'src', 'module-facts.generated.json')
57
- const existing = fs.existsSync(outFile) ? fs.readFileSync(outFile, 'utf8') : null
58
- if (existing === content) {
59
- result.filesUnchanged.push(outFile)
60
- return result
61
- }
62
-
63
- ensureDir(outFile)
64
- fs.writeFileSync(outFile, content)
65
- result.filesWritten.push(outFile)
66
- logGenerationResult(path.relative(process.cwd(), outFile), true)
67
- return result
68
- }