@open-mercato/cli 0.6.8-develop.6930.1.1e5976efc3 → 0.6.8-develop.6940.1.177ea30c6e
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 +65 -1
- package/build.mjs +57 -7
- package/dist/agentic/guides/backend-ui.md +7 -1
- package/dist/agentic/guides/module-facts.json +565 -224
- package/dist/agentic/guides/module-facts.v2.json +122933 -0
- package/dist/agentic/guides/modules/ai_assistant.md +2 -2
- package/dist/agentic/guides/modules/api_docs.md +2 -2
- package/dist/agentic/guides/modules/api_keys.md +2 -2
- 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 +2 -2
- package/dist/agentic/guides/modules/business_rules.md +2 -2
- package/dist/agentic/guides/modules/catalog.md +11 -4
- package/dist/agentic/guides/modules/channel_gmail.md +2 -2
- package/dist/agentic/guides/modules/channel_imap.md +2 -2
- package/dist/agentic/guides/modules/checkout.md +8 -2
- package/dist/agentic/guides/modules/communication_channels.md +6 -6
- package/dist/agentic/guides/modules/configs.md +2 -2
- package/dist/agentic/guides/modules/content.md +2 -2
- package/dist/agentic/guides/modules/currencies.md +2 -2
- package/dist/agentic/guides/modules/customer_accounts.md +2 -2
- package/dist/agentic/guides/modules/customers.md +8 -8
- package/dist/agentic/guides/modules/dashboards.md +12 -12
- package/dist/agentic/guides/modules/data_sync.md +4 -4
- package/dist/agentic/guides/modules/design_system.md +2 -2
- package/dist/agentic/guides/modules/dictionaries.md +2 -2
- package/dist/agentic/guides/modules/directory.md +2 -2
- package/dist/agentic/guides/modules/entities.md +3 -3
- package/dist/agentic/guides/modules/eudr.md +3 -3
- package/dist/agentic/guides/modules/events.md +2 -2
- package/dist/agentic/guides/modules/feature_toggles.md +2 -2
- package/dist/agentic/guides/modules/gateway_stripe.md +2 -2
- package/dist/agentic/guides/modules/generators.md +2 -2
- package/dist/agentic/guides/modules/inbox_ops.md +2 -2
- package/dist/agentic/guides/modules/integrations.md +5 -2
- package/dist/agentic/guides/modules/messages.md +2 -2
- package/dist/agentic/guides/modules/notifications.md +2 -2
- package/dist/agentic/guides/modules/onboarding.md +2 -2
- package/dist/agentic/guides/modules/payment_gateways.md +7 -3
- package/dist/agentic/guides/modules/perspectives.md +2 -2
- package/dist/agentic/guides/modules/planner.md +2 -2
- package/dist/agentic/guides/modules/portal.md +2 -2
- package/dist/agentic/guides/modules/progress.md +2 -2
- package/dist/agentic/guides/modules/query_index.md +2 -2
- package/dist/agentic/guides/modules/record_locks.md +3 -3
- package/dist/agentic/guides/modules/resources.md +2 -2
- package/dist/agentic/guides/modules/sales.md +13 -4
- package/dist/agentic/guides/modules/scheduler.md +2 -2
- package/dist/agentic/guides/modules/search.md +4 -4
- package/dist/agentic/guides/modules/security.md +3 -3
- package/dist/agentic/guides/modules/shipping_carriers.md +2 -2
- package/dist/agentic/guides/modules/sso.md +2 -2
- package/dist/agentic/guides/modules/staff.md +7 -4
- package/dist/agentic/guides/modules/storage_s3.md +2 -2
- package/dist/agentic/guides/modules/sync_akeneo.md +4 -4
- package/dist/agentic/guides/modules/sync_excel.md +2 -2
- package/dist/agentic/guides/modules/system_status_overlays.md +2 -2
- package/dist/agentic/guides/modules/translations.md +2 -2
- package/dist/agentic/guides/modules/webhooks.md +2 -2
- package/dist/agentic/guides/modules/wms.md +31 -4
- package/dist/agentic/guides/modules/workflows.md +2 -2
- package/dist/agentic/guides/reference-module-facts.json +8335 -0
- package/dist/agentic/guides/reference-modules/example.md +489 -0
- package/dist/agentic/guides/spec-delivery.md +34 -0
- package/dist/agentic/guides/testing-debugging.md +2 -1
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +3 -2
- package/dist/agentic/guides/upstream/manifest.json +2 -2
- package/dist/agentic/shared/AGENTS.md.template +10 -14
- package/dist/agentic/shared/ai/harness/README.md +4 -4
- package/dist/agentic/shared/ai/harness/RELEASE.md +4 -4
- package/dist/agentic/shared/ai/harness/cases.json +11350 -5088
- package/dist/agentic/shared/ai/harness/cases.schema.json +70 -7
- package/dist/agentic/shared/ai/harness/design-system-inventory.json +6697 -0
- package/dist/agentic/shared/ai/harness/fixtures/index.json +3 -1
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +4 -0
- package/dist/agentic/shared/ai/harness/knowledge-change.schema.json +228 -0
- package/dist/agentic/shared/ai/harness/release-matrix.json +9 -4
- package/dist/agentic/shared/ai/harness/result.schema.json +60 -0
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +10 -0
- package/dist/agentic/shared/ai/harness/source-link-inventory.json +3231 -0
- package/dist/agentic/shared/ai/harness/validators.json +8 -4
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +5 -2
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +3 -0
- package/dist/agentic/shared/ai/harness/writable-spec-oracles.mjs +879 -0
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.7-to-0.7.0/SKILL.md +102 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +15 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +6 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +4 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +2 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +2 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +3 -1
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +2 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +2 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +3 -3
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +1 -1
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/knowledge-change.md +133 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +11 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +1 -1
- package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +2 -2
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +14 -1
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +2 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +19 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +1 -4
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +24 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +3 -1
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +13 -0
- package/dist/agentic/shared/ai/skills/tiers.json +2 -1
- package/dist/agentic/shared/ai/specs/2026-08-06-reference-module-activation.md +248 -0
- package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +39 -2
- package/dist/agentic/shared/scripts/design-source-contract.mjs +108 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +935 -28
- package/dist/agentic/shared/scripts/framework-context.mjs +4 -1
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1 -1
- package/dist/agentic/shared/scripts/validate-knowledge-change.mjs +1511 -0
- package/dist/lib/__integration__/TC-INT-008.spec.js +1 -0
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-setup.js +9 -1
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/module-extension-facts.js +93 -26
- package/dist/lib/generators/module-extension-facts.js.map +3 -3
- package/dist/lib/generators/module-facts-discovery.js +23 -0
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +969 -41
- package/dist/lib/generators/module-facts.js.map +2 -2
- package/dist/lib/generators/module-override-targets.js +35 -11
- package/dist/lib/generators/module-override-targets.js.map +2 -2
- package/dist/lib/generators/module-registry.js +105 -2
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/resolver.js +5 -0
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/testing/integration-discovery.js +34 -0
- package/dist/lib/testing/integration-discovery.js.map +2 -2
- package/dist/lib/testing/integration.js +10 -2
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +4 -4
- package/dist/mercato.js.map +2 -2
- package/package.json +6 -6
- package/src/lib/__integration__/TC-INT-008.spec.ts +4 -2
- package/src/lib/__tests__/resolve-environment.test.ts +18 -0
- package/src/lib/agentic-setup.ts +10 -2
- package/src/lib/generators/__tests__/helpers/override-target-diagnostic-fixtures.ts +150 -0
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +290 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +72 -7
- package/src/lib/generators/__tests__/module-facts.computed-di-token.test.ts +88 -0
- package/src/lib/generators/__tests__/module-facts.example-fact-coverage.test.ts +502 -0
- package/src/lib/generators/__tests__/module-facts.local-reference.test.ts +414 -0
- package/src/lib/generators/__tests__/module-facts.search-entity-id.test.ts +151 -0
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +42 -1
- package/src/lib/generators/__tests__/module-override-targets.diagnostic-wiring.test.ts +92 -0
- package/src/lib/generators/__tests__/module-override-targets.test.ts +24 -0
- package/src/lib/generators/__tests__/module-override-targets.unknown-mode.test.ts +117 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +60 -0
- package/src/lib/generators/module-extension-facts.ts +181 -25
- package/src/lib/generators/module-facts-discovery.ts +46 -0
- package/src/lib/generators/module-facts.ts +1330 -86
- package/src/lib/generators/module-override-targets.ts +70 -26
- package/src/lib/generators/module-registry.ts +135 -2
- package/src/lib/resolver.ts +6 -1
- package/src/lib/testing/__tests__/integration-discovery.test.ts +35 -0
- package/src/lib/testing/integration-discovery.ts +50 -0
- package/src/lib/testing/integration.ts +10 -2
- package/src/mercato.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/cli",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.6940.1.177ea30c6e",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
"@mikro-orm/decorators": "^7.1.8",
|
|
61
61
|
"@mikro-orm/migrations": "^7.1.8",
|
|
62
62
|
"@mikro-orm/postgresql": "^7.1.8",
|
|
63
|
-
"@open-mercato/queue": "0.6.8-develop.
|
|
64
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
65
|
-
"@open-mercato/telemetry": "0.6.8-develop.
|
|
63
|
+
"@open-mercato/queue": "0.6.8-develop.6940.1.177ea30c6e",
|
|
64
|
+
"@open-mercato/shared": "0.6.8-develop.6940.1.177ea30c6e",
|
|
65
|
+
"@open-mercato/telemetry": "0.6.8-develop.6940.1.177ea30c6e",
|
|
66
66
|
"cross-spawn": "^7.0.6",
|
|
67
67
|
"pg": "8.22.0",
|
|
68
68
|
"semver": "^7.8.5",
|
|
@@ -73,10 +73,10 @@
|
|
|
73
73
|
"typescript-js": "npm:typescript@6.0.3"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
76
|
+
"@open-mercato/shared": "0.6.8-develop.6940.1.177ea30c6e"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
79
|
+
"@open-mercato/shared": "0.6.8-develop.6940.1.177ea30c6e",
|
|
80
80
|
"@types/jest": "^30.0.0",
|
|
81
81
|
"jest": "^30.4.2",
|
|
82
82
|
"ts-jest": "^29.4.12"
|
|
@@ -247,12 +247,14 @@ function expectedGuideOutputNames(): string[] {
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
// Generated fact-sheet artifacts (spec 2026-06-27-ts-morph-module-fact-sheets):
|
|
250
|
-
// the module-facts.json
|
|
251
|
-
// fixture's enabled modules. The legacy
|
|
250
|
+
// the v1 module-facts.json and corrected module-facts.v2.json sidecars are copied
|
|
251
|
+
// as-is and fact-sheets are filtered to the fixture's enabled modules. The legacy
|
|
252
|
+
// core.<module>.md redirect stubs are no longer
|
|
252
253
|
// emitted (#3754). framework-extension-points.md is likewise generated into
|
|
253
254
|
// dist/agentic/guides by both build.mjs pipelines (#4810) rather than checked in
|
|
254
255
|
// alongside the static conceptual guides, so it needs an explicit entry here.
|
|
255
256
|
collected.add('module-facts.json')
|
|
257
|
+
collected.add('module-facts.v2.json')
|
|
256
258
|
collected.add('framework-extension-points.md')
|
|
257
259
|
collected.add('upstream/AGENTS.md')
|
|
258
260
|
collected.add('upstream/BACKWARD_COMPATIBILITY.md')
|
|
@@ -208,6 +208,24 @@ describe('resolveEnvironment', () => {
|
|
|
208
208
|
})
|
|
209
209
|
})
|
|
210
210
|
|
|
211
|
+
describe('monorepo — materialized workspace node_modules', () => {
|
|
212
|
+
it('prefers checked-out package sources when the workspace package is a real directory', async () => {
|
|
213
|
+
await makeDir(tempRoot, 'node_modules', '@open-mercato', 'core')
|
|
214
|
+
await makeDir(tempRoot, 'packages', 'core', 'src', 'modules')
|
|
215
|
+
await makeDir(tempRoot, 'apps', 'mercato')
|
|
216
|
+
const packageDir = await makeDir(tempRoot, 'packages', 'create-app')
|
|
217
|
+
|
|
218
|
+
const env = resolveEnvironment(packageDir)
|
|
219
|
+
|
|
220
|
+
expect(env.mode).toBe('monorepo')
|
|
221
|
+
expect(normalizePath(env.rootDir)).toBe(normalizePath(tempRoot))
|
|
222
|
+
expect(normalizePath(env.appDir)).toBe(normalizePath(path.join(tempRoot, 'apps', 'mercato')))
|
|
223
|
+
expect(normalizePath(env.packageRoot('@open-mercato/core'))).toBe(
|
|
224
|
+
normalizePath(path.join(tempRoot, 'packages', 'core')),
|
|
225
|
+
)
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
211
229
|
describe('no @open-mercato packages present', () => {
|
|
212
230
|
it('falls back to standalone mode with rootDir = cwd', async () => {
|
|
213
231
|
const env = resolveEnvironment(tempRoot)
|
package/src/lib/agentic-setup.ts
CHANGED
|
@@ -439,7 +439,9 @@ function finalizeHarnessManifest(config: AgenticConfig, selectedTools: string[])
|
|
|
439
439
|
...targetPathsForTree(join(srcDir, 'scripts'), join(targetDir, 'scripts')),
|
|
440
440
|
])
|
|
441
441
|
for (const file of readdirSync(GUIDES_DIR)) {
|
|
442
|
-
if (file.endsWith('.md') || file === 'module-facts.json'
|
|
442
|
+
if (file.endsWith('.md') || file === 'module-facts.json' || file === 'module-facts.v2.json') {
|
|
443
|
+
paths.add(join(targetDir, '.ai', 'guides', file))
|
|
444
|
+
}
|
|
443
445
|
}
|
|
444
446
|
for (const file of listFiles(join(GUIDES_DIR, 'upstream'))) {
|
|
445
447
|
paths.add(join(targetDir, '.ai', 'guides', 'upstream', relative(join(GUIDES_DIR, 'upstream'), file)))
|
|
@@ -619,7 +621,7 @@ function generateShared(config: AgenticConfig): void {
|
|
|
619
621
|
|
|
620
622
|
// Routed conceptual guides are copied wholesale (framework-wide). Per-module
|
|
621
623
|
// fact-sheets (.ai/guides/modules/<module>.md) are filtered to the app's enabled
|
|
622
|
-
// module set; the combined module-facts
|
|
624
|
+
// module set; the combined v1 and corrected v2 module-facts sidecars are copied as-is.
|
|
623
625
|
if (existsSync(GUIDES_DIR)) {
|
|
624
626
|
const guidesDestDir = join(targetDir, '.ai', 'guides')
|
|
625
627
|
for (const file of readdirSync(GUIDES_DIR)) {
|
|
@@ -637,6 +639,12 @@ function generateShared(config: AgenticConfig): void {
|
|
|
637
639
|
copyFileSync(moduleFactsPath, destPath)
|
|
638
640
|
}
|
|
639
641
|
|
|
642
|
+
const moduleFactsV2Path = join(GUIDES_DIR, 'module-facts.v2.json')
|
|
643
|
+
if (existsSync(moduleFactsV2Path)) {
|
|
644
|
+
const destPath = join(guidesDestDir, 'module-facts.v2.json')
|
|
645
|
+
ensureDir(destPath)
|
|
646
|
+
copyFileSync(moduleFactsV2Path, destPath)
|
|
647
|
+
}
|
|
640
648
|
|
|
641
649
|
copyTree(join(GUIDES_DIR, 'upstream'), join(guidesDestDir, 'upstream'), config)
|
|
642
650
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { ModuleFactsJsonEntry } from '../../module-facts'
|
|
2
|
+
import { getFrameworkOverrideHostOperations } from '../../module-extension-facts'
|
|
3
|
+
import {
|
|
4
|
+
collectModuleOverrideTargets,
|
|
5
|
+
type ModuleOverrideTargetDiagnostic,
|
|
6
|
+
type ModuleOverrideTargetDiagnosticCode,
|
|
7
|
+
type ModuleOverrideTargetsResult,
|
|
8
|
+
} from '../../module-override-targets'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Real producers for every override-target diagnostic the projector can emit.
|
|
12
|
+
*
|
|
13
|
+
* The `factCoverage` ledger classifies four diagnostic codes as `negative-fixture`,
|
|
14
|
+
* which is a claim that a fixture really drives that code out of
|
|
15
|
+
* {@link collectModuleOverrideTargets}. These fixtures ARE that claim's evidence, and
|
|
16
|
+
* the coverage test asserts the produced set matches the ledger rows exactly — so a
|
|
17
|
+
* row can never name a fixture that does not exist, and a code no fixture reaches
|
|
18
|
+
* cannot be published as `negative-fixture`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export const FIXTURE_MODULE_ID = 'fixture'
|
|
22
|
+
export const FIXTURE_SOURCE_ROOT = `node_modules/@open-mercato/core/src/modules/${FIXTURE_MODULE_ID}`
|
|
23
|
+
|
|
24
|
+
export function fixtureFacts(overrides: Partial<ModuleFactsJsonEntry> = {}): ModuleFactsJsonEntry {
|
|
25
|
+
return {
|
|
26
|
+
title: 'Fixture',
|
|
27
|
+
description: null,
|
|
28
|
+
coreVersion: null,
|
|
29
|
+
sourcePackage: '@open-mercato/core',
|
|
30
|
+
sourceVersion: null,
|
|
31
|
+
sourceRoot: FIXTURE_SOURCE_ROOT,
|
|
32
|
+
entities: [],
|
|
33
|
+
events: [],
|
|
34
|
+
aclFeatures: [],
|
|
35
|
+
apiRoutes: [],
|
|
36
|
+
diTokens: [],
|
|
37
|
+
searchEntities: [],
|
|
38
|
+
hostTokens: { entityIds: [], tableIds: [] },
|
|
39
|
+
notifications: [],
|
|
40
|
+
cli: ['fixture:seed'],
|
|
41
|
+
backendPages: [],
|
|
42
|
+
frontendPages: [],
|
|
43
|
+
cliCommands: [{ command: 'fixture:seed', sourcePath: `${FIXTURE_SOURCE_ROOT}/cli.ts` }],
|
|
44
|
+
aiTools: [],
|
|
45
|
+
aiAgents: [],
|
|
46
|
+
...overrides,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function fixtureWorkerFacts(): ModuleFactsJsonEntry {
|
|
51
|
+
return fixtureFacts({
|
|
52
|
+
ownedContracts: {
|
|
53
|
+
worker: [
|
|
54
|
+
{
|
|
55
|
+
kind: 'worker',
|
|
56
|
+
id: 'fixture.worker',
|
|
57
|
+
source: { sourcePath: `${FIXTURE_SOURCE_ROOT}/workers/fixture.ts`, line: 1 },
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The framework catalog's real declared operations, which the projector reads at
|
|
66
|
+
* import time. Tests derive their stub catalogs from this so a stub differs from
|
|
67
|
+
* production in exactly the one host under test.
|
|
68
|
+
*/
|
|
69
|
+
export function realCatalogOperations(): Record<string, string> {
|
|
70
|
+
return getFrameworkOverrideHostOperations()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function catalogWithout(dottedHost: string): Record<string, string> {
|
|
74
|
+
const operations = realCatalogOperations()
|
|
75
|
+
delete operations[dottedHost]
|
|
76
|
+
return operations
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function catalogWith(dottedHost: string, operation: string): Record<string, string> {
|
|
80
|
+
return { ...realCatalogOperations(), [dottedHost]: operation }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Re-imports the projector against a stubbed framework catalog, so the two "no
|
|
85
|
+
* modes" causes reach the real `pushTarget` wiring instead of only the pure
|
|
86
|
+
* resolver. Every catalog host ships a valid operation today, so a stub is the only
|
|
87
|
+
* way to drive the unknown-domain/unknown-mode branches through production code.
|
|
88
|
+
*/
|
|
89
|
+
export function collectWithCatalogOperations(
|
|
90
|
+
hostOperations: Record<string, string>,
|
|
91
|
+
facts: ModuleFactsJsonEntry,
|
|
92
|
+
): ModuleOverrideTargetsResult {
|
|
93
|
+
let result: ModuleOverrideTargetsResult | undefined
|
|
94
|
+
jest.isolateModules(() => {
|
|
95
|
+
jest.doMock('../../module-extension-facts', () => ({
|
|
96
|
+
...jest.requireActual('../../module-extension-facts'),
|
|
97
|
+
getFrameworkOverrideHostOperations: () => hostOperations,
|
|
98
|
+
}))
|
|
99
|
+
const projector = require('../../module-override-targets') as typeof import('../../module-override-targets')
|
|
100
|
+
result = projector.collectModuleOverrideTargets(facts)
|
|
101
|
+
})
|
|
102
|
+
jest.dontMock('../../module-extension-facts')
|
|
103
|
+
if (!result) throw new Error('[internal] isolated override-target collection produced no result')
|
|
104
|
+
return result
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type OverrideTargetDiagnosticFixture = {
|
|
108
|
+
code: ModuleOverrideTargetDiagnosticCode
|
|
109
|
+
description: string
|
|
110
|
+
run: () => ModuleOverrideTargetDiagnostic[]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const OVERRIDE_TARGET_DIAGNOSTIC_FIXTURES: OverrideTargetDiagnosticFixture[] = [
|
|
114
|
+
{
|
|
115
|
+
code: 'missing-source',
|
|
116
|
+
description: 'a cli command whose provenance cannot be proven',
|
|
117
|
+
run: () =>
|
|
118
|
+
collectModuleOverrideTargets(fixtureFacts({ cliCommands: [{ command: 'fixture:seed', sourcePath: null }] }))
|
|
119
|
+
.overrideTargetDiagnostics,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
code: 'unsupported-dynamic-key',
|
|
123
|
+
description: 'an api route whose method the runtime cannot address',
|
|
124
|
+
run: () =>
|
|
125
|
+
collectModuleOverrideTargets(
|
|
126
|
+
fixtureFacts({
|
|
127
|
+
apiRoutes: [
|
|
128
|
+
{
|
|
129
|
+
path: '/fixture/items',
|
|
130
|
+
methods: ['FETCH'],
|
|
131
|
+
auth: {},
|
|
132
|
+
sourcePath: `${FIXTURE_SOURCE_ROOT}/api/items/route.ts`,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
}),
|
|
136
|
+
).overrideTargetDiagnostics,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
code: 'unknown-framework-domain',
|
|
140
|
+
description: 'the framework catalog does not describe the cli host at all',
|
|
141
|
+
run: () => collectWithCatalogOperations(catalogWithout('cli'), fixtureFacts()).overrideTargetDiagnostics,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
code: 'unknown-framework-mode',
|
|
145
|
+
description: 'the framework catalog describes the workers host with an operation that is not a public mode',
|
|
146
|
+
run: () =>
|
|
147
|
+
collectWithCatalogOperations(catalogWith('workers', 'merge-deep'), fixtureWorkerFacts())
|
|
148
|
+
.overrideTargetDiagnostics,
|
|
149
|
+
},
|
|
150
|
+
]
|
|
@@ -312,6 +312,296 @@ describe('module extension facts', () => {
|
|
|
312
312
|
]))
|
|
313
313
|
})
|
|
314
314
|
|
|
315
|
+
it('fails closed on an unknown member call instead of publishing its first argument', () => {
|
|
316
|
+
// The generic call fallback used to forward the FIRST ARGUMENT of any call expression.
|
|
317
|
+
// For a member call that COMPUTES its value from the arguments — a handle builder, an
|
|
318
|
+
// enum lookup, anything namespaced — that publishes an id the argument merely contributed
|
|
319
|
+
// to, which resolves to nothing. `ComponentReplacementHandles.*` is intercepted by an
|
|
320
|
+
// explicit formula above, so only a builder the reader does NOT know exercises this path.
|
|
321
|
+
// The failure is silent (the fact vanishes rather than erroring), so it needs its own test.
|
|
322
|
+
write(moduleRoot, 'widgets/components.ts', `
|
|
323
|
+
import { SomeOtherRegistry } from '@open-mercato/shared/modules/widgets/component-registry'
|
|
324
|
+
export const componentOverrides = [
|
|
325
|
+
{
|
|
326
|
+
target: { componentId: SomeOtherRegistry.derive('alpha.records.list') },
|
|
327
|
+
priority: 50,
|
|
328
|
+
wrapper: (Original) => Original,
|
|
329
|
+
},
|
|
330
|
+
]
|
|
331
|
+
`)
|
|
332
|
+
|
|
333
|
+
const facts = extractModuleExtensionFacts({
|
|
334
|
+
moduleId: 'alpha',
|
|
335
|
+
moduleRoot,
|
|
336
|
+
sourceRoot: 'node_modules/pkg/src/modules/alpha',
|
|
337
|
+
entities: [],
|
|
338
|
+
events: [],
|
|
339
|
+
apiRoutes: [],
|
|
340
|
+
searchEntities: [],
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
const overrides = facts.contributions.filter((contribution) => contribution.kind === 'component-override')
|
|
344
|
+
expect(overrides).toEqual([])
|
|
345
|
+
expect(JSON.stringify(facts)).not.toContain('alpha.records.list')
|
|
346
|
+
|
|
347
|
+
const legacyFacts = extractModuleExtensionFacts({
|
|
348
|
+
moduleId: 'alpha',
|
|
349
|
+
moduleRoot,
|
|
350
|
+
sourceRoot: 'node_modules/pkg/src/modules/alpha',
|
|
351
|
+
entities: [],
|
|
352
|
+
events: [],
|
|
353
|
+
apiRoutes: [],
|
|
354
|
+
searchEntities: [],
|
|
355
|
+
factsContractVersion: 1,
|
|
356
|
+
})
|
|
357
|
+
expect(legacyFacts.contributions).toEqual(expect.arrayContaining([
|
|
358
|
+
expect.objectContaining({
|
|
359
|
+
id: 'alpha.component-override.0:alpha.records.list',
|
|
360
|
+
details: expect.objectContaining({ handle: 'alpha.records.list', mode: 'replace' }),
|
|
361
|
+
}),
|
|
362
|
+
]))
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('classifies component overrides by the ComponentOverride discriminant and resolves handle builders', () => {
|
|
366
|
+
write(moduleRoot, 'widgets/components.ts', `
|
|
367
|
+
import { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'
|
|
368
|
+
function wrapRecords(Original) { return Original }
|
|
369
|
+
export const componentOverrides = [
|
|
370
|
+
{
|
|
371
|
+
target: { componentId: ComponentReplacementHandles.section('ui.detail', 'NotesSection') },
|
|
372
|
+
priority: 50,
|
|
373
|
+
wrapper: (Original) => Original,
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
target: { componentId: ComponentReplacementHandles.dataTable('alpha.records.list') },
|
|
377
|
+
priority: 50,
|
|
378
|
+
wrapper: wrapRecords,
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
target: { componentId: ComponentReplacementHandles.crudForm('alpha.record') },
|
|
382
|
+
priority: 50,
|
|
383
|
+
propsTransform: (props) => props,
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
target: { componentId: ComponentReplacementHandles.page('/backend/alpha') },
|
|
387
|
+
priority: 50,
|
|
388
|
+
replacement: AlphaPage,
|
|
389
|
+
propsSchema: alphaPropsSchema,
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
target: { componentId: ComponentReplacementHandles.mystery('alpha') },
|
|
393
|
+
priority: 50,
|
|
394
|
+
wrapper: (Original) => Original,
|
|
395
|
+
},
|
|
396
|
+
]
|
|
397
|
+
`)
|
|
398
|
+
|
|
399
|
+
const facts = extractModuleExtensionFacts({
|
|
400
|
+
moduleId: 'alpha',
|
|
401
|
+
moduleRoot,
|
|
402
|
+
sourceRoot: 'node_modules/pkg/src/modules/alpha',
|
|
403
|
+
entities: [],
|
|
404
|
+
events: [],
|
|
405
|
+
apiRoutes: [],
|
|
406
|
+
searchEntities: [],
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
const overrides = facts.contributions
|
|
410
|
+
.filter((contribution) => contribution.kind === 'component-override')
|
|
411
|
+
.map((contribution) => contribution.kind === 'component-override'
|
|
412
|
+
? { id: contribution.id, ...contribution.details }
|
|
413
|
+
: null)
|
|
414
|
+
|
|
415
|
+
expect(overrides).toEqual([
|
|
416
|
+
{
|
|
417
|
+
id: 'alpha.component-override.0:section:ui.detail.NotesSection',
|
|
418
|
+
handle: 'section:ui.detail.NotesSection',
|
|
419
|
+
mode: 'wrapper',
|
|
420
|
+
propsContract: 'component-props-schema',
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
id: 'alpha.component-override.1:data-table:alpha.records.list',
|
|
424
|
+
handle: 'data-table:alpha.records.list',
|
|
425
|
+
mode: 'wrapper',
|
|
426
|
+
propsContract: 'component-props-schema',
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
id: 'alpha.component-override.2:crud-form:alpha.record',
|
|
430
|
+
handle: 'crud-form:alpha.record',
|
|
431
|
+
mode: 'props',
|
|
432
|
+
propsContract: 'component-props-schema',
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
id: 'alpha.component-override.3:page:/backend/alpha',
|
|
436
|
+
handle: 'page:/backend/alpha',
|
|
437
|
+
mode: 'replace',
|
|
438
|
+
propsContract: 'component-props-schema',
|
|
439
|
+
},
|
|
440
|
+
])
|
|
441
|
+
// A handle the framework builder catalog does not describe never becomes a
|
|
442
|
+
// guessed id built from the call's first argument.
|
|
443
|
+
expect(JSON.stringify(facts.contributions)).not.toContain('ui.detail"')
|
|
444
|
+
expect(JSON.stringify(facts.contributions)).not.toContain('"alpha"')
|
|
445
|
+
|
|
446
|
+
const legacyFacts = extractModuleExtensionFacts({
|
|
447
|
+
moduleId: 'alpha',
|
|
448
|
+
moduleRoot,
|
|
449
|
+
sourceRoot: 'node_modules/pkg/src/modules/alpha',
|
|
450
|
+
entities: [],
|
|
451
|
+
events: [],
|
|
452
|
+
apiRoutes: [],
|
|
453
|
+
searchEntities: [],
|
|
454
|
+
factsContractVersion: 1,
|
|
455
|
+
})
|
|
456
|
+
expect(legacyFacts.contributions
|
|
457
|
+
.filter((contribution) => contribution.kind === 'component-override')
|
|
458
|
+
.map((contribution) => contribution.kind === 'component-override'
|
|
459
|
+
? { id: contribution.id, ...contribution.details }
|
|
460
|
+
: null)).toEqual([
|
|
461
|
+
{
|
|
462
|
+
id: 'alpha.component-override.0:ui.detail',
|
|
463
|
+
handle: 'ui.detail',
|
|
464
|
+
mode: 'replace',
|
|
465
|
+
propsContract: 'component-props-schema',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
id: 'alpha.component-override.1:alpha.records.list',
|
|
469
|
+
handle: 'alpha.records.list',
|
|
470
|
+
mode: 'replace',
|
|
471
|
+
propsContract: 'component-props-schema',
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
id: 'alpha.component-override.2:alpha.record',
|
|
475
|
+
handle: 'alpha.record',
|
|
476
|
+
mode: 'replace',
|
|
477
|
+
propsContract: 'component-props-schema',
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
id: 'alpha.component-override.3:/backend/alpha',
|
|
481
|
+
handle: '/backend/alpha',
|
|
482
|
+
mode: 'replace',
|
|
483
|
+
propsContract: 'component-props-schema',
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
id: 'alpha.component-override.4:alpha',
|
|
487
|
+
handle: 'alpha',
|
|
488
|
+
mode: 'replace',
|
|
489
|
+
propsContract: 'component-props-schema',
|
|
490
|
+
},
|
|
491
|
+
])
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
it('reads arrow-function convention hooks as declared surfaces', () => {
|
|
495
|
+
write(moduleRoot, 'data/enrichers.ts', `
|
|
496
|
+
const enrichList = async (records) => records
|
|
497
|
+
export const enrichers = [{
|
|
498
|
+
id: 'alpha.arrow', targetEntity: 'alpha:record',
|
|
499
|
+
enrichMany: enrichList,
|
|
500
|
+
enrichOne: async (record) => record,
|
|
501
|
+
}]
|
|
502
|
+
`)
|
|
503
|
+
|
|
504
|
+
const facts = extractModuleExtensionFacts({
|
|
505
|
+
moduleId: 'alpha',
|
|
506
|
+
moduleRoot,
|
|
507
|
+
sourceRoot: 'node_modules/pkg/src/modules/alpha',
|
|
508
|
+
entities: [{ id: 'alpha:record' }],
|
|
509
|
+
events: [],
|
|
510
|
+
apiRoutes: [],
|
|
511
|
+
searchEntities: [],
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
expect(facts.contributions).toEqual(expect.arrayContaining([
|
|
515
|
+
expect.objectContaining({
|
|
516
|
+
id: 'alpha.arrow',
|
|
517
|
+
details: expect.objectContaining({ surfaces: ['list', 'detail'] }),
|
|
518
|
+
}),
|
|
519
|
+
]))
|
|
520
|
+
|
|
521
|
+
const legacyFacts = extractModuleExtensionFacts({
|
|
522
|
+
moduleId: 'alpha',
|
|
523
|
+
moduleRoot,
|
|
524
|
+
sourceRoot: 'node_modules/pkg/src/modules/alpha',
|
|
525
|
+
entities: [{ id: 'alpha:record' }],
|
|
526
|
+
events: [],
|
|
527
|
+
apiRoutes: [],
|
|
528
|
+
searchEntities: [],
|
|
529
|
+
factsContractVersion: 1,
|
|
530
|
+
})
|
|
531
|
+
expect(legacyFacts.contributions).toEqual(expect.arrayContaining([
|
|
532
|
+
expect.objectContaining({ id: 'alpha.arrow', details: expect.objectContaining({ surfaces: [] }) }),
|
|
533
|
+
]))
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
it('reads every injection-table slot form the runtime loader accepts', () => {
|
|
537
|
+
write(moduleRoot, 'extension-points.ts', `
|
|
538
|
+
export const extensionPoints = defineModuleExtensionPoints({
|
|
539
|
+
moduleId: 'alpha',
|
|
540
|
+
hosts: {
|
|
541
|
+
records: dataTableExtensionHost({ tableId: 'alpha.records', source: 'Records.tsx' }),
|
|
542
|
+
editor: crudFormExtensionHost({ entityId: 'alpha.record', source: 'Edit.tsx' }),
|
|
543
|
+
},
|
|
544
|
+
})
|
|
545
|
+
`)
|
|
546
|
+
write(moduleRoot, 'Records.tsx', 'export const tableId = extensionPoints.hosts.records.tableId')
|
|
547
|
+
write(moduleRoot, 'Edit.tsx', 'export const entityId = extensionPoints.hosts.editor.entityId')
|
|
548
|
+
write(moduleRoot, 'widgets/injection-table.ts', `
|
|
549
|
+
export const injectionTable = {
|
|
550
|
+
'crud-form:alpha.record': 'alpha.bare-string',
|
|
551
|
+
'data-table:alpha.records:bulk-actions': { widgetId: 'alpha.single-object', priority: 40, features: ['alpha.manage'] },
|
|
552
|
+
'data-table:alpha.records:columns': [{ widgetId: 'alpha.array-entry', priority: 20 }],
|
|
553
|
+
}
|
|
554
|
+
`)
|
|
555
|
+
|
|
556
|
+
const facts = extractModuleExtensionFacts({
|
|
557
|
+
moduleId: 'alpha',
|
|
558
|
+
moduleRoot,
|
|
559
|
+
sourceRoot: 'node_modules/pkg/src/modules/alpha',
|
|
560
|
+
entities: [],
|
|
561
|
+
events: [],
|
|
562
|
+
apiRoutes: [],
|
|
563
|
+
searchEntities: [],
|
|
564
|
+
})
|
|
565
|
+
const injected = facts.contributions.filter((contribution) => contribution.source.symbol === 'injectionTable')
|
|
566
|
+
|
|
567
|
+
expect(injected).toEqual(expect.arrayContaining([
|
|
568
|
+
expect.objectContaining({
|
|
569
|
+
id: 'alpha.bare-string@crud-form:alpha.record',
|
|
570
|
+
kind: 'crud-form',
|
|
571
|
+
features: [],
|
|
572
|
+
placement: undefined,
|
|
573
|
+
details: expect.objectContaining({ entityId: 'alpha.record', payload: 'render' }),
|
|
574
|
+
}),
|
|
575
|
+
expect.objectContaining({
|
|
576
|
+
id: 'alpha.single-object@data-table:alpha.records:bulk-actions',
|
|
577
|
+
kind: 'data-table',
|
|
578
|
+
features: ['alpha.manage'],
|
|
579
|
+
placement: { priority: 40 },
|
|
580
|
+
details: expect.objectContaining({ payload: 'bulk-action', tableId: 'alpha.records', executionGuard: 'both' }),
|
|
581
|
+
}),
|
|
582
|
+
expect.objectContaining({
|
|
583
|
+
id: 'alpha.array-entry@data-table:alpha.records:columns',
|
|
584
|
+
kind: 'data-table',
|
|
585
|
+
placement: { priority: 20 },
|
|
586
|
+
details: expect.objectContaining({ payload: 'column', executionGuard: 'host' }),
|
|
587
|
+
}),
|
|
588
|
+
]))
|
|
589
|
+
expect(injected).toHaveLength(3)
|
|
590
|
+
|
|
591
|
+
const legacyFacts = extractModuleExtensionFacts({
|
|
592
|
+
moduleId: 'alpha',
|
|
593
|
+
moduleRoot,
|
|
594
|
+
sourceRoot: 'node_modules/pkg/src/modules/alpha',
|
|
595
|
+
entities: [],
|
|
596
|
+
events: [],
|
|
597
|
+
apiRoutes: [],
|
|
598
|
+
searchEntities: [],
|
|
599
|
+
factsContractVersion: 1,
|
|
600
|
+
})
|
|
601
|
+
expect(legacyFacts.contributions.filter((contribution) => contribution.source.symbol === 'injectionTable'))
|
|
602
|
+
.toEqual([expect.objectContaining({ id: 'alpha.array-entry@data-table:alpha.records:columns' })])
|
|
603
|
+
})
|
|
604
|
+
|
|
315
605
|
it('reports declarations whose authoritative source no longer binds the host key', () => {
|
|
316
606
|
write(moduleRoot, 'extension-points.ts', `
|
|
317
607
|
export const extensionPoints = defineModuleExtensionPoints({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
-
import { extractAllModuleFacts, renderModuleFactsJson } from '../module-facts'
|
|
3
|
+
import { extractAllModuleFacts, isExactSourceFilePath, renderModuleFactsJson } from '../module-facts'
|
|
4
4
|
import { discoverPackageModuleSources } from '../module-facts-discovery'
|
|
5
5
|
import { createResolver } from '../../resolver'
|
|
6
6
|
|
|
@@ -17,11 +17,18 @@ function isUnique(values: string[]): boolean {
|
|
|
17
17
|
return values.length === new Set(values).size
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const MARKDOWN_LINK_TARGET = /\]\(\.\.\/\.\.\/\.\.\/([^)#]+)(?:#L\d+)?\)/g
|
|
21
|
+
|
|
22
|
+
function collectLinkTargets(markdown: string): string[] {
|
|
23
|
+
return [...markdown.matchAll(MARKDOWN_LINK_TARGET)].map((match) => match[1])
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
describe('module-facts BC resolve guard (T2)', () => {
|
|
21
27
|
const repoRoot = findRepoRoot()
|
|
22
28
|
const sources = discoverPackageModuleSources(createResolver(repoRoot))
|
|
23
29
|
const extractionStartedAt = process.cpuUsage()
|
|
24
30
|
const { factsByModule, markdownByModule, frameworkMarkdown } = extractAllModuleFacts({ sources })
|
|
31
|
+
const legacyFactsByModule = extractAllModuleFacts({ sources, factsContractVersion: 1 }).factsByModule
|
|
25
32
|
const extractionCpuUsage = process.cpuUsage(extractionStartedAt)
|
|
26
33
|
const extractionCpuDurationMs = (extractionCpuUsage.user + extractionCpuUsage.system) / 1_000
|
|
27
34
|
|
|
@@ -36,6 +43,23 @@ describe('module-facts BC resolve guard (T2)', () => {
|
|
|
36
43
|
}
|
|
37
44
|
})
|
|
38
45
|
|
|
46
|
+
it('preserves stable v1 extension arrays while exposing corrected v2 facts separately', () => {
|
|
47
|
+
const legacySecurity = legacyFactsByModule.security.extensionSurfaces?.contributions.find(
|
|
48
|
+
(contribution) => contribution.id.includes('section:auth.login.form'),
|
|
49
|
+
)
|
|
50
|
+
const v2Security = factsByModule.security.extensionSurfaces?.contributions.find(
|
|
51
|
+
(contribution) => contribution.id.includes('section:auth.login.form'),
|
|
52
|
+
)
|
|
53
|
+
expect(legacySecurity?.kind === 'component-override' ? legacySecurity.details.mode : null).toBe('replace')
|
|
54
|
+
expect(v2Security?.kind === 'component-override' ? v2Security.details.mode : null).toBe('wrapper')
|
|
55
|
+
|
|
56
|
+
const recoveredContribution = 'catalog.injection.product-bulk-delete@data-table:catalog.products.list:bulk-actions'
|
|
57
|
+
expect(legacyFactsByModule.catalog.extensionSurfaces?.contributions.map((entry) => entry.id))
|
|
58
|
+
.not.toContain(recoveredContribution)
|
|
59
|
+
expect(factsByModule.catalog.extensionSurfaces?.contributions.map((entry) => entry.id))
|
|
60
|
+
.toContain(recoveredContribution)
|
|
61
|
+
})
|
|
62
|
+
|
|
39
63
|
it('keeps generated extension facts within bounded build-time and context budgets', () => {
|
|
40
64
|
const completeJson = renderModuleFactsJson(factsByModule)
|
|
41
65
|
const legacyJson = renderModuleFactsJson(Object.fromEntries(
|
|
@@ -82,13 +106,16 @@ describe('module-facts BC resolve guard (T2)', () => {
|
|
|
82
106
|
// pathological regression from ordinary hardware variance and failed
|
|
83
107
|
// unrelated PRs at random. 90s keeps it meaningful — a real blow-up here is
|
|
84
108
|
// multiplicative, not a few percent — while leaving CI roughly 3x headroom.
|
|
85
|
-
//
|
|
86
|
-
// Whole-repo output caps include the additive EUDR module. Measured with the
|
|
87
|
-
// same extractor, EUDR adds 147,395 JSON bytes and 66,037 Markdown bytes for
|
|
88
|
-
// its real routes, ACL, events, entities, and extension surfaces; removing
|
|
89
|
-
// only EUDR leaves 3,486,786 JSON bytes and 1,519,856 Markdown bytes. The
|
|
90
|
-
// extension-only delta remains below its existing 1.8 MB blow-up guard.
|
|
91
109
|
expect(extractionCpuDurationMs).toBeLessThan(90_000)
|
|
110
|
+
// JSON cap raised a fourth time by the injection-table slot normalization:
|
|
111
|
+
// `extractInjectionTable` previously did `if (!Array.isArray(entries)) continue`,
|
|
112
|
+
// silently dropping every string and single-object slot form that
|
|
113
|
+
// `ModuleInjectionTable` allows. Twelve real contributions across catalog, sales,
|
|
114
|
+
// wms, staff, integrations and checkout were therefore invisible to every fact
|
|
115
|
+
// consumer — `integrations` published no contributions at all. Reading them costs
|
|
116
|
+
// ~28KB, which is the fix working, not drift.
|
|
117
|
+
// The additive EUDR module contributes its real routes, ACL, events,
|
|
118
|
+
// entities, and extension surfaces without changing the extraction shape.
|
|
92
119
|
expect(Buffer.byteLength(completeJson)).toBeLessThan(3_750_000)
|
|
93
120
|
expect(Buffer.byteLength(completeJson) - Buffer.byteLength(legacyJson)).toBeLessThan(1_800_000)
|
|
94
121
|
// Markdown cap raised with the source-link contract: entities, events, ACL
|
|
@@ -98,6 +125,44 @@ describe('module-facts BC resolve guard (T2)', () => {
|
|
|
98
125
|
expect(markdownBytes).toBeLessThan(1_650_000)
|
|
99
126
|
})
|
|
100
127
|
|
|
128
|
+
it('links every generated fact to an exact resolvable file, never a directory', () => {
|
|
129
|
+
const packageLinkRoot = path.join(repoRoot, 'node_modules', '@open-mercato')
|
|
130
|
+
const canCheckDisk = fs.existsSync(packageLinkRoot)
|
|
131
|
+
const nonExactTargets = new Set<string>()
|
|
132
|
+
const unresolvedTargets = new Set<string>()
|
|
133
|
+
let checkedTargets = 0
|
|
134
|
+
|
|
135
|
+
for (const markdown of Object.values(markdownByModule)) {
|
|
136
|
+
for (const target of collectLinkTargets(markdown)) {
|
|
137
|
+
checkedTargets += 1
|
|
138
|
+
if (!isExactSourceFilePath(target)) {
|
|
139
|
+
nonExactTargets.add(target)
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
if (!canCheckDisk) continue
|
|
143
|
+
const absolute = path.join(repoRoot, target)
|
|
144
|
+
if (!fs.existsSync(absolute) || !fs.statSync(absolute).isFile()) unresolvedTargets.add(target)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
expect(checkedTargets).toBeGreaterThan(1_000)
|
|
149
|
+
expect([...nonExactTargets]).toEqual([])
|
|
150
|
+
expect([...unresolvedTargets]).toEqual([])
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('keeps directory-valued provenance readable as plain text', () => {
|
|
154
|
+
const frameworkHostMarkdowns = Object.values(markdownByModule)
|
|
155
|
+
.filter((markdown) => markdown.includes('packages/ui/src'))
|
|
156
|
+
expect(frameworkHostMarkdowns.length).toBeGreaterThan(0)
|
|
157
|
+
for (const markdown of frameworkHostMarkdowns) {
|
|
158
|
+
expect(markdown).not.toContain('(../../../packages/ui/src)')
|
|
159
|
+
}
|
|
160
|
+
for (const [moduleId, facts] of Object.entries(factsByModule)) {
|
|
161
|
+
expect(markdownByModule[moduleId]).toContain(`Source root: ${facts.sourceRoot}\n`)
|
|
162
|
+
expect(markdownByModule[moduleId]).not.toContain(`(../../../${facts.sourceRoot})`)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
101
166
|
it('discovers a superset of the historical core modules', () => {
|
|
102
167
|
const discovered = new Set(Object.keys(factsByModule))
|
|
103
168
|
for (const moduleId of ['auth', 'catalog', 'customers', 'sales', 'workflows']) {
|