@open-mercato/cli 0.6.7-develop.6685.1.77c0a5591b → 0.6.7-develop.6686.1.bdda5ed397
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 +2 -3
- package/build.mjs +21 -5
- package/dist/agentic/claude-code/hooks/entity-migration-check.ts +32 -88
- package/dist/agentic/codex/enforcement-rules.md +2 -16
- package/dist/agentic/cursor/hooks/entity-migration-check.mjs +19 -50
- package/dist/agentic/cursor/rules/entity-guard.mdc +9 -21
- package/dist/agentic/cursor/rules/generated-guard.mdc +3 -5
- package/dist/agentic/cursor/rules/open-mercato.mdc +3 -29
- package/dist/agentic/guides/ai-workflows.md +76 -0
- package/dist/agentic/guides/architecture.md +107 -0
- package/dist/agentic/guides/backend-ui.md +80 -0
- package/dist/agentic/guides/contracts.md +92 -0
- package/dist/agentic/guides/extensions.md +104 -0
- package/dist/agentic/guides/integrations.md +81 -0
- package/dist/agentic/guides/module-facts.json +217 -81
- package/dist/agentic/guides/module-system.md +12 -127
- package/dist/agentic/guides/modules/ai_assistant.md +1 -1
- package/dist/agentic/guides/modules/api_docs.md +1 -1
- package/dist/agentic/guides/modules/api_keys.md +1 -1
- package/dist/agentic/guides/modules/attachments.md +2 -2
- package/dist/agentic/guides/modules/audit_logs.md +2 -2
- package/dist/agentic/guides/modules/auth.md +1 -1
- package/dist/agentic/guides/modules/business_rules.md +1 -1
- package/dist/agentic/guides/modules/catalog.md +5 -5
- package/dist/agentic/guides/modules/channel_gmail.md +1 -1
- package/dist/agentic/guides/modules/channel_imap.md +1 -1
- package/dist/agentic/guides/modules/checkout.md +1 -1
- package/dist/agentic/guides/modules/communication_channels.md +1 -1
- package/dist/agentic/guides/modules/configs.md +1 -1
- package/dist/agentic/guides/modules/content.md +1 -1
- 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 +2 -2
- package/dist/agentic/guides/modules/dashboards.md +1 -1
- package/dist/agentic/guides/modules/data_sync.md +1 -1
- package/dist/agentic/guides/modules/dictionaries.md +1 -1
- package/dist/agentic/guides/modules/directory.md +1 -1
- package/dist/agentic/guides/modules/entities.md +2 -2
- package/dist/agentic/guides/modules/events.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
- package/dist/agentic/guides/modules/generators.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops.md +1 -1
- package/dist/agentic/guides/modules/integrations.md +1 -1
- package/dist/agentic/guides/modules/messages.md +1 -1
- package/dist/agentic/guides/modules/notifications.md +1 -1
- package/dist/agentic/guides/modules/onboarding.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways.md +1 -1
- package/dist/agentic/guides/modules/perspectives.md +1 -1
- package/dist/agentic/guides/modules/planner.md +1 -1
- package/dist/agentic/guides/modules/portal.md +1 -1
- package/dist/agentic/guides/modules/progress.md +1 -1
- package/dist/agentic/guides/modules/query_index.md +2 -2
- package/dist/agentic/guides/modules/record_locks.md +1 -1
- package/dist/agentic/guides/modules/resources.md +1 -1
- package/dist/agentic/guides/modules/sales.md +17 -17
- package/dist/agentic/guides/modules/scheduler.md +1 -1
- package/dist/agentic/guides/modules/search.md +1 -1
- package/dist/agentic/guides/modules/security.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
- package/dist/agentic/guides/modules/sso.md +1 -1
- package/dist/agentic/guides/modules/staff.md +2 -2
- package/dist/agentic/guides/modules/storage_s3.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
- package/dist/agentic/guides/modules/sync_excel.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
- package/dist/agentic/guides/modules/translations.md +1 -1
- package/dist/agentic/guides/modules/webhooks.md +2 -2
- package/dist/agentic/guides/modules/wms.md +2 -2
- package/dist/agentic/guides/modules/workflows.md +1 -1
- package/dist/agentic/guides/testing-debugging.md +74 -0
- package/dist/agentic/guides/upstream/AGENTS.md +297 -0
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +303 -0
- package/dist/agentic/guides/upstream/manifest.json +8 -0
- package/dist/agentic/shared/AGENTS.md.template +123 -311
- package/dist/agentic/shared/ai/agentic.config.json +1 -0
- package/dist/agentic/shared/ai/harness/README.md +48 -0
- package/dist/agentic/shared/ai/harness/RELEASE.md +57 -0
- package/dist/agentic/shared/ai/harness/cases.json +13487 -0
- package/dist/agentic/shared/ai/harness/cases.schema.json +132 -0
- package/dist/agentic/shared/ai/harness/fixtures/index.json +49 -0
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +162 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +20 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +43 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +110 -0
- package/dist/agentic/shared/ai/harness/release-matrix.json +133 -0
- package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
- package/dist/agentic/shared/ai/harness/result.schema.json +76 -0
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +12 -0
- package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
- package/dist/agentic/shared/ai/harness/validators.json +78 -0
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +1768 -0
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +1096 -0
- package/dist/agentic/shared/ai/review-checklist.md +36 -0
- package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +4 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +20 -241
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +16 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +53 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +13 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +11 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +30 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +12 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +13 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +14 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +28 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +22 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +19 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +16 -601
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +19 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +13 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +14 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +10 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +14 -310
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +17 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +28 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +89 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +16 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +17 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +25 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +11 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +13 -0
- package/dist/agentic/shared/ai/skills/om-help/SKILL.md +15 -97
- package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +17 -0
- package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +20 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +15 -156
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +12 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +24 -708
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +16 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +14 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +15 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +12 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +20 -820
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +19 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +107 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +12 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +83 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +21 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +11 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +22 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +13 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +19 -843
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +18 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +25 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +13 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +38 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +11 -0
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +15 -63
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +15 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +16 -467
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +16 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +14 -0
- package/dist/agentic/shared/ai/skills/tiers.json +137 -20
- package/dist/agentic/shared/ai/skills/tiers.schema.json +52 -40
- package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +174 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +2783 -0
- package/dist/agentic/shared/scripts/execution-sandbox.mjs +329 -0
- package/dist/agentic/shared/scripts/framework-context.mjs +894 -0
- package/dist/agentic/shared/scripts/install-skills.mjs +1029 -0
- package/dist/agentic/shared/scripts/install-skills.sh +4 -694
- package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +222 -0
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1761 -0
- package/dist/lib/__integration__/TC-INT-008.spec.js +87 -8
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-init.js +13 -3
- package/dist/lib/agentic-init.js.map +2 -2
- package/dist/lib/agentic-setup.js +418 -186
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/module-facts-discovery.js +41 -7
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +65 -17
- package/dist/lib/generators/module-facts.js.map +2 -2
- package/dist/lib/generators/module-registry.js +5 -4
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/resolver.js +2 -0
- package/dist/lib/resolver.js.map +2 -2
- package/package.json +5 -5
- package/src/lib/__integration__/TC-INT-008.spec.ts +103 -8
- package/src/lib/__tests__/agentic-init.test.ts +41 -5
- package/src/lib/__tests__/agentic-setup.ownership.test.ts +380 -0
- package/src/lib/agentic-init.ts +15 -3
- package/src/lib/agentic-setup.ts +526 -224
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +6 -0
- package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +1 -0
- package/src/lib/generators/__tests__/module-facts.discovery.test.ts +54 -10
- package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +108 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +18 -0
- package/src/lib/generators/module-facts-discovery.ts +58 -7
- package/src/lib/generators/module-facts.ts +84 -20
- package/src/lib/generators/module-registry.ts +8 -4
- package/src/lib/resolver.ts +3 -0
- package/dist/agentic/guides/cache.md +0 -45
- package/dist/agentic/guides/core.md +0 -453
- package/dist/agentic/guides/events.md +0 -117
- package/dist/agentic/guides/queue.md +0 -53
- package/dist/agentic/guides/search.md +0 -115
- package/dist/agentic/guides/shared.md +0 -108
- package/dist/agentic/guides/ui.md +0 -347
- package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +0 -27
- package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +0 -27
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +0 -260
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +0 -146
- package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +0 -84
- package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +0 -193
- package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +0 -34
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +0 -762
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +0 -50
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +0 -97
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +0 -271
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +0 -101
|
@@ -35,6 +35,12 @@ describe('module-facts BC resolve guard (T2)', () => {
|
|
|
35
35
|
describe(`${moduleId}`, () => {
|
|
36
36
|
const facts = factsByModule[moduleId]
|
|
37
37
|
|
|
38
|
+
it('stamps the exact providing package and version without removing coreVersion', () => {
|
|
39
|
+
expect(facts.sourcePackage).toBe(source.from ?? null)
|
|
40
|
+
expect(facts.sourceVersion).toBe(source.packageVersion ?? null)
|
|
41
|
+
expect(facts).toHaveProperty('coreVersion')
|
|
42
|
+
})
|
|
43
|
+
|
|
38
44
|
// Entity / search / host ids are colon-namespaced under the module by construction
|
|
39
45
|
// and convention; drift here means the builder or a module's data model broke.
|
|
40
46
|
it('colon-namespaces entity / search / host ids under the module and keeps ids unique', () => {
|
|
@@ -52,6 +52,7 @@ describe('module-facts customers fixture (T1 anti-drift guard)', () => {
|
|
|
52
52
|
'customers.companies.list',
|
|
53
53
|
'customers.deals.list',
|
|
54
54
|
'customers.people.list',
|
|
55
|
+
'customers.todos.list',
|
|
55
56
|
])
|
|
56
57
|
expect(facts.hostTokens.entityIds).toEqual(['customers:customer_entity'])
|
|
57
58
|
expect(facts.diTokens).toEqual([])
|
|
@@ -4,11 +4,13 @@ import path from 'node:path'
|
|
|
4
4
|
import type { PackageResolver } from '../../resolver'
|
|
5
5
|
import { discoverPackageModuleSources, hasReadableModuleSource } from '../module-facts-discovery'
|
|
6
6
|
|
|
7
|
-
type FakePackage = { name: string; path: string; modulesPath: string }
|
|
7
|
+
type FakePackage = { name: string; version?: string | null; path: string; modulesPath: string }
|
|
8
|
+
type FakeModuleEntry = { id: string; from?: string }
|
|
8
9
|
|
|
9
|
-
function makeResolver(packages: FakePackage[]): PackageResolver {
|
|
10
|
+
function makeResolver(packages: FakePackage[], enabledModules: FakeModuleEntry[] = []): PackageResolver {
|
|
10
11
|
return {
|
|
11
12
|
discoverPackages: () => packages,
|
|
13
|
+
loadEnabledModules: () => enabledModules,
|
|
12
14
|
} as unknown as PackageResolver
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -62,27 +64,69 @@ describe('module-facts discovery (T1)', () => {
|
|
|
62
64
|
writeModule(path.join(pkgSrc, 'src', 'modules'), 'gamma', 'events.ts')
|
|
63
65
|
|
|
64
66
|
const resolver = makeResolver([
|
|
65
|
-
{ name: '@open-mercato/tagged', path: pkgSrc, modulesPath: path.join(pkgSrc, 'src', 'modules') },
|
|
67
|
+
{ name: '@open-mercato/tagged', version: '0.6.6', path: pkgSrc, modulesPath: path.join(pkgSrc, 'src', 'modules') },
|
|
66
68
|
])
|
|
67
69
|
|
|
68
70
|
const [source] = discoverPackageModuleSources(resolver)
|
|
69
|
-
expect(source).toMatchObject({
|
|
71
|
+
expect(source).toMatchObject({
|
|
72
|
+
moduleId: 'gamma',
|
|
73
|
+
from: '@open-mercato/tagged',
|
|
74
|
+
packageVersion: '0.6.6',
|
|
75
|
+
})
|
|
70
76
|
expect(source.moduleRoot).toBe(path.join(pkgSrc, 'src', 'modules', 'gamma'))
|
|
71
77
|
})
|
|
72
78
|
|
|
73
|
-
it('
|
|
79
|
+
it('fails closed on duplicate module ids and reports every provider independent of discovery order', () => {
|
|
74
80
|
const pkgA = path.join(tmp, 'dup-a')
|
|
75
81
|
writeModule(path.join(pkgA, 'src', 'modules'), 'shared', 'index.ts')
|
|
76
82
|
const pkgB = path.join(tmp, 'dup-b')
|
|
77
83
|
writeModule(path.join(pkgB, 'src', 'modules'), 'shared', 'index.ts')
|
|
78
84
|
|
|
79
|
-
const
|
|
85
|
+
const packages = [
|
|
80
86
|
{ name: '@open-mercato/dup-a', path: pkgA, modulesPath: path.join(pkgA, 'src', 'modules') },
|
|
81
87
|
{ name: '@open-mercato/dup-b', path: pkgB, modulesPath: path.join(pkgB, 'src', 'modules') },
|
|
82
|
-
]
|
|
88
|
+
]
|
|
89
|
+
const expected = '[module-facts] duplicate module id "shared" is provided by '
|
|
90
|
+
+ '@open-mercato/dup-a, @open-mercato/dup-b; src/modules.ts does not select a provider. '
|
|
91
|
+
+ 'Select exactly one matching provider in src/modules.ts.'
|
|
92
|
+
|
|
93
|
+
expect(() => discoverPackageModuleSources(makeResolver(packages))).toThrow(expected)
|
|
94
|
+
expect(() => discoverPackageModuleSources(makeResolver([...packages].reverse()))).toThrow(expected)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('uses the exact src/modules.ts provider even when package discovery order is reversed', () => {
|
|
98
|
+
const pkgA = path.join(tmp, 'selected-a')
|
|
99
|
+
writeModule(path.join(pkgA, 'src', 'modules'), 'shared', 'index.ts')
|
|
100
|
+
const pkgB = path.join(tmp, 'selected-b')
|
|
101
|
+
writeModule(path.join(pkgB, 'src', 'modules'), 'shared', 'index.ts')
|
|
102
|
+
|
|
103
|
+
const packages = [
|
|
104
|
+
{ name: '@open-mercato/selected-a', path: pkgA, modulesPath: path.join(pkgA, 'src', 'modules') },
|
|
105
|
+
{ name: '@open-mercato/selected-b', path: pkgB, modulesPath: path.join(pkgB, 'src', 'modules') },
|
|
106
|
+
]
|
|
107
|
+
const enabled = [{ id: 'shared', from: '@open-mercato/selected-b' }]
|
|
108
|
+
|
|
109
|
+
for (const discoveredPackages of [packages, [...packages].reverse()]) {
|
|
110
|
+
const sources = discoverPackageModuleSources(makeResolver(discoveredPackages, enabled))
|
|
111
|
+
expect(sources).toHaveLength(1)
|
|
112
|
+
expect(sources[0]).toMatchObject({ moduleId: 'shared', from: '@open-mercato/selected-b' })
|
|
113
|
+
expect(sources[0].moduleRoot).toBe(path.join(pkgB, 'src', 'modules', 'shared'))
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('explains that an omitted provider implicitly selects core', () => {
|
|
118
|
+
const pkgA = path.join(tmp, 'implicit-a')
|
|
119
|
+
writeModule(path.join(pkgA, 'src', 'modules'), 'shared', 'index.ts')
|
|
120
|
+
const pkgB = path.join(tmp, 'implicit-b')
|
|
121
|
+
writeModule(path.join(pkgB, 'src', 'modules'), 'shared', 'index.ts')
|
|
122
|
+
|
|
123
|
+
const packages = [
|
|
124
|
+
{ name: '@open-mercato/dup-a', path: pkgA, modulesPath: path.join(pkgA, 'src', 'modules') },
|
|
125
|
+
{ name: '@open-mercato/dup-b', path: pkgB, modulesPath: path.join(pkgB, 'src', 'modules') },
|
|
126
|
+
]
|
|
83
127
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
128
|
+
expect(() => discoverPackageModuleSources(makeResolver(packages, [{ id: 'shared' }]))).toThrow(
|
|
129
|
+
'src/modules.ts omits "from", which defaults to @open-mercato/core',
|
|
130
|
+
)
|
|
87
131
|
})
|
|
88
132
|
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { extractModuleFacts } from '../module-facts'
|
|
5
|
+
|
|
6
|
+
function findCoreSrcRoot(): string {
|
|
7
|
+
let dir = __dirname
|
|
8
|
+
for (let depth = 0; depth < 10; depth += 1) {
|
|
9
|
+
const candidate = path.join(dir, 'packages', 'core', 'src', 'modules')
|
|
10
|
+
if (fs.existsSync(candidate)) return candidate
|
|
11
|
+
dir = path.dirname(dir)
|
|
12
|
+
}
|
|
13
|
+
throw new Error('[internal] could not locate packages/core/src/modules from the test directory')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function write(filePath: string, source: string): void {
|
|
17
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
18
|
+
fs.writeFileSync(filePath, source)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('module-facts extension host extraction', () => {
|
|
22
|
+
const coreSrcRoot = findCoreSrcRoot()
|
|
23
|
+
|
|
24
|
+
it('resolves generated E.<module>.<entity> custom-field declarations from shipped modules', () => {
|
|
25
|
+
const expectedByModule: Record<string, string[]> = {
|
|
26
|
+
attachments: ['attachments:attachment'],
|
|
27
|
+
catalog: [
|
|
28
|
+
'catalog:catalog_product',
|
|
29
|
+
'catalog:catalog_product_variant',
|
|
30
|
+
'catalog:catalog_product_price',
|
|
31
|
+
],
|
|
32
|
+
sales: [
|
|
33
|
+
'sales:sales_order',
|
|
34
|
+
'sales:sales_order_line',
|
|
35
|
+
'sales:sales_order_adjustment',
|
|
36
|
+
'sales:sales_quote',
|
|
37
|
+
'sales:sales_quote_line',
|
|
38
|
+
'sales:sales_quote_adjustment',
|
|
39
|
+
'sales:sales_invoice',
|
|
40
|
+
'sales:sales_invoice_line',
|
|
41
|
+
'sales:sales_payment',
|
|
42
|
+
'sales:sales_payment_allocation',
|
|
43
|
+
'sales:sales_credit_memo',
|
|
44
|
+
'sales:sales_credit_memo_line',
|
|
45
|
+
'sales:sales_shipment',
|
|
46
|
+
'sales:sales_shipment_item',
|
|
47
|
+
'sales:sales_note',
|
|
48
|
+
],
|
|
49
|
+
staff: ['staff:staff_team_member'],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const [moduleId, expected] of Object.entries(expectedByModule)) {
|
|
53
|
+
const facts = extractModuleFacts({ moduleId, coreSrcRoot })
|
|
54
|
+
expect(facts.entities.filter((entity) => entity.customFields).map((entity) => entity.id).sort()).toEqual(
|
|
55
|
+
[...expected].sort(),
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('discovers literal and conditional DataTable IDs outside backend while excluding test sources', () => {
|
|
61
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'module-facts-extension-hosts-'))
|
|
62
|
+
const moduleRoot = path.join(tmpRoot, 'alpha')
|
|
63
|
+
try {
|
|
64
|
+
write(
|
|
65
|
+
path.join(moduleRoot, 'data', 'entities.ts'),
|
|
66
|
+
`@Entity({ tableName: 'alpha_records' })\nexport class AlphaRecord { updatedAt?: Date }\n`,
|
|
67
|
+
)
|
|
68
|
+
write(
|
|
69
|
+
path.join(moduleRoot, 'ce.ts'),
|
|
70
|
+
`export const entities = [{ id: E.alpha.alpha_record, fields: [] }]\n`,
|
|
71
|
+
)
|
|
72
|
+
write(
|
|
73
|
+
path.join(moduleRoot, 'components', 'AlphaTable.tsx'),
|
|
74
|
+
`export const props = {
|
|
75
|
+
perspective: { tableId: kind === 'one' ? 'alpha.one' : 'alpha.two' },
|
|
76
|
+
extensionTableId: 'alpha.extension',
|
|
77
|
+
}\n`,
|
|
78
|
+
)
|
|
79
|
+
write(
|
|
80
|
+
path.join(moduleRoot, 'components', 'Noise.test.tsx'),
|
|
81
|
+
`export const noise = { tableId: 'alpha.test-noise' }\n`,
|
|
82
|
+
)
|
|
83
|
+
write(
|
|
84
|
+
path.join(moduleRoot, 'backend', '__tests__', 'noise.tsx'),
|
|
85
|
+
`export const noise = { extensionTableId: 'alpha.test-directory-noise' }\n`,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const facts = extractModuleFacts({ moduleId: 'alpha', moduleRoot })
|
|
89
|
+
expect(facts.entities).toEqual([
|
|
90
|
+
expect.objectContaining({ id: 'alpha:alpha_record', customFields: true }),
|
|
91
|
+
])
|
|
92
|
+
expect(facts.hostTokens.tableIds).toEqual(['alpha.extension', 'alpha.one', 'alpha.two'])
|
|
93
|
+
} finally {
|
|
94
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true })
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('captures shipped component-level table hosts used by catalog and sales', () => {
|
|
99
|
+
const catalog = extractModuleFacts({ moduleId: 'catalog', coreSrcRoot })
|
|
100
|
+
expect(catalog.hostTokens.tableIds).toEqual([
|
|
101
|
+
'catalog.categories.list',
|
|
102
|
+
'catalog.products.list',
|
|
103
|
+
])
|
|
104
|
+
|
|
105
|
+
const sales = extractModuleFacts({ moduleId: 'sales', coreSrcRoot })
|
|
106
|
+
expect(sales.hostTokens.tableIds).toEqual(['sales.orders', 'sales.quotes'])
|
|
107
|
+
})
|
|
108
|
+
})
|
|
@@ -702,6 +702,24 @@ export function GET() {
|
|
|
702
702
|
expect(output).toContain('features:')
|
|
703
703
|
})
|
|
704
704
|
|
|
705
|
+
it('normalizes singular app-owned integration exports without requiring plural properties', async () => {
|
|
706
|
+
touchFile(
|
|
707
|
+
path.join(tmpDir, 'app', 'src', 'modules', 'mailbox', 'integration.ts'),
|
|
708
|
+
"export const integration = { id: 'mailbox' }\nexport default integration\n",
|
|
709
|
+
)
|
|
710
|
+
const resolver = createMockResolver(tmpDir, [{ id: 'mailbox', from: '@app' }])
|
|
711
|
+
const result = await generateModuleRegistry({ resolver, quiet: true })
|
|
712
|
+
|
|
713
|
+
expect(result.errors).toEqual([])
|
|
714
|
+
for (const file of ['modules.generated.ts', 'modules.runtime.generated.ts']) {
|
|
715
|
+
const output = readGenerated(tmpDir, file)!
|
|
716
|
+
expect(output).toContain('as unknown as { integrations?:')
|
|
717
|
+
expect(output).toContain('.integration ? [')
|
|
718
|
+
expect(output).not.toMatch(/INTEGRATION_[A-Za-z0-9_]+\.integrations/)
|
|
719
|
+
expect(output).not.toMatch(/INTEGRATION_[A-Za-z0-9_]+\.bundles/)
|
|
720
|
+
}
|
|
721
|
+
})
|
|
722
|
+
|
|
705
723
|
it('escapes generated module specifiers for lazy imports', async () => {
|
|
706
724
|
touchFile(
|
|
707
725
|
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'safe_mod', 'subscribers', `o'hare.ts`),
|
|
@@ -29,13 +29,59 @@ export function hasReadableModuleSource(moduleRoot: string): boolean {
|
|
|
29
29
|
return false
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function
|
|
33
|
-
|
|
32
|
+
function resolveDuplicateModuleIds(
|
|
33
|
+
sources: ModuleFactSource[],
|
|
34
|
+
resolver: PackageResolver,
|
|
35
|
+
): ModuleFactSource[] {
|
|
36
|
+
const sourcesByModule = new Map<string, ModuleFactSource[]>()
|
|
37
|
+
for (const source of sources) {
|
|
38
|
+
const matches = sourcesByModule.get(source.moduleId) ?? []
|
|
39
|
+
matches.push(source)
|
|
40
|
+
sourcesByModule.set(source.moduleId, matches)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const duplicates = [...sourcesByModule.entries()].filter(([, matches]) => matches.length > 1)
|
|
44
|
+
if (duplicates.length === 0) return sources
|
|
45
|
+
|
|
46
|
+
const selectedProviders = new Map<string, Set<string>>()
|
|
47
|
+
const implicitCoreSelections = new Set<string>()
|
|
48
|
+
for (const entry of resolver.loadEnabledModules()) {
|
|
49
|
+
const providers = selectedProviders.get(entry.id) ?? new Set<string>()
|
|
50
|
+
providers.add(entry.from ?? '@open-mercato/core')
|
|
51
|
+
selectedProviders.set(entry.id, providers)
|
|
52
|
+
if (entry.from === undefined) implicitCoreSelections.add(entry.id)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const selectedSources = new Map<string, ModuleFactSource>()
|
|
56
|
+
for (const [moduleId, matches] of duplicates) {
|
|
57
|
+
const providers = [...new Set(matches.map((source) => source.from ?? '<unknown>'))]
|
|
58
|
+
.sort((left, right) => left.localeCompare(right))
|
|
59
|
+
const configuredProviders = [...(selectedProviders.get(moduleId) ?? [])]
|
|
60
|
+
.sort((left, right) => left.localeCompare(right))
|
|
61
|
+
const selected = configuredProviders.length === 1
|
|
62
|
+
? matches.filter((source) => source.from === configuredProviders[0])
|
|
63
|
+
: []
|
|
64
|
+
|
|
65
|
+
if (selected.length !== 1) {
|
|
66
|
+
const configured = configuredProviders.length > 0
|
|
67
|
+
? implicitCoreSelections.has(moduleId)
|
|
68
|
+
? '; src/modules.ts omits "from", which defaults to @open-mercato/core'
|
|
69
|
+
: `; src/modules.ts selects ${configuredProviders.join(', ')}`
|
|
70
|
+
: '; src/modules.ts does not select a provider'
|
|
71
|
+
throw new Error(
|
|
72
|
+
`[module-facts] duplicate module id "${moduleId}" is provided by ${providers.join(', ')}${configured}. `
|
|
73
|
+
+ 'Select exactly one matching provider in src/modules.ts.',
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
selectedSources.set(moduleId, selected[0])
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const emitted = new Set<string>()
|
|
34
80
|
const result: ModuleFactSource[] = []
|
|
35
81
|
for (const source of sources) {
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
result.push(source)
|
|
82
|
+
if (emitted.has(source.moduleId)) continue
|
|
83
|
+
emitted.add(source.moduleId)
|
|
84
|
+
result.push(selectedSources.get(source.moduleId) ?? source)
|
|
39
85
|
}
|
|
40
86
|
return result
|
|
41
87
|
}
|
|
@@ -51,8 +97,13 @@ export function discoverPackageModuleSources(resolver: PackageResolver): ModuleF
|
|
|
51
97
|
for (const discovered of discoverModulesInPackage(pkg.path)) {
|
|
52
98
|
const moduleRoot = path.join(pkg.modulesPath, discovered.moduleId)
|
|
53
99
|
if (!hasReadableModuleSource(moduleRoot)) continue
|
|
54
|
-
sources.push({
|
|
100
|
+
sources.push({
|
|
101
|
+
moduleId: discovered.moduleId,
|
|
102
|
+
moduleRoot,
|
|
103
|
+
from: pkg.name,
|
|
104
|
+
packageVersion: pkg.version ?? null,
|
|
105
|
+
})
|
|
55
106
|
}
|
|
56
107
|
}
|
|
57
|
-
return
|
|
108
|
+
return resolveDuplicateModuleIds(sources, resolver)
|
|
58
109
|
}
|
|
@@ -40,6 +40,8 @@ export interface ModuleFacts {
|
|
|
40
40
|
title: string | null
|
|
41
41
|
description: string | null
|
|
42
42
|
coreVersion: string | null
|
|
43
|
+
sourcePackage: string | null
|
|
44
|
+
sourceVersion: string | null
|
|
43
45
|
entities: ModuleEntityFact[]
|
|
44
46
|
events: ModuleEventFact[]
|
|
45
47
|
aclFeatures: string[]
|
|
@@ -64,6 +66,8 @@ export interface ExtractModuleFactsOptions {
|
|
|
64
66
|
/** Explicit module source directory. When set it overrides `coreSrcRoot + moduleId`. */
|
|
65
67
|
moduleRoot?: string
|
|
66
68
|
coreVersion?: string | null
|
|
69
|
+
sourcePackage?: string | null
|
|
70
|
+
sourceVersion?: string | null
|
|
67
71
|
registryPath?: string | null
|
|
68
72
|
registrySource?: string | null
|
|
69
73
|
}
|
|
@@ -73,6 +77,7 @@ export interface ModuleFactSource {
|
|
|
73
77
|
moduleId: string
|
|
74
78
|
moduleRoot: string
|
|
75
79
|
from?: string
|
|
80
|
+
packageVersion?: string | null
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
function readSourceFile(filePath: string): ts.SourceFile | null {
|
|
@@ -159,10 +164,41 @@ function collectCustomFieldEntityIds(ceFilePath: string | null): Set<string> {
|
|
|
159
164
|
const sourceFile = readSourceFile(ceFilePath)
|
|
160
165
|
if (!sourceFile) return result
|
|
161
166
|
|
|
167
|
+
const readEntityId = (expression: ts.Expression): string | undefined => {
|
|
168
|
+
const current = unwrapExpression(expression)
|
|
169
|
+
if (ts.isStringLiteralLike(current)) {
|
|
170
|
+
return current.text.includes(':') ? current.text : undefined
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const readAccessPath = (candidate: ts.Expression): string[] | null => {
|
|
174
|
+
const access = unwrapExpression(candidate)
|
|
175
|
+
if (ts.isIdentifier(access)) return [access.text]
|
|
176
|
+
if (ts.isPropertyAccessExpression(access)) {
|
|
177
|
+
const parent = readAccessPath(access.expression)
|
|
178
|
+
return parent ? [...parent, access.name.text] : null
|
|
179
|
+
}
|
|
180
|
+
if (ts.isElementAccessExpression(access) && access.argumentExpression) {
|
|
181
|
+
const parent = readAccessPath(access.expression)
|
|
182
|
+
const key = unwrapExpression(access.argumentExpression)
|
|
183
|
+
return parent && ts.isStringLiteralLike(key) ? [...parent, key.text] : null
|
|
184
|
+
}
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const accessPath = readAccessPath(current)
|
|
189
|
+
if (accessPath?.length === 3 && accessPath[0] === 'E') {
|
|
190
|
+
return `${accessPath[1]}:${accessPath[2]}`
|
|
191
|
+
}
|
|
192
|
+
return undefined
|
|
193
|
+
}
|
|
194
|
+
|
|
162
195
|
const visit = (node: ts.Node): void => {
|
|
163
196
|
if (ts.isObjectLiteralExpression(node)) {
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
197
|
+
const initializer = getObjectPropertyInitializer(node, 'id')
|
|
198
|
+
if (initializer) {
|
|
199
|
+
const id = readEntityId(initializer)
|
|
200
|
+
if (id) result.add(id)
|
|
201
|
+
}
|
|
166
202
|
}
|
|
167
203
|
node.forEachChild(visit)
|
|
168
204
|
}
|
|
@@ -666,36 +702,50 @@ function extractCli(cliFilePath: string | null, warnings: string[]): string[] {
|
|
|
666
702
|
function listSourceFilesRecursive(directory: string): string[] {
|
|
667
703
|
const files: string[] = []
|
|
668
704
|
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
669
|
-
if (entry.name === '__tests__' || entry.name === 'node_modules') continue
|
|
705
|
+
if (entry.name === '__tests__' || entry.name === '__mocks__' || entry.name === 'node_modules') continue
|
|
670
706
|
const fullPath = path.join(directory, entry.name)
|
|
671
707
|
if (entry.isDirectory()) {
|
|
672
708
|
files.push(...listSourceFilesRecursive(fullPath))
|
|
673
|
-
} else if (
|
|
709
|
+
} else if (
|
|
710
|
+
/\.tsx?$/.test(entry.name)
|
|
711
|
+
&& !entry.name.endsWith('.d.ts')
|
|
712
|
+
&& !/\.(?:test|spec)\.tsx?$/.test(entry.name)
|
|
713
|
+
) {
|
|
674
714
|
files.push(fullPath)
|
|
675
715
|
}
|
|
676
716
|
}
|
|
677
717
|
return files
|
|
678
718
|
}
|
|
679
719
|
|
|
680
|
-
function extractTableIds(
|
|
681
|
-
if (!fs.existsSync(
|
|
720
|
+
function extractTableIds(moduleRoot: string): string[] {
|
|
721
|
+
if (!fs.existsSync(moduleRoot)) return []
|
|
682
722
|
|
|
683
723
|
const tableIds: string[] = []
|
|
684
724
|
const seen = new Set<string>()
|
|
685
|
-
|
|
725
|
+
const collectLiteralValues = (expression: ts.Expression): string[] => {
|
|
726
|
+
const current = unwrapExpression(expression)
|
|
727
|
+
if (ts.isStringLiteralLike(current)) return [current.text]
|
|
728
|
+
if (ts.isConditionalExpression(current)) {
|
|
729
|
+
return [
|
|
730
|
+
...collectLiteralValues(current.whenTrue),
|
|
731
|
+
...collectLiteralValues(current.whenFalse),
|
|
732
|
+
]
|
|
733
|
+
}
|
|
734
|
+
return []
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
for (const filePath of listSourceFilesRecursive(moduleRoot)) {
|
|
686
738
|
const sourceFile = readSourceFile(filePath)
|
|
687
739
|
if (!sourceFile) continue
|
|
688
740
|
const visit = (node: ts.Node): void => {
|
|
689
741
|
if (ts.isPropertyAssignment(node)) {
|
|
690
742
|
const propertyName = getPropertyName(node)
|
|
691
|
-
if (
|
|
692
|
-
(
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
seen.add(value)
|
|
698
|
-
tableIds.push(value)
|
|
743
|
+
if (propertyName === 'tableId' || propertyName === 'extensionTableId') {
|
|
744
|
+
for (const value of collectLiteralValues(node.initializer)) {
|
|
745
|
+
if (value && !seen.has(value)) {
|
|
746
|
+
seen.add(value)
|
|
747
|
+
tableIds.push(value)
|
|
748
|
+
}
|
|
699
749
|
}
|
|
700
750
|
}
|
|
701
751
|
}
|
|
@@ -724,7 +774,7 @@ function extractModuleMeta(indexFilePath: string | null): { title: string | null
|
|
|
724
774
|
}
|
|
725
775
|
|
|
726
776
|
export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFacts {
|
|
727
|
-
const { moduleId, coreVersion = null } = options
|
|
777
|
+
const { moduleId, coreVersion = null, sourcePackage = null, sourceVersion = null } = options
|
|
728
778
|
const moduleRoot = options.moduleRoot
|
|
729
779
|
?? (options.coreSrcRoot ? path.join(options.coreSrcRoot, moduleId) : null)
|
|
730
780
|
if (!moduleRoot) {
|
|
@@ -743,7 +793,6 @@ export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFa
|
|
|
743
793
|
const notificationsFilePath = resolveConventionFile(moduleRoot, 'notifications')
|
|
744
794
|
const cliFilePath = resolveConventionFile(moduleRoot, 'cli')
|
|
745
795
|
const indexFilePath = resolveConventionFile(moduleRoot, 'index')
|
|
746
|
-
const backendDir = path.join(moduleRoot, 'backend')
|
|
747
796
|
|
|
748
797
|
const warnings: string[] = []
|
|
749
798
|
const { title, description } = extractModuleMeta(indexFilePath)
|
|
@@ -760,7 +809,7 @@ export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFa
|
|
|
760
809
|
const cli = extractCli(cliFilePath, warnings)
|
|
761
810
|
const hostTokens: ModuleHostTokens = {
|
|
762
811
|
entityIds: extractHostEntityIds(entities),
|
|
763
|
-
tableIds: extractTableIds(
|
|
812
|
+
tableIds: extractTableIds(moduleRoot),
|
|
764
813
|
}
|
|
765
814
|
|
|
766
815
|
return {
|
|
@@ -768,6 +817,8 @@ export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFa
|
|
|
768
817
|
title,
|
|
769
818
|
description,
|
|
770
819
|
coreVersion,
|
|
820
|
+
sourcePackage,
|
|
821
|
+
sourceVersion,
|
|
771
822
|
entities,
|
|
772
823
|
events,
|
|
773
824
|
aclFeatures,
|
|
@@ -791,6 +842,8 @@ export interface ModuleFactsJsonEntry {
|
|
|
791
842
|
title: string | null
|
|
792
843
|
description: string | null
|
|
793
844
|
coreVersion: string | null
|
|
845
|
+
sourcePackage: string | null
|
|
846
|
+
sourceVersion: string | null
|
|
794
847
|
entities: ModuleEntityFact[]
|
|
795
848
|
events: ModuleFactsJsonEvent[]
|
|
796
849
|
aclFeatures: string[]
|
|
@@ -804,7 +857,14 @@ export interface ModuleFactsJsonEntry {
|
|
|
804
857
|
|
|
805
858
|
const EMPTY_SECTION_MARKER = '_none_'
|
|
806
859
|
|
|
807
|
-
function renderVersionStamp(
|
|
860
|
+
function renderVersionStamp(
|
|
861
|
+
coreVersion: string | null,
|
|
862
|
+
sourcePackage: string | null,
|
|
863
|
+
sourceVersion: string | null,
|
|
864
|
+
): string {
|
|
865
|
+
if (sourcePackage) {
|
|
866
|
+
return `<!-- generated from ${sourcePackage} ${sourceVersion || '<unknown>'}; core ${coreVersion || '<unknown>'} — R1 staleness stamp -->`
|
|
867
|
+
}
|
|
808
868
|
const version = coreVersion && coreVersion.length > 0 ? coreVersion : '<unknown>'
|
|
809
869
|
return `<!-- generated from @open-mercato/core ${version} — R1 staleness stamp -->`
|
|
810
870
|
}
|
|
@@ -873,7 +933,7 @@ function renderHostTokensSection(hostTokens: ModuleHostTokens): string {
|
|
|
873
933
|
export function renderModuleFactsMarkdown(facts: ModuleFacts): string {
|
|
874
934
|
const sections = [
|
|
875
935
|
`# ${facts.module} — module facts (generated, do not edit)`,
|
|
876
|
-
renderVersionStamp(facts.coreVersion),
|
|
936
|
+
renderVersionStamp(facts.coreVersion, facts.sourcePackage, facts.sourceVersion),
|
|
877
937
|
'',
|
|
878
938
|
renderEntitiesSection(facts.entities),
|
|
879
939
|
'',
|
|
@@ -902,6 +962,8 @@ export function toModuleFactsJsonEntry(facts: ModuleFacts): ModuleFactsJsonEntry
|
|
|
902
962
|
title: facts.title,
|
|
903
963
|
description: facts.description,
|
|
904
964
|
coreVersion: facts.coreVersion,
|
|
965
|
+
sourcePackage: facts.sourcePackage,
|
|
966
|
+
sourceVersion: facts.sourceVersion,
|
|
905
967
|
entities: facts.entities,
|
|
906
968
|
events: facts.events.map((event) => ({ id: event.id, category: event.category, entity: event.entity })),
|
|
907
969
|
aclFeatures: facts.aclFeatures,
|
|
@@ -967,6 +1029,8 @@ export function extractAllModuleFacts(options: ExtractAllModuleFactsOptions): Ex
|
|
|
967
1029
|
moduleId: source.moduleId,
|
|
968
1030
|
moduleRoot: source.moduleRoot,
|
|
969
1031
|
coreVersion: options.coreVersion ?? null,
|
|
1032
|
+
sourcePackage: source.from ?? null,
|
|
1033
|
+
sourceVersion: source.packageVersion ?? null,
|
|
970
1034
|
registryPath: options.registryPath ?? null,
|
|
971
1035
|
registrySource: options.registrySource ?? null,
|
|
972
1036
|
})
|
|
@@ -3059,6 +3059,10 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
|
|
|
3059
3059
|
if (fields) fieldsImportName = fields.importName
|
|
3060
3060
|
}
|
|
3061
3061
|
|
|
3062
|
+
const integrationExports = integrationImportName
|
|
3063
|
+
? `(${integrationImportName} as unknown as { integrations?: import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]; integration?: import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition; bundles?: import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]; bundle?: import('@open-mercato/shared/modules/integrations/types').IntegrationBundle })`
|
|
3064
|
+
: null
|
|
3065
|
+
|
|
3062
3066
|
// 13. Pages: backend
|
|
3063
3067
|
{
|
|
3064
3068
|
const beApp = path.join(roots.appBase, 'backend')
|
|
@@ -3167,8 +3171,8 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
|
|
|
3167
3171
|
${dashboardWidgets.length ? `dashboardWidgets: [${dashboardWidgets.join(', ')}],` : ''}
|
|
3168
3172
|
${setupImportName ? `setup: (${setupImportName}.default ?? ${setupImportName}.setup) || undefined,` : ''}
|
|
3169
3173
|
${encryptionImportName ? `defaultEncryptionMaps: ((${encryptionImportName}.default ?? ${encryptionImportName}.defaultEncryptionMaps) as import('@open-mercato/shared/modules/encryption').ModuleEncryptionMap[]) || [],` : ''}
|
|
3170
|
-
${
|
|
3171
|
-
${
|
|
3174
|
+
${integrationExports ? `integrations: ((${integrationExports}.integrations ?? (${integrationExports}.integration ? [${integrationExports}.integration] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]),` : ''}
|
|
3175
|
+
${integrationExports ? `bundles: ((${integrationExports}.bundles ?? (${integrationExports}.bundle ? [${integrationExports}.bundle] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]),` : ''}
|
|
3172
3176
|
}`)
|
|
3173
3177
|
runtimeModuleDecls.push(`{
|
|
3174
3178
|
id: ${toLiteral(modId)},
|
|
@@ -3185,8 +3189,8 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
|
|
|
3185
3189
|
${customEntitiesImportName ? `customEntities: ((${customEntitiesImportName}.default ?? ${customEntitiesImportName}.entities) as any) || [],` : ''}
|
|
3186
3190
|
${setupImportName ? `setup: (${setupImportName}.default ?? ${setupImportName}.setup) || undefined,` : ''}
|
|
3187
3191
|
${encryptionImportName ? `defaultEncryptionMaps: ((${encryptionImportName}.default ?? ${encryptionImportName}.defaultEncryptionMaps) as import('@open-mercato/shared/modules/encryption').ModuleEncryptionMap[]) || [],` : ''}
|
|
3188
|
-
${
|
|
3189
|
-
${
|
|
3192
|
+
${integrationExports ? `integrations: ((${integrationExports}.integrations ?? (${integrationExports}.integration ? [${integrationExports}.integration] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]),` : ''}
|
|
3193
|
+
${integrationExports ? `bundles: ((${integrationExports}.bundles ?? (${integrationExports}.bundle ? [${integrationExports}.bundle] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]),` : ''}
|
|
3190
3194
|
}`)
|
|
3191
3195
|
}
|
|
3192
3196
|
|
package/src/lib/resolver.ts
CHANGED
|
@@ -29,6 +29,7 @@ export type ModuleEntry = {
|
|
|
29
29
|
|
|
30
30
|
export type PackageInfo = {
|
|
31
31
|
name: string
|
|
32
|
+
version?: string | null
|
|
32
33
|
path: string
|
|
33
34
|
modulesPath: string
|
|
34
35
|
}
|
|
@@ -356,6 +357,7 @@ function discoverPackagesInMonorepo(rootDir: string): PackageInfo[] {
|
|
|
356
357
|
if (fs.existsSync(modulesPath)) {
|
|
357
358
|
packages.push({
|
|
358
359
|
name: pkgJson.name || `@open-mercato/${entry.name}`,
|
|
360
|
+
version: typeof pkgJson.version === 'string' ? pkgJson.version : null,
|
|
359
361
|
path: pkgPath,
|
|
360
362
|
modulesPath,
|
|
361
363
|
})
|
|
@@ -391,6 +393,7 @@ function discoverPackagesInNodeModules(rootDir: string): PackageInfo[] {
|
|
|
391
393
|
if (fs.existsSync(modulesPath)) {
|
|
392
394
|
packages.push({
|
|
393
395
|
name: pkgJson.name || `@open-mercato/${entry.name}`,
|
|
396
|
+
version: typeof pkgJson.version === 'string' ? pkgJson.version : null,
|
|
394
397
|
path: pkgPath,
|
|
395
398
|
modulesPath,
|
|
396
399
|
})
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Cache Package — Standalone Developer Guide
|
|
2
|
-
|
|
3
|
-
`@open-mercato/cache` provides tenant-scoped caching with tag-based invalidation. MUST NOT use raw Redis, SQLite, or in-memory caching directly.
|
|
4
|
-
|
|
5
|
-
## Strategy Selection
|
|
6
|
-
|
|
7
|
-
| Strategy | When | Config |
|
|
8
|
-
|----------|------|--------|
|
|
9
|
-
| Memory | Development, single-process | Default (no config) |
|
|
10
|
-
| SQLite | Single-server production | `CACHE_STRATEGY=sqlite` |
|
|
11
|
-
| Redis | Multi-server, shared cache | `CACHE_STRATEGY=redis` |
|
|
12
|
-
|
|
13
|
-
## Usage
|
|
14
|
-
|
|
15
|
-
Always resolve via DI:
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
const cacheService = container.resolve('cacheService')
|
|
19
|
-
|
|
20
|
-
// Set with tags for targeted invalidation
|
|
21
|
-
await cacheService.set(`${tenantId}:my_module:stats`, value, {
|
|
22
|
-
tags: [`tenant:${tenantId}`, 'my_module'],
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
// Get
|
|
26
|
-
const cached = await cacheService.get(`${tenantId}:my_module:stats`)
|
|
27
|
-
|
|
28
|
-
// Invalidate by tag (clears all entries with that tag)
|
|
29
|
-
await cacheService.invalidateTag('my_module')
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## MUST Rules
|
|
33
|
-
|
|
34
|
-
1. **MUST resolve via DI** — `container.resolve('cacheService')`, never instantiate directly
|
|
35
|
-
2. **MUST scope to tenant** — include `tenantId` in cache keys
|
|
36
|
-
3. **MUST use tag-based invalidation** for CRUD side effects
|
|
37
|
-
4. **MUST NOT cache sensitive data** without encryption
|
|
38
|
-
|
|
39
|
-
## Adding Cache to a Module
|
|
40
|
-
|
|
41
|
-
1. Resolve `cacheService` from DI in your service or route handler
|
|
42
|
-
2. Define cache keys: `${tenantId}:${module}:${identifier}`
|
|
43
|
-
3. Tag entries: `{ tags: ['tenant:123', 'my_module'] }`
|
|
44
|
-
4. Add cache invalidation to CRUD side effects (`emitCrudSideEffects` with `cacheAliases`)
|
|
45
|
-
5. Test with `CACHE_STRATEGY=memory` (default in dev)
|