@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.
- package/.turbo/turbo-build.log +45 -1
- package/build.mjs +25 -15
- package/dist/agentic/guides/module-facts.json +3375 -349
- package/dist/agentic/guides/modules/ai_assistant.md +57 -0
- package/dist/agentic/guides/modules/api_docs.md +39 -0
- package/dist/agentic/guides/modules/api_keys.md +41 -0
- package/dist/agentic/guides/modules/attachments.md +46 -0
- package/dist/agentic/guides/modules/audit_logs.md +42 -0
- package/dist/agentic/guides/modules/auth.md +1 -1
- package/dist/agentic/guides/modules/business_rules.md +44 -0
- package/dist/agentic/guides/modules/catalog.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail.md +39 -0
- package/dist/agentic/guides/modules/channel_imap.md +39 -0
- package/dist/agentic/guides/modules/checkout.md +60 -0
- package/dist/agentic/guides/modules/communication_channels.md +65 -0
- package/dist/agentic/guides/modules/configs.md +42 -0
- package/dist/agentic/guides/modules/content.md +39 -0
- package/dist/agentic/guides/modules/currencies.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts.md +1 -1
- package/dist/agentic/guides/modules/customers.md +1 -1
- package/dist/agentic/guides/modules/dashboards.md +43 -0
- package/dist/agentic/guides/modules/data_sync.md +1 -1
- package/dist/agentic/guides/modules/dictionaries.md +46 -0
- package/dist/agentic/guides/modules/directory.md +49 -0
- package/dist/agentic/guides/modules/entities.md +46 -0
- package/dist/agentic/guides/modules/events.md +39 -0
- package/dist/agentic/guides/modules/feature_toggles.md +42 -0
- package/dist/agentic/guides/modules/gateway_stripe.md +39 -0
- package/dist/agentic/guides/modules/generators.md +39 -0
- package/dist/agentic/guides/modules/inbox_ops.md +59 -0
- package/dist/agentic/guides/modules/integrations.md +1 -1
- package/dist/agentic/guides/modules/messages.md +55 -0
- package/dist/agentic/guides/modules/notifications.md +41 -0
- package/dist/agentic/guides/modules/onboarding.md +41 -0
- package/dist/agentic/guides/modules/payment_gateways.md +52 -0
- package/dist/agentic/guides/modules/perspectives.md +42 -0
- package/dist/agentic/guides/modules/planner.md +49 -0
- package/dist/agentic/guides/modules/portal.md +39 -0
- package/dist/agentic/guides/modules/progress.md +48 -0
- package/dist/agentic/guides/modules/query_index.md +46 -0
- package/dist/agentic/guides/modules/record_locks.md +53 -0
- package/dist/agentic/guides/modules/resources.md +62 -0
- package/dist/agentic/guides/modules/sales.md +1 -1
- package/dist/agentic/guides/modules/scheduler.md +46 -0
- package/dist/agentic/guides/modules/search.md +39 -0
- package/dist/agentic/guides/modules/security.md +67 -0
- package/dist/agentic/guides/modules/shipping_carriers.md +51 -0
- package/dist/agentic/guides/modules/sso.md +59 -0
- package/dist/agentic/guides/modules/staff.md +88 -0
- package/dist/agentic/guides/modules/storage_s3.md +39 -0
- package/dist/agentic/guides/modules/sync_akeneo.md +39 -0
- package/dist/agentic/guides/modules/sync_excel.md +41 -0
- package/dist/agentic/guides/modules/system_status_overlays.md +39 -0
- package/dist/agentic/guides/modules/translations.md +44 -0
- package/dist/agentic/guides/modules/webhooks.md +54 -0
- package/dist/agentic/guides/modules/workflows.md +1 -1
- package/dist/lib/generators/index.js +0 -2
- package/dist/lib/generators/index.js.map +2 -2
- package/dist/lib/generators/module-facts-discovery.js +46 -0
- package/dist/lib/generators/module-facts-discovery.js.map +7 -0
- package/dist/lib/generators/module-facts.js +14 -8
- package/dist/lib/generators/module-facts.js.map +2 -2
- package/dist/mercato.js +1 -3
- package/dist/mercato.js.map +2 -2
- package/package.json +5 -5
- package/src/__tests__/mercato.test.ts +0 -5
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +36 -23
- package/src/lib/generators/__tests__/module-facts.discovery.test.ts +88 -0
- package/src/lib/generators/index.ts +0 -1
- package/src/lib/generators/module-facts-discovery.ts +58 -0
- package/src/lib/generators/module-facts.ts +50 -10
- package/src/mercato.ts +0 -2
- package/dist/lib/generators/module-facts-generate.js +0 -52
- package/dist/lib/generators/module-facts-generate.js.map +0 -7
- 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.
|
|
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.
|
|
64
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
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.
|
|
74
|
+
"@open-mercato/shared": "0.6.6-develop.6460.1.f94c74174c"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
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
|
|
3
|
+
import { extractAllModuleFacts } from '../module-facts'
|
|
4
|
+
import { discoverPackageModuleSources } from '../module-facts-discovery'
|
|
5
|
+
import { createResolver } from '../../resolver'
|
|
4
6
|
|
|
5
|
-
function
|
|
7
|
+
function findRepoRoot(): string {
|
|
6
8
|
let dir = __dirname
|
|
7
9
|
for (let depth = 0; depth < 10; depth += 1) {
|
|
8
|
-
|
|
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
|
|
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 (
|
|
20
|
-
const
|
|
21
|
-
const
|
|
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('
|
|
24
|
-
|
|
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
|
|
33
|
+
for (const source of sources) {
|
|
34
|
+
const moduleId = source.moduleId
|
|
28
35
|
describe(`${moduleId}`, () => {
|
|
29
|
-
|
|
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.
|
|
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
|
|
57
|
+
expect(isUnique(facts.notifications)).toBe(true)
|
|
40
58
|
})
|
|
41
59
|
|
|
42
|
-
it('resolves
|
|
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
|
-
|
|
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,
|
|
727
|
-
const moduleRoot =
|
|
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
|
-
|
|
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
|
|
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
|
|
985
|
+
for (const source of sources) {
|
|
946
986
|
const facts = extractModuleFacts({
|
|
947
|
-
moduleId,
|
|
948
|
-
|
|
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
|
-
}
|