@open-mercato/cli 0.6.7-develop.6863.1.61001cf9a8 → 0.6.7
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 +4 -4
- package/build.mjs +7 -24
- package/dist/agentic/claude-code/hooks/entity-migration-check.ts +88 -32
- package/dist/agentic/codex/enforcement-rules.md +16 -2
- package/dist/agentic/cursor/hooks/entity-migration-check.mjs +50 -19
- package/dist/agentic/cursor/rules/entity-guard.mdc +21 -9
- package/dist/agentic/cursor/rules/generated-guard.mdc +5 -3
- package/dist/agentic/cursor/rules/open-mercato.mdc +29 -3
- package/dist/agentic/guides/cache.md +45 -0
- package/dist/agentic/guides/core.md +453 -0
- package/dist/agentic/guides/events.md +117 -0
- package/dist/agentic/guides/module-facts.json +3833 -115419
- package/dist/agentic/guides/module-system.md +127 -12
- package/dist/agentic/guides/modules/ai_assistant.md +28 -227
- package/dist/agentic/guides/modules/api_docs.md +2 -58
- package/dist/agentic/guides/modules/api_keys.md +7 -92
- package/dist/agentic/guides/modules/attachments.md +14 -119
- package/dist/agentic/guides/modules/audit_logs.md +10 -131
- package/dist/agentic/guides/modules/auth.md +33 -276
- package/dist/agentic/guides/modules/business_rules.md +14 -149
- package/dist/agentic/guides/modules/catalog.md +43 -475
- package/dist/agentic/guides/modules/channel_gmail.md +3 -82
- package/dist/agentic/guides/modules/channel_imap.md +3 -82
- package/dist/agentic/guides/modules/checkout.md +27 -299
- package/dist/agentic/guides/modules/communication_channels.md +33 -315
- package/dist/agentic/guides/modules/configs.md +8 -104
- package/dist/agentic/guides/modules/content.md +2 -57
- package/dist/agentic/guides/modules/currencies.md +17 -152
- package/dist/agentic/guides/modules/customer_accounts.md +45 -387
- package/dist/agentic/guides/modules/customers.md +85 -746
- package/dist/agentic/guides/modules/dashboards.md +10 -155
- package/dist/agentic/guides/modules/data_sync.md +16 -149
- package/dist/agentic/guides/modules/dictionaries.md +12 -107
- package/dist/agentic/guides/modules/directory.md +15 -148
- package/dist/agentic/guides/modules/entities.md +13 -145
- package/dist/agentic/guides/modules/events.md +2 -59
- package/dist/agentic/guides/modules/feature_toggles.md +9 -119
- package/dist/agentic/guides/modules/gateway_stripe.md +4 -92
- package/dist/agentic/guides/modules/generators.md +2 -45
- package/dist/agentic/guides/modules/inbox_ops.md +27 -207
- package/dist/agentic/guides/modules/integrations.md +16 -146
- package/dist/agentic/guides/modules/messages.md +24 -223
- package/dist/agentic/guides/modules/notifications.md +8 -96
- package/dist/agentic/guides/modules/onboarding.md +6 -77
- package/dist/agentic/guides/modules/payment_gateways.md +21 -164
- package/dist/agentic/guides/modules/perspectives.md +7 -77
- package/dist/agentic/guides/modules/planner.md +18 -136
- package/dist/agentic/guides/modules/portal.md +2 -93
- package/dist/agentic/guides/modules/progress.md +14 -104
- package/dist/agentic/guides/modules/query_index.md +15 -154
- package/dist/agentic/guides/modules/record_locks.md +21 -206
- package/dist/agentic/guides/modules/resources.md +30 -197
- package/dist/agentic/guides/modules/sales.md +84 -678
- package/dist/agentic/guides/modules/scheduler.md +14 -133
- package/dist/agentic/guides/modules/search.md +6 -170
- package/dist/agentic/guides/modules/security.md +35 -294
- package/dist/agentic/guides/modules/shipping_carriers.md +18 -134
- package/dist/agentic/guides/modules/sso.md +26 -151
- package/dist/agentic/guides/modules/staff.md +57 -369
- package/dist/agentic/guides/modules/storage_s3.md +5 -95
- package/dist/agentic/guides/modules/sync_akeneo.md +3 -85
- package/dist/agentic/guides/modules/sync_excel.md +6 -75
- package/dist/agentic/guides/modules/system_status_overlays.md +2 -60
- package/dist/agentic/guides/modules/translations.md +10 -104
- package/dist/agentic/guides/modules/webhooks.md +24 -203
- package/dist/agentic/guides/modules/wms.md +46 -441
- package/dist/agentic/guides/modules/workflows.md +43 -302
- package/dist/agentic/guides/queue.md +53 -0
- package/dist/agentic/guides/search.md +115 -0
- package/dist/agentic/guides/shared.md +108 -0
- package/dist/agentic/guides/ui.md +347 -0
- package/dist/agentic/shared/AGENTS.md.template +311 -124
- package/dist/agentic/shared/ai/agentic.config.json +0 -2
- package/dist/agentic/shared/ai/lessons.md +3 -26
- package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +27 -0
- package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +27 -0
- package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +0 -4
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +241 -20
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +260 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +601 -16
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +146 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +310 -14
- package/dist/agentic/shared/ai/skills/om-help/SKILL.md +97 -15
- package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +84 -0
- package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +193 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +152 -33
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +708 -24
- package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +34 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +762 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +820 -20
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +50 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +97 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +843 -19
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +271 -0
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +63 -15
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +467 -16
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +101 -0
- package/dist/agentic/shared/ai/skills/tiers.json +21 -143
- package/dist/agentic/shared/ai/skills/tiers.schema.json +40 -52
- package/dist/agentic/shared/ai/specs/README.md +3 -4
- package/dist/agentic/shared/ai/specs/SPEC-000-template.md +10 -226
- package/dist/agentic/shared/ai/trackers/github.md +4 -108
- package/dist/agentic/shared/scripts/install-skills.sh +694 -4
- package/dist/bin.js +1 -13
- package/dist/bin.js.map +2 -2
- package/dist/lib/__integration__/TC-INT-008.spec.js +8 -88
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-init.js +3 -13
- package/dist/lib/agentic-init.js.map +2 -2
- package/dist/lib/agentic-setup.js +186 -418
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/extensions/injection-widgets.js +0 -20
- package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
- package/dist/lib/generators/module-facts-discovery.js +7 -41
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +45 -1387
- package/dist/lib/generators/module-facts.js.map +3 -3
- package/dist/lib/generators/module-registry.js +5 -11
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/resolver.js +0 -2
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/testing/integration.js +0 -5
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +1 -23
- package/dist/mercato.js.map +2 -2
- package/package.json +11 -13
- package/src/bin.ts +1 -33
- package/src/lib/__integration__/TC-INT-008.spec.ts +9 -107
- package/src/lib/__tests__/agentic-init.test.ts +5 -41
- package/src/lib/agentic-init.ts +3 -15
- package/src/lib/agentic-setup.ts +224 -530
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +2 -88
- package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +0 -1
- package/src/lib/generators/__tests__/module-facts.discovery.test.ts +10 -54
- package/src/lib/generators/__tests__/module-subset.test.ts +0 -18
- package/src/lib/generators/extensions/injection-widgets.ts +0 -51
- package/src/lib/generators/module-facts-discovery.ts +7 -58
- package/src/lib/generators/module-facts.ts +46 -1851
- package/src/lib/generators/module-registry.ts +6 -16
- package/src/lib/resolver.ts +0 -3
- package/src/lib/testing/__tests__/integration.test.ts +0 -4
- package/src/lib/testing/integration.ts +0 -5
- package/src/mercato.ts +1 -26
- package/dist/agentic/guides/ai-workflows.md +0 -76
- package/dist/agentic/guides/architecture.md +0 -107
- package/dist/agentic/guides/backend-ui.md +0 -80
- package/dist/agentic/guides/contracts.md +0 -92
- package/dist/agentic/guides/extensions.md +0 -121
- package/dist/agentic/guides/framework-extension-points.md +0 -36
- package/dist/agentic/guides/integrations.md +0 -81
- package/dist/agentic/guides/modules/design_system.md +0 -102
- package/dist/agentic/guides/spec-delivery.md +0 -22
- package/dist/agentic/guides/testing-debugging.md +0 -80
- package/dist/agentic/guides/upstream/AGENTS.md +0 -298
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +0 -316
- package/dist/agentic/guides/upstream/manifest.json +0 -8
- package/dist/agentic/shared/ai/harness/README.md +0 -50
- package/dist/agentic/shared/ai/harness/RELEASE.md +0 -57
- package/dist/agentic/shared/ai/harness/cases.json +0 -13680
- package/dist/agentic/shared/ai/harness/cases.schema.json +0 -133
- package/dist/agentic/shared/ai/harness/fixtures/index.json +0 -49
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +0 -162
- package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +0 -21
- package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +0 -96
- package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +0 -172
- package/dist/agentic/shared/ai/harness/release-matrix.json +0 -158
- package/dist/agentic/shared/ai/harness/release-result.schema.json +0 -219
- package/dist/agentic/shared/ai/harness/result.schema.json +0 -76
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +0 -12
- package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +0 -73
- package/dist/agentic/shared/ai/harness/validators.json +0 -78
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +0 -2121
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +0 -1096
- package/dist/agentic/shared/ai/lessons/_template.md +0 -16
- package/dist/agentic/shared/ai/review-checklist.md +0 -36
- package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +0 -21
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +0 -98
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +0 -16
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +0 -53
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +0 -13
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +0 -11
- package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +0 -30
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +0 -12
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +0 -13
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +0 -14
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +0 -28
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +0 -12
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +0 -22
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +0 -12
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +0 -19
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +0 -19
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +0 -13
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +0 -14
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +0 -10
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +0 -17
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +0 -29
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +0 -100
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +0 -16
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +0 -17
- package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +0 -25
- package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +0 -11
- package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +0 -13
- package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +0 -18
- package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +0 -20
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +0 -33
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +0 -40
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +0 -27
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +0 -24
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +0 -16
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +0 -14
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +0 -15
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +0 -12
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +0 -34
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +0 -4
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +0 -11
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +0 -31
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +0 -39
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +0 -43
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +0 -11
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +0 -19
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +0 -172
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +0 -12
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +0 -83
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +0 -21
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +0 -11
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +0 -22
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +0 -13
- package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +0 -41
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +0 -19
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +0 -44
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +0 -39
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +0 -20
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +0 -57
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +0 -11
- package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +0 -486
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +0 -18
- package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +0 -27
- package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +0 -13
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +0 -41
- package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +0 -11
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +0 -15
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +0 -16
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +0 -14
- package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +0 -187
- package/dist/agentic/shared/scripts/check-lessons.mjs +0 -178
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +0 -2958
- package/dist/agentic/shared/scripts/execution-sandbox.mjs +0 -329
- package/dist/agentic/shared/scripts/framework-context.mjs +0 -888
- package/dist/agentic/shared/scripts/install-skills.mjs +0 -1029
- package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +0 -222
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +0 -1774
- package/dist/lib/generators/module-extension-facts.js +0 -1840
- package/dist/lib/generators/module-extension-facts.js.map +0 -7
- package/dist/lib/generators/module-fact-sources.js +0 -70
- package/dist/lib/generators/module-fact-sources.js.map +0 -7
- package/dist/lib/generators/module-override-targets.js +0 -688
- package/dist/lib/generators/module-override-targets.js.map +0 -7
- package/dist/lib/load-env.js +0 -24
- package/dist/lib/load-env.js.map +0 -7
- package/dist/lib/telemetry-init.js +0 -358
- package/dist/lib/telemetry-init.js.map +0 -7
- package/src/lib/__tests__/agentic-setup.ownership.test.ts +0 -386
- package/src/lib/__tests__/injection-widget-id-validation.test.ts +0 -184
- package/src/lib/__tests__/load-env.test.ts +0 -65
- package/src/lib/__tests__/telemetry-init.test.ts +0 -298
- package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +0 -92
- package/src/lib/generators/__tests__/module-extension-activations.test.ts +0 -762
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +0 -404
- package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +0 -108
- package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +0 -561
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +0 -119
- package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +0 -149
- package/src/lib/generators/__tests__/module-override-targets.test.ts +0 -304
- package/src/lib/generators/module-extension-facts.ts +0 -2365
- package/src/lib/generators/module-fact-sources.ts +0 -108
- package/src/lib/generators/module-override-targets.ts +0 -863
- package/src/lib/load-env.ts +0 -30
- package/src/lib/telemetry-init.ts +0 -461
|
@@ -1,33 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import ts from 'typescript-js'
|
|
4
|
-
import type {
|
|
5
|
-
ModuleExtensionContributionFact,
|
|
6
|
-
ModuleExtensionHostFact,
|
|
7
|
-
ModuleExtensionSurfaceFacts,
|
|
8
|
-
ModuleExtensionTargetRef,
|
|
9
|
-
} from '@open-mercato/shared/modules/widgets/extension-points'
|
|
10
4
|
import { toSnake } from '../utils'
|
|
11
|
-
import { extractCommandIdsFromSource } from './module-registry'
|
|
12
|
-
import { MODULE_CODE_EXTENSIONS } from './scanner'
|
|
13
|
-
import type { ApiRouteInterceptorBridge } from './module-extension-facts'
|
|
14
|
-
import {
|
|
15
|
-
assertNoUnresolvedExtensionTargets,
|
|
16
|
-
correlateIncomingExtensions,
|
|
17
|
-
correlateModuleExtensionFacts,
|
|
18
|
-
extractActivationTargetOwners,
|
|
19
|
-
extractKnownApiRouteIds,
|
|
20
|
-
extractKnownCommandIds,
|
|
21
|
-
extractModuleExtensionFacts,
|
|
22
|
-
renderFrameworkExtensionPointsMarkdown,
|
|
23
|
-
withModuleExtensionFactExtractionCache,
|
|
24
|
-
} from './module-extension-facts'
|
|
25
|
-
import {
|
|
26
|
-
collectModuleOverrideTargets,
|
|
27
|
-
type ModuleOverrideTarget,
|
|
28
|
-
type ModuleOverrideTargetDiagnostic,
|
|
29
|
-
} from './module-override-targets'
|
|
30
|
-
import { buildFactSourceLookup, type FactSourceLookup } from './module-fact-sources'
|
|
31
5
|
|
|
32
6
|
export interface ModuleEntityFact {
|
|
33
7
|
id: string
|
|
@@ -47,49 +21,6 @@ export interface ModuleApiRouteFact {
|
|
|
47
21
|
path: string
|
|
48
22
|
methods: string[]
|
|
49
23
|
auth: Record<string, ApiRouteAuthRule>
|
|
50
|
-
sourcePath: string | null
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Statically declared page metadata that affects extensibility or access, using the
|
|
55
|
-
* runtime `PageMetadata` contract keys and the same alias precedence as
|
|
56
|
-
* `resolvePageRouteMetadata` (`pageGroup ?? group`, `pageGroupKey ?? groupKey`,
|
|
57
|
-
* `pageOrder ?? order`). Executable guards, icons, titles and translation results are
|
|
58
|
-
* never serialized.
|
|
59
|
-
*/
|
|
60
|
-
export interface ModulePageMetadata {
|
|
61
|
-
requireAuth?: boolean
|
|
62
|
-
requireCustomerAuth?: boolean
|
|
63
|
-
requireFeatures?: string[]
|
|
64
|
-
requireCustomerFeatures?: string[]
|
|
65
|
-
navHidden?: boolean
|
|
66
|
-
navGroup?: string
|
|
67
|
-
navGroupKey?: string
|
|
68
|
-
navOrder?: number
|
|
69
|
-
pageContext?: 'main' | 'admin' | 'settings' | 'profile'
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface ModulePageFact {
|
|
73
|
-
path: string
|
|
74
|
-
sourcePath: string
|
|
75
|
-
/** Companion metadata file (`page.meta.*` / `meta.*`) when runtime reads metadata from one. */
|
|
76
|
-
metadataSourcePath?: string
|
|
77
|
-
metadata?: ModulePageMetadata
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface ModuleCliCommandFact {
|
|
81
|
-
command: string
|
|
82
|
-
sourcePath: string
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface ModuleAiToolFact {
|
|
86
|
-
name: string
|
|
87
|
-
sourcePath: string
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface ModuleAiAgentFact {
|
|
91
|
-
id: string
|
|
92
|
-
sourcePath: string
|
|
93
24
|
}
|
|
94
25
|
|
|
95
26
|
export interface ModuleEventFact {
|
|
@@ -97,8 +28,6 @@ export interface ModuleEventFact {
|
|
|
97
28
|
label?: string
|
|
98
29
|
category: string | null
|
|
99
30
|
entity: string | null
|
|
100
|
-
clientBroadcast?: boolean
|
|
101
|
-
portalBroadcast?: boolean
|
|
102
31
|
}
|
|
103
32
|
|
|
104
33
|
export interface ModuleHostTokens {
|
|
@@ -106,116 +35,11 @@ export interface ModuleHostTokens {
|
|
|
106
35
|
tableIds: string[]
|
|
107
36
|
}
|
|
108
37
|
|
|
109
|
-
export type ModuleFactSourceRef = {
|
|
110
|
-
sourcePath: string
|
|
111
|
-
exportName?: string
|
|
112
|
-
line?: number
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export type ModuleFactRef = {
|
|
116
|
-
factSection: string
|
|
117
|
-
factKey: string
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export type ModuleFactSourceKind =
|
|
121
|
-
| 'module-metadata'
|
|
122
|
-
| 'entity'
|
|
123
|
-
| 'event'
|
|
124
|
-
| 'acl-feature'
|
|
125
|
-
| 'api-route'
|
|
126
|
-
| 'di-registration'
|
|
127
|
-
| 'search'
|
|
128
|
-
| 'vector'
|
|
129
|
-
| 'notification'
|
|
130
|
-
| 'cli-command'
|
|
131
|
-
| 'backend-page'
|
|
132
|
-
| 'frontend-page'
|
|
133
|
-
| 'ai-tool'
|
|
134
|
-
| 'ai-agent'
|
|
135
|
-
| 'ai-extension'
|
|
136
|
-
| 'command'
|
|
137
|
-
| 'subscriber'
|
|
138
|
-
| 'worker'
|
|
139
|
-
| 'page-middleware'
|
|
140
|
-
| 'setup'
|
|
141
|
-
| 'encryption'
|
|
142
|
-
| 'custom-entity'
|
|
143
|
-
| 'integration'
|
|
144
|
-
| 'generator-plugin'
|
|
145
|
-
| 'extension-host'
|
|
146
|
-
| 'extension-contribution'
|
|
147
|
-
|
|
148
|
-
export type ModuleOwnedContractKind =
|
|
149
|
-
| 'module-metadata'
|
|
150
|
-
| 'command'
|
|
151
|
-
| 'worker'
|
|
152
|
-
| 'page-middleware'
|
|
153
|
-
| 'setup'
|
|
154
|
-
| 'encryption'
|
|
155
|
-
| 'di-registration'
|
|
156
|
-
| 'custom-entity'
|
|
157
|
-
| 'ai-extension'
|
|
158
|
-
| 'generator-plugin'
|
|
159
|
-
|
|
160
|
-
export type ModuleFactSafeScalar = string | number | boolean | null
|
|
161
|
-
|
|
162
|
-
export type ModuleOwnedContractFact = {
|
|
163
|
-
kind: ModuleOwnedContractKind
|
|
164
|
-
id: string
|
|
165
|
-
source: ModuleFactSourceRef
|
|
166
|
-
metadata?: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]>
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export type ModuleOwnedContracts = Partial<
|
|
170
|
-
Record<ModuleOwnedContractKind, ModuleOwnedContractFact[]>
|
|
171
|
-
>
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Provenance pointer into a fact section that already serializes the declaration site.
|
|
175
|
-
* `factKey` is omitted when it equals the owning entry's `id`, which is the common case.
|
|
176
|
-
*/
|
|
177
|
-
export type ModuleFactIndexRef = {
|
|
178
|
-
factSection: string
|
|
179
|
-
factKey?: string
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* One provenance entry per proven `(kind, id)` in the module. Exactly one of `source`
|
|
184
|
-
* (the declaration site, for facts that have no other home in the JSON) or `factRef`
|
|
185
|
-
* (a deterministic pointer into a fact section that already carries the source inline)
|
|
186
|
-
* is present, so a consumer resolves any fact's origin through this single index.
|
|
187
|
-
*/
|
|
188
|
-
export type ModuleFactSourceEntry = {
|
|
189
|
-
kind: ModuleFactSourceKind
|
|
190
|
-
id: string
|
|
191
|
-
source?: ModuleFactSourceRef
|
|
192
|
-
factRef?: ModuleFactIndexRef
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export type ModuleFactDiagnostic = {
|
|
196
|
-
code: 'duplicate-source' | 'unresolved-static-contract'
|
|
197
|
-
kind: ModuleFactSourceKind
|
|
198
|
-
id: string
|
|
199
|
-
source?: ModuleFactSourceRef
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export type ModuleDiRegistrationFact = {
|
|
203
|
-
token: string
|
|
204
|
-
registrationKind: 'function' | 'class' | 'value' | 'alias'
|
|
205
|
-
providerSymbol?: string
|
|
206
|
-
lifetime?: 'singleton' | 'scoped' | 'transient'
|
|
207
|
-
injectionMode?: 'classic' | 'proxy'
|
|
208
|
-
source: ModuleFactSourceRef
|
|
209
|
-
}
|
|
210
|
-
|
|
211
38
|
export interface ModuleFacts {
|
|
212
39
|
module: string
|
|
213
40
|
title: string | null
|
|
214
41
|
description: string | null
|
|
215
42
|
coreVersion: string | null
|
|
216
|
-
sourcePackage: string | null
|
|
217
|
-
sourceVersion: string | null
|
|
218
|
-
sourceRoot: string
|
|
219
43
|
entities: ModuleEntityFact[]
|
|
220
44
|
events: ModuleEventFact[]
|
|
221
45
|
aclFeatures: string[]
|
|
@@ -225,17 +49,6 @@ export interface ModuleFacts {
|
|
|
225
49
|
hostTokens: ModuleHostTokens
|
|
226
50
|
notifications: string[]
|
|
227
51
|
cli: string[]
|
|
228
|
-
backendPages: ModulePageFact[]
|
|
229
|
-
frontendPages: ModulePageFact[]
|
|
230
|
-
cliCommands: ModuleCliCommandFact[]
|
|
231
|
-
aiTools: ModuleAiToolFact[]
|
|
232
|
-
aiAgents: ModuleAiAgentFact[]
|
|
233
|
-
extensionSurfaces?: ModuleExtensionSurfaceFacts
|
|
234
|
-
factSources?: ModuleFactSourceEntry[]
|
|
235
|
-
ownedContracts?: ModuleOwnedContracts
|
|
236
|
-
factDiagnostics?: ModuleFactDiagnostic[]
|
|
237
|
-
overrideTargets?: ModuleOverrideTarget[]
|
|
238
|
-
overrideTargetDiagnostics?: ModuleOverrideTargetDiagnostic[]
|
|
239
52
|
warnings: string[]
|
|
240
53
|
}
|
|
241
54
|
|
|
@@ -251,8 +64,6 @@ export interface ExtractModuleFactsOptions {
|
|
|
251
64
|
/** Explicit module source directory. When set it overrides `coreSrcRoot + moduleId`. */
|
|
252
65
|
moduleRoot?: string
|
|
253
66
|
coreVersion?: string | null
|
|
254
|
-
sourcePackage?: string | null
|
|
255
|
-
sourceVersion?: string | null
|
|
256
67
|
registryPath?: string | null
|
|
257
68
|
registrySource?: string | null
|
|
258
69
|
}
|
|
@@ -262,7 +73,6 @@ export interface ModuleFactSource {
|
|
|
262
73
|
moduleId: string
|
|
263
74
|
moduleRoot: string
|
|
264
75
|
from?: string
|
|
265
|
-
packageVersion?: string | null
|
|
266
76
|
}
|
|
267
77
|
|
|
268
78
|
function readSourceFile(filePath: string): ts.SourceFile | null {
|
|
@@ -349,41 +159,10 @@ function collectCustomFieldEntityIds(ceFilePath: string | null): Set<string> {
|
|
|
349
159
|
const sourceFile = readSourceFile(ceFilePath)
|
|
350
160
|
if (!sourceFile) return result
|
|
351
161
|
|
|
352
|
-
const readEntityId = (expression: ts.Expression): string | undefined => {
|
|
353
|
-
const current = unwrapExpression(expression)
|
|
354
|
-
if (ts.isStringLiteralLike(current)) {
|
|
355
|
-
return current.text.includes(':') ? current.text : undefined
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const readAccessPath = (candidate: ts.Expression): string[] | null => {
|
|
359
|
-
const access = unwrapExpression(candidate)
|
|
360
|
-
if (ts.isIdentifier(access)) return [access.text]
|
|
361
|
-
if (ts.isPropertyAccessExpression(access)) {
|
|
362
|
-
const parent = readAccessPath(access.expression)
|
|
363
|
-
return parent ? [...parent, access.name.text] : null
|
|
364
|
-
}
|
|
365
|
-
if (ts.isElementAccessExpression(access) && access.argumentExpression) {
|
|
366
|
-
const parent = readAccessPath(access.expression)
|
|
367
|
-
const key = unwrapExpression(access.argumentExpression)
|
|
368
|
-
return parent && ts.isStringLiteralLike(key) ? [...parent, key.text] : null
|
|
369
|
-
}
|
|
370
|
-
return null
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const accessPath = readAccessPath(current)
|
|
374
|
-
if (accessPath?.length === 3 && accessPath[0] === 'E') {
|
|
375
|
-
return `${accessPath[1]}:${accessPath[2]}`
|
|
376
|
-
}
|
|
377
|
-
return undefined
|
|
378
|
-
}
|
|
379
|
-
|
|
380
162
|
const visit = (node: ts.Node): void => {
|
|
381
163
|
if (ts.isObjectLiteralExpression(node)) {
|
|
382
|
-
const
|
|
383
|
-
if (
|
|
384
|
-
const id = readEntityId(initializer)
|
|
385
|
-
if (id) result.add(id)
|
|
386
|
-
}
|
|
164
|
+
const id = readStringPropertyInitializer(node, 'id')
|
|
165
|
+
if (id && id.includes(':')) result.add(id)
|
|
387
166
|
}
|
|
388
167
|
node.forEachChild(visit)
|
|
389
168
|
}
|
|
@@ -572,11 +351,7 @@ function extractEvents(eventsFilePath: string | null): ModuleEventFact[] {
|
|
|
572
351
|
category: category ?? null,
|
|
573
352
|
entity: entity ?? null,
|
|
574
353
|
}
|
|
575
|
-
const clientBroadcast = readBooleanPropertyInitializer(element, 'clientBroadcast')
|
|
576
|
-
const portalBroadcast = readBooleanPropertyInitializer(element, 'portalBroadcast')
|
|
577
354
|
if (label !== undefined) fact.label = label
|
|
578
|
-
if (clientBroadcast !== undefined) fact.clientBroadcast = clientBroadcast
|
|
579
|
-
if (portalBroadcast !== undefined) fact.portalBroadcast = portalBroadcast
|
|
580
355
|
facts.push(fact)
|
|
581
356
|
}
|
|
582
357
|
|
|
@@ -684,14 +459,13 @@ function parseApiRouteEntry(entryLiteral: ts.ObjectLiteralExpression): ModuleApi
|
|
|
684
459
|
}
|
|
685
460
|
}
|
|
686
461
|
|
|
687
|
-
return { path: routePath, methods, auth
|
|
462
|
+
return { path: routePath, methods, auth }
|
|
688
463
|
}
|
|
689
464
|
|
|
690
465
|
function extractApiRoutes(
|
|
691
466
|
moduleId: string,
|
|
692
467
|
registrySource: string | null,
|
|
693
468
|
registryDescription: string,
|
|
694
|
-
routeSourcePaths: ReadonlyMap<string, string>,
|
|
695
469
|
warnings: string[],
|
|
696
470
|
): ModuleApiRouteFact[] {
|
|
697
471
|
if (registrySource == null) {
|
|
@@ -719,7 +493,7 @@ function extractApiRoutes(
|
|
|
719
493
|
const route = parseApiRouteEntry(element)
|
|
720
494
|
if (route && !seenPaths.has(route.path)) {
|
|
721
495
|
seenPaths.add(route.path)
|
|
722
|
-
routes.push(
|
|
496
|
+
routes.push(route)
|
|
723
497
|
}
|
|
724
498
|
}
|
|
725
499
|
}
|
|
@@ -892,241 +666,36 @@ function extractCli(cliFilePath: string | null, warnings: string[]): string[] {
|
|
|
892
666
|
function listSourceFilesRecursive(directory: string): string[] {
|
|
893
667
|
const files: string[] = []
|
|
894
668
|
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
895
|
-
if (entry.name === '__tests__' || entry.name === '
|
|
669
|
+
if (entry.name === '__tests__' || entry.name === 'node_modules') continue
|
|
896
670
|
const fullPath = path.join(directory, entry.name)
|
|
897
671
|
if (entry.isDirectory()) {
|
|
898
672
|
files.push(...listSourceFilesRecursive(fullPath))
|
|
899
|
-
} else if (
|
|
900
|
-
/\.tsx?$/.test(entry.name)
|
|
901
|
-
&& !entry.name.endsWith('.d.ts')
|
|
902
|
-
&& !/\.(?:test|spec)\.tsx?$/.test(entry.name)
|
|
903
|
-
) {
|
|
673
|
+
} else if (/\.tsx?$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
|
|
904
674
|
files.push(fullPath)
|
|
905
675
|
}
|
|
906
676
|
}
|
|
907
677
|
return files
|
|
908
678
|
}
|
|
909
679
|
|
|
910
|
-
function
|
|
911
|
-
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
function toPortableSourcePath(moduleRoot: string, sourceRoot: string, filePath: string): string {
|
|
915
|
-
const relativePath = path.relative(moduleRoot, filePath).split(path.sep).join('/')
|
|
916
|
-
return path.posix.join(sourceRoot, relativePath)
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
function sourceHasDefaultExport(sourceFile: ts.SourceFile): boolean {
|
|
920
|
-
return sourceFile.statements.some((statement) => {
|
|
921
|
-
if (ts.isExportAssignment(statement) && !statement.isExportEquals) return true
|
|
922
|
-
if (!ts.isFunctionDeclaration(statement) && !ts.isClassDeclaration(statement)) return false
|
|
923
|
-
return statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword) ?? false
|
|
924
|
-
})
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
function readRouteMetadataPath(sourceFile: ts.SourceFile): string | null {
|
|
928
|
-
const metadata = findObjectLiteralDeclaration(sourceFile, 'metadata')
|
|
929
|
-
return metadata ? readStringPropertyInitializer(metadata, 'path') ?? null : null
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
function extractApiRouteSourcePaths(
|
|
933
|
-
moduleId: string,
|
|
934
|
-
moduleRoot: string,
|
|
935
|
-
sourceRoot: string,
|
|
936
|
-
): Map<string, string> {
|
|
937
|
-
const apiRoot = path.join(moduleRoot, 'api')
|
|
938
|
-
const sources = new Map<string, string>()
|
|
939
|
-
if (!fs.existsSync(apiRoot)) return sources
|
|
940
|
-
const methodDirectories = new Set(['get', 'post', 'put', 'patch', 'delete'])
|
|
941
|
-
|
|
942
|
-
for (const filePath of listSourceFilesRecursive(apiRoot)) {
|
|
943
|
-
const relativePath = path.relative(apiRoot, filePath).split(path.sep).join('/')
|
|
944
|
-
const segments = relativePath.split('/')
|
|
945
|
-
const fileName = segments.pop() as string
|
|
946
|
-
const fileStem = fileName.replace(/\.tsx?$/, '')
|
|
947
|
-
let routeSegments: string[]
|
|
948
|
-
if (fileStem === 'route') {
|
|
949
|
-
routeSegments = segments
|
|
950
|
-
} else if (segments[0] && methodDirectories.has(segments[0].toLowerCase())) {
|
|
951
|
-
routeSegments = [...segments.slice(1), fileStem]
|
|
952
|
-
} else {
|
|
953
|
-
routeSegments = [...segments, fileStem]
|
|
954
|
-
}
|
|
955
|
-
const defaultPath = `/${[moduleId, ...routeSegments].filter(Boolean).join('/')}`
|
|
956
|
-
const sourceFile = readSourceFile(filePath)
|
|
957
|
-
const routePath = sourceFile ? readRouteMetadataPath(sourceFile) ?? defaultPath : defaultPath
|
|
958
|
-
sources.set(routePath, toPortableSourcePath(moduleRoot, sourceRoot, filePath))
|
|
959
|
-
}
|
|
960
|
-
return sources
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
function extractModulePages(
|
|
964
|
-
moduleId: string,
|
|
965
|
-
moduleRoot: string,
|
|
966
|
-
sourceRoot: string,
|
|
967
|
-
surface: 'backend' | 'frontend',
|
|
968
|
-
): ModulePageFact[] {
|
|
969
|
-
const pageRoot = path.join(moduleRoot, surface)
|
|
970
|
-
if (!fs.existsSync(pageRoot)) return []
|
|
971
|
-
const pages: ModulePageFact[] = []
|
|
972
|
-
const seen = new Set<string>()
|
|
973
|
-
|
|
974
|
-
for (const filePath of listSourceFilesRecursive(pageRoot)) {
|
|
975
|
-
if (!filePath.endsWith('.tsx')) continue
|
|
976
|
-
const relativePath = path.relative(pageRoot, filePath).split(path.sep).join('/')
|
|
977
|
-
const segments = relativePath.split('/')
|
|
978
|
-
const fileName = segments.pop() as string
|
|
979
|
-
const fileStem = fileName.replace(/\.tsx$/, '')
|
|
980
|
-
const isModernPage = fileStem === 'page'
|
|
981
|
-
if (!isModernPage && (fileStem === 'meta' || fileStem.endsWith('.meta') || /^[A-Z]/.test(fileStem))) continue
|
|
982
|
-
const sourceFile = readSourceFile(filePath)
|
|
983
|
-
if (!sourceFile || !sourceHasDefaultExport(sourceFile)) continue
|
|
984
|
-
const routeSegments = isModernPage ? segments : [...segments, fileStem]
|
|
985
|
-
const routePath = surface === 'frontend'
|
|
986
|
-
? `/${routeSegments.filter(Boolean).join('/')}`
|
|
987
|
-
: isModernPage
|
|
988
|
-
? `/backend/${routeSegments.join('/') || moduleId}`
|
|
989
|
-
: `/backend/${routeSegments[0] === moduleId
|
|
990
|
-
? routeSegments.filter(Boolean).join('/')
|
|
991
|
-
: [moduleId, ...routeSegments].filter(Boolean).join('/')}`
|
|
992
|
-
if (seen.has(routePath)) continue
|
|
993
|
-
seen.add(routePath)
|
|
994
|
-
const metadataFilePath = resolvePageMetadataFile(path.dirname(filePath))
|
|
995
|
-
const metadataSourceFile = metadataFilePath ? readSourceFile(metadataFilePath) : sourceFile
|
|
996
|
-
const metadata = metadataSourceFile ? readPageMetadata(metadataSourceFile) : undefined
|
|
997
|
-
pages.push({
|
|
998
|
-
path: routePath,
|
|
999
|
-
sourcePath: toPortableSourcePath(moduleRoot, sourceRoot, filePath),
|
|
1000
|
-
...(metadataFilePath
|
|
1001
|
-
? { metadataSourcePath: toPortableSourcePath(moduleRoot, sourceRoot, metadataFilePath) }
|
|
1002
|
-
: {}),
|
|
1003
|
-
...(metadata ? { metadata } : {}),
|
|
1004
|
-
})
|
|
1005
|
-
}
|
|
1006
|
-
return pages.sort((left, right) => left.path.localeCompare(right.path))
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
function findAncestorVariableDeclaration(node: ts.Node): ts.VariableDeclaration | null {
|
|
1010
|
-
let current: ts.Node | undefined = node.parent
|
|
1011
|
-
while (current) {
|
|
1012
|
-
if (ts.isVariableDeclaration(current)) return current
|
|
1013
|
-
current = current.parent
|
|
1014
|
-
}
|
|
1015
|
-
return null
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
function findAncestorCallExpression(node: ts.Node): ts.CallExpression | null {
|
|
1019
|
-
let current: ts.Node | undefined = node.parent
|
|
1020
|
-
while (current) {
|
|
1021
|
-
if (ts.isCallExpression(current)) return current
|
|
1022
|
-
if (ts.isVariableDeclaration(current) || ts.isStatement(current)) return null
|
|
1023
|
-
current = current.parent
|
|
1024
|
-
}
|
|
1025
|
-
return null
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
function extractNamedAiFacts(
|
|
1029
|
-
filePaths: readonly string[],
|
|
1030
|
-
moduleRoot: string,
|
|
1031
|
-
sourceRoot: string,
|
|
1032
|
-
propertyName: 'name' | 'id',
|
|
1033
|
-
): Array<{ value: string; sourcePath: string }> {
|
|
1034
|
-
const facts: Array<{ value: string; sourcePath: string }> = []
|
|
1035
|
-
const seen = new Set<string>()
|
|
1036
|
-
for (const filePath of filePaths) {
|
|
1037
|
-
const sourceFile = readSourceFile(filePath)
|
|
1038
|
-
if (!sourceFile) continue
|
|
1039
|
-
const initializers = buildVariableInitializerMap(sourceFile)
|
|
1040
|
-
const readDefinitionValue = (objectLiteral: ts.ObjectLiteralExpression): string | undefined => {
|
|
1041
|
-
const initializer = getObjectPropertyInitializer(objectLiteral, propertyName)
|
|
1042
|
-
if (!initializer) return undefined
|
|
1043
|
-
const resolved = unwrapExpression(initializer)
|
|
1044
|
-
if (ts.isStringLiteralLike(resolved)) return resolved.text
|
|
1045
|
-
if (ts.isIdentifier(resolved)) {
|
|
1046
|
-
const declarationInitializer = initializers.get(resolved.text)
|
|
1047
|
-
if (declarationInitializer) {
|
|
1048
|
-
const declarationValue = unwrapExpression(declarationInitializer)
|
|
1049
|
-
if (ts.isStringLiteralLike(declarationValue)) return declarationValue.text
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
return undefined
|
|
1053
|
-
}
|
|
1054
|
-
const visit = (node: ts.Node): void => {
|
|
1055
|
-
if (ts.isObjectLiteralExpression(node)) {
|
|
1056
|
-
const value = readDefinitionValue(node)
|
|
1057
|
-
if (value && !seen.has(value)) {
|
|
1058
|
-
const declaration = findAncestorVariableDeclaration(node)
|
|
1059
|
-
const declarationName = declaration && ts.isIdentifier(declaration.name) ? declaration.name.text : ''
|
|
1060
|
-
const declarationType = declaration?.type?.getText(sourceFile) ?? ''
|
|
1061
|
-
const call = findAncestorCallExpression(node)
|
|
1062
|
-
const callee = call?.expression.getText(sourceFile) ?? ''
|
|
1063
|
-
const isDefinition = propertyName === 'name'
|
|
1064
|
-
? /AiTool/.test(declarationType) || /tool$/i.test(declarationName) || /AiTool/.test(callee)
|
|
1065
|
-
: /AiAgentDefinition/.test(declarationType) || /agent$/i.test(declarationName) || /defineAiAgent/.test(callee)
|
|
1066
|
-
if (isDefinition) {
|
|
1067
|
-
seen.add(value)
|
|
1068
|
-
facts.push({
|
|
1069
|
-
value,
|
|
1070
|
-
sourcePath: toPortableSourcePath(moduleRoot, sourceRoot, filePath),
|
|
1071
|
-
})
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
node.forEachChild(visit)
|
|
1076
|
-
}
|
|
1077
|
-
sourceFile.forEachChild(visit)
|
|
1078
|
-
}
|
|
1079
|
-
return facts.sort((left, right) => left.value.localeCompare(right.value))
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
function extractAiTools(moduleRoot: string, sourceRoot: string): ModuleAiToolFact[] {
|
|
1083
|
-
const files = new Set<string>()
|
|
1084
|
-
const rootFile = resolveConventionFile(moduleRoot, 'ai-tools')
|
|
1085
|
-
if (rootFile) files.add(rootFile)
|
|
1086
|
-
const toolsDirectory = path.join(moduleRoot, 'ai-tools')
|
|
1087
|
-
if (fs.existsSync(toolsDirectory)) {
|
|
1088
|
-
for (const filePath of listSourceFilesRecursive(toolsDirectory)) files.add(filePath)
|
|
1089
|
-
}
|
|
1090
|
-
return extractNamedAiFacts([...files], moduleRoot, sourceRoot, 'name')
|
|
1091
|
-
.map((fact) => ({ name: fact.value, sourcePath: fact.sourcePath }))
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
function extractAiAgents(moduleRoot: string, sourceRoot: string): ModuleAiAgentFact[] {
|
|
1095
|
-
const agentsFile = resolveConventionFile(moduleRoot, 'ai-agents')
|
|
1096
|
-
if (!agentsFile) return []
|
|
1097
|
-
return extractNamedAiFacts([agentsFile], moduleRoot, sourceRoot, 'id')
|
|
1098
|
-
.map((fact) => ({ id: fact.value, sourcePath: fact.sourcePath }))
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
function extractTableIds(moduleRoot: string): string[] {
|
|
1102
|
-
if (!fs.existsSync(moduleRoot)) return []
|
|
680
|
+
function extractTableIds(backendDir: string): string[] {
|
|
681
|
+
if (!fs.existsSync(backendDir)) return []
|
|
1103
682
|
|
|
1104
683
|
const tableIds: string[] = []
|
|
1105
684
|
const seen = new Set<string>()
|
|
1106
|
-
const
|
|
1107
|
-
const current = unwrapExpression(expression)
|
|
1108
|
-
if (ts.isStringLiteralLike(current)) return [current.text]
|
|
1109
|
-
if (ts.isConditionalExpression(current)) {
|
|
1110
|
-
return [
|
|
1111
|
-
...collectLiteralValues(current.whenTrue),
|
|
1112
|
-
...collectLiteralValues(current.whenFalse),
|
|
1113
|
-
]
|
|
1114
|
-
}
|
|
1115
|
-
return []
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
for (const filePath of listSourceFilesRecursive(moduleRoot)) {
|
|
685
|
+
for (const filePath of listSourceFilesRecursive(backendDir)) {
|
|
1119
686
|
const sourceFile = readSourceFile(filePath)
|
|
1120
687
|
if (!sourceFile) continue
|
|
1121
688
|
const visit = (node: ts.Node): void => {
|
|
1122
689
|
if (ts.isPropertyAssignment(node)) {
|
|
1123
690
|
const propertyName = getPropertyName(node)
|
|
1124
|
-
if (
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
691
|
+
if (
|
|
692
|
+
(propertyName === 'tableId' || propertyName === 'extensionTableId') &&
|
|
693
|
+
ts.isStringLiteralLike(node.initializer)
|
|
694
|
+
) {
|
|
695
|
+
const value = node.initializer.text
|
|
696
|
+
if (value && !seen.has(value)) {
|
|
697
|
+
seen.add(value)
|
|
698
|
+
tableIds.push(value)
|
|
1130
699
|
}
|
|
1131
700
|
}
|
|
1132
701
|
}
|
|
@@ -1154,750 +723,13 @@ function extractModuleMeta(indexFilePath: string | null): { title: string | null
|
|
|
1154
723
|
}
|
|
1155
724
|
}
|
|
1156
725
|
|
|
1157
|
-
function nodeLine(sourceFile: ts.SourceFile, node: ts.Node): number {
|
|
1158
|
-
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
function readPropertyNameText(name: ts.PropertyName): string | undefined {
|
|
1162
|
-
if (ts.isIdentifier(name) || ts.isStringLiteralLike(name)) return name.text
|
|
1163
|
-
return undefined
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
function readMethodBearingPropertyNames(objectLiteral: ts.ObjectLiteralExpression): string[] {
|
|
1167
|
-
const names: string[] = []
|
|
1168
|
-
for (const property of objectLiteral.properties) {
|
|
1169
|
-
if (ts.isMethodDeclaration(property)) {
|
|
1170
|
-
const name = readPropertyNameText(property.name)
|
|
1171
|
-
if (name) names.push(name)
|
|
1172
|
-
continue
|
|
1173
|
-
}
|
|
1174
|
-
if (ts.isPropertyAssignment(property)) {
|
|
1175
|
-
const name = readPropertyNameText(property.name)
|
|
1176
|
-
if (!name) continue
|
|
1177
|
-
const initializer = unwrapExpression(property.initializer)
|
|
1178
|
-
if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) names.push(name)
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
return names.sort((left, right) => left.localeCompare(right))
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
function readObjectKeyNames(objectLiteral: ts.ObjectLiteralExpression): string[] {
|
|
1185
|
-
const names: string[] = []
|
|
1186
|
-
for (const property of objectLiteral.properties) {
|
|
1187
|
-
const name = getPropertyName(property)
|
|
1188
|
-
if (name) names.push(name)
|
|
1189
|
-
}
|
|
1190
|
-
return names.sort((left, right) => left.localeCompare(right))
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
function mapDiRegistrationKind(base: string | null): ModuleDiRegistrationFact['registrationKind'] | null {
|
|
1194
|
-
switch (base) {
|
|
1195
|
-
case 'asFunction':
|
|
1196
|
-
return 'function'
|
|
1197
|
-
case 'asClass':
|
|
1198
|
-
return 'class'
|
|
1199
|
-
case 'asValue':
|
|
1200
|
-
return 'value'
|
|
1201
|
-
case 'aliasTo':
|
|
1202
|
-
return 'alias'
|
|
1203
|
-
default:
|
|
1204
|
-
return null
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
function readDiChainMetadata(initializer: ts.Expression): {
|
|
1209
|
-
lifetime?: ModuleDiRegistrationFact['lifetime']
|
|
1210
|
-
injectionMode?: ModuleDiRegistrationFact['injectionMode']
|
|
1211
|
-
} {
|
|
1212
|
-
const result: {
|
|
1213
|
-
lifetime?: ModuleDiRegistrationFact['lifetime']
|
|
1214
|
-
injectionMode?: ModuleDiRegistrationFact['injectionMode']
|
|
1215
|
-
} = {}
|
|
1216
|
-
let current: ts.Expression = unwrapExpression(initializer)
|
|
1217
|
-
while (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
1218
|
-
const method = current.expression.name.text
|
|
1219
|
-
if ((method === 'singleton' || method === 'scoped' || method === 'transient') && !result.lifetime) {
|
|
1220
|
-
result.lifetime = method
|
|
1221
|
-
} else if (method === 'proxy' && !result.injectionMode) {
|
|
1222
|
-
result.injectionMode = 'proxy'
|
|
1223
|
-
} else if (method === 'classic' && !result.injectionMode) {
|
|
1224
|
-
result.injectionMode = 'classic'
|
|
1225
|
-
}
|
|
1226
|
-
current = unwrapExpression(current.expression.expression)
|
|
1227
|
-
}
|
|
1228
|
-
return result
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
function readDiProviderSymbol(
|
|
1232
|
-
initializer: ts.Expression,
|
|
1233
|
-
kind: ModuleDiRegistrationFact['registrationKind'],
|
|
1234
|
-
): string | undefined {
|
|
1235
|
-
let current: ts.Expression = unwrapExpression(initializer)
|
|
1236
|
-
while (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
1237
|
-
current = unwrapExpression(current.expression.expression)
|
|
1238
|
-
}
|
|
1239
|
-
if (!ts.isCallExpression(current)) return undefined
|
|
1240
|
-
const argument = current.arguments[0]
|
|
1241
|
-
if (!argument) return undefined
|
|
1242
|
-
if (kind === 'value') return undefined
|
|
1243
|
-
if (kind === 'alias' && ts.isStringLiteralLike(argument)) return argument.text
|
|
1244
|
-
if ((kind === 'function' || kind === 'class') && ts.isIdentifier(argument)) return argument.text
|
|
1245
|
-
return undefined
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
function extractDiRegistrationFacts(
|
|
1249
|
-
diFilePath: string | null,
|
|
1250
|
-
sourcePath: string,
|
|
1251
|
-
): { facts: ModuleDiRegistrationFact[]; unresolvedTokens: string[] } {
|
|
1252
|
-
if (!diFilePath) return { facts: [], unresolvedTokens: [] }
|
|
1253
|
-
const sourceFile = readSourceFile(diFilePath)
|
|
1254
|
-
if (!sourceFile) return { facts: [], unresolvedTokens: [] }
|
|
1255
|
-
|
|
1256
|
-
const facts: ModuleDiRegistrationFact[] = []
|
|
1257
|
-
const unresolvedTokens: string[] = []
|
|
1258
|
-
const visit = (node: ts.Node): void => {
|
|
1259
|
-
if (
|
|
1260
|
-
ts.isCallExpression(node) &&
|
|
1261
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
1262
|
-
node.expression.name.text === 'register'
|
|
1263
|
-
) {
|
|
1264
|
-
const argument = node.arguments[0]
|
|
1265
|
-
if (argument && ts.isObjectLiteralExpression(argument)) {
|
|
1266
|
-
for (const property of argument.properties) {
|
|
1267
|
-
if (!ts.isPropertyAssignment(property)) continue
|
|
1268
|
-
const token = getPropertyName(property)
|
|
1269
|
-
if (!token) continue
|
|
1270
|
-
const kind = mapDiRegistrationKind(detectAwilixRegistrationKind(property.initializer))
|
|
1271
|
-
if (!kind) {
|
|
1272
|
-
if (!unresolvedTokens.includes(token)) unresolvedTokens.push(token)
|
|
1273
|
-
continue
|
|
1274
|
-
}
|
|
1275
|
-
const chain = readDiChainMetadata(property.initializer)
|
|
1276
|
-
const providerSymbol = readDiProviderSymbol(property.initializer, kind)
|
|
1277
|
-
facts.push({
|
|
1278
|
-
token,
|
|
1279
|
-
registrationKind: kind,
|
|
1280
|
-
...(providerSymbol ? { providerSymbol } : {}),
|
|
1281
|
-
...(chain.lifetime ? { lifetime: chain.lifetime } : {}),
|
|
1282
|
-
...(chain.injectionMode ? { injectionMode: chain.injectionMode } : {}),
|
|
1283
|
-
source: { sourcePath, exportName: token, line: nodeLine(sourceFile, property) },
|
|
1284
|
-
})
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
node.forEachChild(visit)
|
|
1289
|
-
}
|
|
1290
|
-
sourceFile.forEachChild(visit)
|
|
1291
|
-
return { facts, unresolvedTokens }
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
function deriveLegacyDiTokens(facts: readonly ModuleDiRegistrationFact[]): string[] {
|
|
1295
|
-
const tokens: string[] = []
|
|
1296
|
-
const seen = new Set<string>()
|
|
1297
|
-
for (const fact of facts) {
|
|
1298
|
-
if (fact.registrationKind !== 'function' && fact.registrationKind !== 'class') continue
|
|
1299
|
-
if (seen.has(fact.token)) continue
|
|
1300
|
-
seen.add(fact.token)
|
|
1301
|
-
tokens.push(fact.token)
|
|
1302
|
-
}
|
|
1303
|
-
return tokens
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
function dedupeOwnedContractFacts(facts: readonly ModuleOwnedContractFact[]): ModuleOwnedContractFact[] {
|
|
1307
|
-
const byId = new Map<string, ModuleOwnedContractFact>()
|
|
1308
|
-
for (const fact of facts) {
|
|
1309
|
-
const existing = byId.get(fact.id)
|
|
1310
|
-
if (!existing) {
|
|
1311
|
-
byId.set(fact.id, fact)
|
|
1312
|
-
continue
|
|
1313
|
-
}
|
|
1314
|
-
if (fact.source.sourcePath.localeCompare(existing.source.sourcePath) < 0) byId.set(fact.id, fact)
|
|
1315
|
-
}
|
|
1316
|
-
return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id))
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
const PAGE_CONTEXT_VALUES = new Set(['main', 'admin', 'settings', 'profile'])
|
|
1320
|
-
|
|
1321
|
-
function readAliasedStringProperty(
|
|
1322
|
-
metadata: ts.ObjectLiteralExpression,
|
|
1323
|
-
preferred: string,
|
|
1324
|
-
fallback: string,
|
|
1325
|
-
): string | undefined {
|
|
1326
|
-
return readStringPropertyInitializer(metadata, preferred) ?? readStringPropertyInitializer(metadata, fallback)
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
function readNumericProperty(metadata: ts.ObjectLiteralExpression, propertyName: string): number | undefined {
|
|
1330
|
-
const initializer = getObjectPropertyInitializer(metadata, propertyName)
|
|
1331
|
-
if (!initializer || !ts.isNumericLiteral(initializer)) return undefined
|
|
1332
|
-
return Number(initializer.text)
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
function readPageMetadata(sourceFile: ts.SourceFile): ModulePageMetadata | undefined {
|
|
1336
|
-
const metadata = findObjectLiteralDeclaration(sourceFile, 'metadata')
|
|
1337
|
-
if (!metadata) return undefined
|
|
1338
|
-
const result: ModulePageMetadata = {}
|
|
1339
|
-
const requireAuth = readBooleanPropertyInitializer(metadata, 'requireAuth')
|
|
1340
|
-
if (requireAuth !== undefined) result.requireAuth = requireAuth
|
|
1341
|
-
const requireCustomerAuth = readBooleanPropertyInitializer(metadata, 'requireCustomerAuth')
|
|
1342
|
-
if (requireCustomerAuth !== undefined) result.requireCustomerAuth = requireCustomerAuth
|
|
1343
|
-
const requireFeatures = readStringArrayPropertyInitializer(metadata, 'requireFeatures')
|
|
1344
|
-
if (requireFeatures && requireFeatures.length > 0) result.requireFeatures = requireFeatures
|
|
1345
|
-
const requireCustomerFeatures = readStringArrayPropertyInitializer(metadata, 'requireCustomerFeatures')
|
|
1346
|
-
if (requireCustomerFeatures && requireCustomerFeatures.length > 0) {
|
|
1347
|
-
result.requireCustomerFeatures = requireCustomerFeatures
|
|
1348
|
-
}
|
|
1349
|
-
const navHidden = readBooleanPropertyInitializer(metadata, 'navHidden')
|
|
1350
|
-
if (navHidden !== undefined) result.navHidden = navHidden
|
|
1351
|
-
const navGroup = readAliasedStringProperty(metadata, 'pageGroup', 'group')
|
|
1352
|
-
if (navGroup) result.navGroup = navGroup
|
|
1353
|
-
const navGroupKey = readAliasedStringProperty(metadata, 'pageGroupKey', 'groupKey')
|
|
1354
|
-
if (navGroupKey) result.navGroupKey = navGroupKey
|
|
1355
|
-
const navOrder = readNumericProperty(metadata, 'pageOrder') ?? readNumericProperty(metadata, 'order')
|
|
1356
|
-
if (navOrder !== undefined) result.navOrder = navOrder
|
|
1357
|
-
const pageContext = readStringPropertyInitializer(metadata, 'pageContext')
|
|
1358
|
-
if (pageContext && PAGE_CONTEXT_VALUES.has(pageContext)) {
|
|
1359
|
-
result.pageContext = pageContext as ModulePageMetadata['pageContext']
|
|
1360
|
-
}
|
|
1361
|
-
return Object.keys(result).length > 0 ? result : undefined
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
/**
|
|
1365
|
-
* Mirrors the runtime companion-file convention: a page's metadata comes from
|
|
1366
|
-
* `page.meta.*` (or `meta.*`) in the page directory when one exists, and only
|
|
1367
|
-
* otherwise from the page component itself.
|
|
1368
|
-
*/
|
|
1369
|
-
function resolvePageMetadataFile(pageDirectory: string): string | null {
|
|
1370
|
-
for (const baseName of ['page.meta', 'meta']) {
|
|
1371
|
-
for (const extension of MODULE_CODE_EXTENSIONS) {
|
|
1372
|
-
const candidate = path.join(pageDirectory, `${baseName}${extension}`)
|
|
1373
|
-
if (fs.existsSync(candidate)) return candidate
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
return null
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
function extractModuleMetadataContract(
|
|
1380
|
-
indexFilePath: string | null,
|
|
1381
|
-
moduleId: string,
|
|
1382
|
-
sourcePath: string | null,
|
|
1383
|
-
): ModuleOwnedContractFact | null {
|
|
1384
|
-
if (!indexFilePath || !sourcePath) return null
|
|
1385
|
-
const sourceFile = readSourceFile(indexFilePath)
|
|
1386
|
-
if (!sourceFile) return null
|
|
1387
|
-
const metadata = findObjectLiteralDeclaration(sourceFile, 'metadata')
|
|
1388
|
-
if (!metadata) return null
|
|
1389
|
-
const facts: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]> = {}
|
|
1390
|
-
const name = readStringPropertyInitializer(metadata, 'name')
|
|
1391
|
-
if (name) facts.name = name
|
|
1392
|
-
const version = readStringPropertyInitializer(metadata, 'version')
|
|
1393
|
-
if (version) facts.version = version
|
|
1394
|
-
const requires = readStringArrayPropertyInitializer(metadata, 'requires')
|
|
1395
|
-
if (requires && requires.length > 0) facts.requires = requires
|
|
1396
|
-
const ejectable = readBooleanPropertyInitializer(metadata, 'ejectable')
|
|
1397
|
-
if (ejectable !== undefined) facts.ejectable = ejectable
|
|
1398
|
-
return {
|
|
1399
|
-
kind: 'module-metadata',
|
|
1400
|
-
id: moduleId,
|
|
1401
|
-
source: { sourcePath, exportName: 'metadata', line: nodeLine(sourceFile, metadata) },
|
|
1402
|
-
...(Object.keys(facts).length > 0 ? { metadata: facts } : {}),
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
function extractCommandContractCandidates(
|
|
1407
|
-
moduleId: string,
|
|
1408
|
-
moduleRoot: string,
|
|
1409
|
-
sourceRoot: string,
|
|
1410
|
-
): Array<{ id: string; sourcePath: string }> {
|
|
1411
|
-
const commandsRoot = path.join(moduleRoot, 'commands')
|
|
1412
|
-
if (!fs.existsSync(commandsRoot)) return []
|
|
1413
|
-
const candidates: Array<{ id: string; sourcePath: string }> = []
|
|
1414
|
-
for (const filePath of listSourceFilesRecursive(commandsRoot)) {
|
|
1415
|
-
if (path.basename(filePath).replace(/\.tsx?$/, '') === 'interceptors') continue
|
|
1416
|
-
const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath)
|
|
1417
|
-
for (const id of extractCommandIdsFromSource(filePath)) candidates.push({ id, sourcePath })
|
|
1418
|
-
}
|
|
1419
|
-
return candidates
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
/**
|
|
1423
|
-
* Runtime worker id: `metadata.id` when declared, otherwise the path-derived
|
|
1424
|
-
* `<moduleId>:workers:<...segments>:<fileStem>` that `discoverWorkers` builds.
|
|
1425
|
-
*/
|
|
1426
|
-
function deriveWorkerId(moduleId: string, workersRoot: string, filePath: string): string {
|
|
1427
|
-
const relativePath = path.relative(workersRoot, filePath).split(path.sep).join('/')
|
|
1428
|
-
const segments = relativePath.split('/')
|
|
1429
|
-
const fileName = segments.pop() as string
|
|
1430
|
-
const fileStem = fileName.replace(/\.tsx?$/, '')
|
|
1431
|
-
return [moduleId, 'workers', ...segments, fileStem].filter(Boolean).join(':')
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
/** Resolves a string property directly or through a local `const` in the same file. */
|
|
1435
|
-
function readResolvedStringProperty(
|
|
1436
|
-
objectLiteral: ts.ObjectLiteralExpression,
|
|
1437
|
-
propertyName: string,
|
|
1438
|
-
initializers: Map<string, ts.Expression>,
|
|
1439
|
-
): string | undefined {
|
|
1440
|
-
const initializer = getObjectPropertyInitializer(objectLiteral, propertyName)
|
|
1441
|
-
if (!initializer) return undefined
|
|
1442
|
-
const resolved = unwrapExpression(initializer)
|
|
1443
|
-
if (ts.isStringLiteralLike(resolved)) return resolved.text
|
|
1444
|
-
if (ts.isIdentifier(resolved)) {
|
|
1445
|
-
const declaration = initializers.get(resolved.text)
|
|
1446
|
-
if (declaration) {
|
|
1447
|
-
const declarationValue = unwrapExpression(declaration)
|
|
1448
|
-
if (ts.isStringLiteralLike(declarationValue)) return declarationValue.text
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
return undefined
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
function extractWorkerContracts(
|
|
1455
|
-
moduleId: string,
|
|
1456
|
-
moduleRoot: string,
|
|
1457
|
-
sourceRoot: string,
|
|
1458
|
-
): { facts: ModuleOwnedContractFact[]; unresolved: ModuleFactDiagnostic[] } {
|
|
1459
|
-
const workersRoot = path.join(moduleRoot, 'workers')
|
|
1460
|
-
if (!fs.existsSync(workersRoot)) return { facts: [], unresolved: [] }
|
|
1461
|
-
const facts: ModuleOwnedContractFact[] = []
|
|
1462
|
-
const unresolved: ModuleFactDiagnostic[] = []
|
|
1463
|
-
for (const filePath of listSourceFilesRecursive(workersRoot)) {
|
|
1464
|
-
const sourceFile = readSourceFile(filePath)
|
|
1465
|
-
if (!sourceFile) continue
|
|
1466
|
-
const metadata = findObjectLiteralDeclaration(sourceFile, 'metadata')
|
|
1467
|
-
if (!metadata) continue
|
|
1468
|
-
const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath)
|
|
1469
|
-
const source = { sourcePath, exportName: 'metadata', line: nodeLine(sourceFile, metadata) }
|
|
1470
|
-
const initializers = buildVariableInitializerMap(sourceFile)
|
|
1471
|
-
const workerId = readResolvedStringProperty(metadata, 'id', initializers)
|
|
1472
|
-
?? deriveWorkerId(moduleId, workersRoot, filePath)
|
|
1473
|
-
|
|
1474
|
-
// Runtime skips any worker without a queue, so a worker missing the required
|
|
1475
|
-
// property is not a contract; a present-but-dynamic queue is reported instead.
|
|
1476
|
-
const hasQueueProperty = getObjectPropertyInitializer(metadata, 'queue') !== undefined
|
|
1477
|
-
const queue = readResolvedStringProperty(metadata, 'queue', initializers)
|
|
1478
|
-
if (!hasQueueProperty) {
|
|
1479
|
-
unresolved.push({ code: 'unresolved-static-contract', kind: 'worker', id: workerId, source })
|
|
1480
|
-
continue
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
const workerMetadata: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]> = {}
|
|
1484
|
-
if (queue) workerMetadata.queue = queue
|
|
1485
|
-
else unresolved.push({ code: 'unresolved-static-contract', kind: 'worker', id: workerId, source })
|
|
1486
|
-
const name = readResolvedStringProperty(metadata, 'name', initializers)
|
|
1487
|
-
if (name) workerMetadata.name = name
|
|
1488
|
-
const concurrencyInitializer = getObjectPropertyInitializer(metadata, 'concurrency')
|
|
1489
|
-
if (concurrencyInitializer && ts.isNumericLiteral(concurrencyInitializer)) {
|
|
1490
|
-
workerMetadata.concurrency = Number(concurrencyInitializer.text)
|
|
1491
|
-
}
|
|
1492
|
-
facts.push({
|
|
1493
|
-
kind: 'worker',
|
|
1494
|
-
id: workerId,
|
|
1495
|
-
source,
|
|
1496
|
-
...(Object.keys(workerMetadata).length > 0 ? { metadata: workerMetadata } : {}),
|
|
1497
|
-
})
|
|
1498
|
-
}
|
|
1499
|
-
return { facts: dedupeOwnedContractFacts(facts), unresolved }
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
function extractPageMiddlewareContracts(
|
|
1503
|
-
moduleRoot: string,
|
|
1504
|
-
sourceRoot: string,
|
|
1505
|
-
): { facts: ModuleOwnedContractFact[]; unresolved: ModuleFactDiagnostic[] } {
|
|
1506
|
-
const facts: ModuleOwnedContractFact[] = []
|
|
1507
|
-
const unresolved: ModuleFactDiagnostic[] = []
|
|
1508
|
-
for (const surface of ['backend', 'frontend'] as const) {
|
|
1509
|
-
const filePath = resolveConventionFile(path.join(moduleRoot, surface), 'middleware')
|
|
1510
|
-
if (!filePath) continue
|
|
1511
|
-
const sourceFile = readSourceFile(filePath)
|
|
1512
|
-
if (!sourceFile) continue
|
|
1513
|
-
const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath)
|
|
1514
|
-
const middlewareArray =
|
|
1515
|
-
findArrayLiteralDeclaration(sourceFile, 'middleware') ??
|
|
1516
|
-
(() => {
|
|
1517
|
-
const defaultExport = findDefaultExportExpression(sourceFile)
|
|
1518
|
-
return defaultExport ? unwrapArrayLiteral(defaultExport) : null
|
|
1519
|
-
})()
|
|
1520
|
-
if (!middlewareArray) continue
|
|
1521
|
-
let index = 0
|
|
1522
|
-
for (const element of middlewareArray.elements) {
|
|
1523
|
-
if (!ts.isObjectLiteralExpression(element)) continue
|
|
1524
|
-
const middlewareId = readStringPropertyInitializer(element, 'id')
|
|
1525
|
-
if (!middlewareId) {
|
|
1526
|
-
unresolved.push({
|
|
1527
|
-
code: 'unresolved-static-contract',
|
|
1528
|
-
kind: 'page-middleware',
|
|
1529
|
-
id: `${sourcePath}#${index}`,
|
|
1530
|
-
source: { sourcePath, line: nodeLine(sourceFile, element) },
|
|
1531
|
-
})
|
|
1532
|
-
index += 1
|
|
1533
|
-
continue
|
|
1534
|
-
}
|
|
1535
|
-
const metadata: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]> = { surface }
|
|
1536
|
-
const mode = readStringPropertyInitializer(element, 'mode')
|
|
1537
|
-
if (mode) metadata.mode = mode
|
|
1538
|
-
const priorityInitializer = getObjectPropertyInitializer(element, 'priority')
|
|
1539
|
-
if (priorityInitializer && ts.isNumericLiteral(priorityInitializer)) {
|
|
1540
|
-
metadata.priority = Number(priorityInitializer.text)
|
|
1541
|
-
}
|
|
1542
|
-
facts.push({
|
|
1543
|
-
kind: 'page-middleware',
|
|
1544
|
-
id: middlewareId,
|
|
1545
|
-
source: { sourcePath, exportName: 'middleware', line: nodeLine(sourceFile, element) },
|
|
1546
|
-
metadata,
|
|
1547
|
-
})
|
|
1548
|
-
index += 1
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
return { facts: dedupeOwnedContractFacts(facts), unresolved }
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
function extractSetupContract(
|
|
1555
|
-
moduleId: string,
|
|
1556
|
-
setupFilePath: string | null,
|
|
1557
|
-
sourcePath: string | null,
|
|
1558
|
-
): ModuleOwnedContractFact | null {
|
|
1559
|
-
if (!setupFilePath || !sourcePath) return null
|
|
1560
|
-
const sourceFile = readSourceFile(setupFilePath)
|
|
1561
|
-
if (!sourceFile) return null
|
|
1562
|
-
const setupObject =
|
|
1563
|
-
findObjectLiteralDeclaration(sourceFile, 'setup') ??
|
|
1564
|
-
(() => {
|
|
1565
|
-
const defaultExport = findDefaultExportExpression(sourceFile)
|
|
1566
|
-
if (!defaultExport) return null
|
|
1567
|
-
const unwrapped = unwrapExpression(defaultExport)
|
|
1568
|
-
return ts.isObjectLiteralExpression(unwrapped) ? unwrapped : null
|
|
1569
|
-
})()
|
|
1570
|
-
if (!setupObject) return null
|
|
1571
|
-
const hooks = readMethodBearingPropertyNames(setupObject)
|
|
1572
|
-
const metadata: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]> = {}
|
|
1573
|
-
if (hooks.length > 0) metadata.hooks = hooks
|
|
1574
|
-
const roleFeatures = getObjectPropertyInitializer(setupObject, 'defaultRoleFeatures')
|
|
1575
|
-
if (roleFeatures && ts.isObjectLiteralExpression(roleFeatures)) {
|
|
1576
|
-
const roles = readObjectKeyNames(roleFeatures)
|
|
1577
|
-
if (roles.length > 0) metadata.roles = roles
|
|
1578
|
-
}
|
|
1579
|
-
const customerRoleFeatures = getObjectPropertyInitializer(setupObject, 'defaultCustomerRoleFeatures')
|
|
1580
|
-
if (customerRoleFeatures && ts.isObjectLiteralExpression(customerRoleFeatures)) {
|
|
1581
|
-
const customerRoles = readObjectKeyNames(customerRoleFeatures)
|
|
1582
|
-
if (customerRoles.length > 0) metadata.customerRoles = customerRoles
|
|
1583
|
-
}
|
|
1584
|
-
return {
|
|
1585
|
-
kind: 'setup',
|
|
1586
|
-
id: `${moduleId}:setup`,
|
|
1587
|
-
source: { sourcePath, exportName: 'setup', line: nodeLine(sourceFile, setupObject) },
|
|
1588
|
-
...(Object.keys(metadata).length > 0 ? { metadata } : {}),
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
function extractEncryptionContracts(
|
|
1593
|
-
encryptionFilePath: string | null,
|
|
1594
|
-
sourcePath: string | null,
|
|
1595
|
-
): ModuleOwnedContractFact[] {
|
|
1596
|
-
if (!encryptionFilePath || !sourcePath) return []
|
|
1597
|
-
const sourceFile = readSourceFile(encryptionFilePath)
|
|
1598
|
-
if (!sourceFile) return []
|
|
1599
|
-
const mapsArray =
|
|
1600
|
-
findArrayLiteralDeclaration(sourceFile, 'defaultEncryptionMaps') ??
|
|
1601
|
-
findArrayLiteralDeclaration(sourceFile, 'encryptionMaps')
|
|
1602
|
-
if (!mapsArray) return []
|
|
1603
|
-
const facts: ModuleOwnedContractFact[] = []
|
|
1604
|
-
for (const element of mapsArray.elements) {
|
|
1605
|
-
if (!ts.isObjectLiteralExpression(element)) continue
|
|
1606
|
-
const entityId = readStringPropertyInitializer(element, 'entityId')
|
|
1607
|
-
if (!entityId) continue
|
|
1608
|
-
const fieldsInitializer = getObjectPropertyInitializer(element, 'fields')
|
|
1609
|
-
const fields: string[] = []
|
|
1610
|
-
if (fieldsInitializer) {
|
|
1611
|
-
const fieldsArray = unwrapArrayLiteral(fieldsInitializer)
|
|
1612
|
-
if (fieldsArray) {
|
|
1613
|
-
for (const fieldElement of fieldsArray.elements) {
|
|
1614
|
-
if (!ts.isObjectLiteralExpression(fieldElement)) continue
|
|
1615
|
-
const field = readStringPropertyInitializer(fieldElement, 'field')
|
|
1616
|
-
if (field) fields.push(field)
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
facts.push({
|
|
1621
|
-
kind: 'encryption',
|
|
1622
|
-
id: entityId,
|
|
1623
|
-
source: { sourcePath, exportName: 'defaultEncryptionMaps', line: nodeLine(sourceFile, element) },
|
|
1624
|
-
...(fields.length > 0 ? { metadata: { fields: fields.sort((left, right) => left.localeCompare(right)) } } : {}),
|
|
1625
|
-
})
|
|
1626
|
-
}
|
|
1627
|
-
return dedupeOwnedContractFacts(facts)
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
function extractCustomEntityContracts(
|
|
1631
|
-
ceFilePath: string | null,
|
|
1632
|
-
sourcePath: string | null,
|
|
1633
|
-
): ModuleOwnedContractFact[] {
|
|
1634
|
-
if (!ceFilePath || !sourcePath) return []
|
|
1635
|
-
const entityIds = [...collectCustomFieldEntityIds(ceFilePath)].sort((left, right) => left.localeCompare(right))
|
|
1636
|
-
return entityIds.map((id) => ({
|
|
1637
|
-
kind: 'custom-entity' as const,
|
|
1638
|
-
id,
|
|
1639
|
-
source: { sourcePath, exportName: 'default' },
|
|
1640
|
-
}))
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
/**
|
|
1644
|
-
* Keyed AI override exports. Agent *extensions* (`aiAgentExtensions`) patch an agent
|
|
1645
|
-
* additively and belong to the `ai.extensions` array; these two replace or disable an
|
|
1646
|
-
* entry by key and map to exact `ai.agents.<id>` / `ai.tools.<id>` override targets.
|
|
1647
|
-
*/
|
|
1648
|
-
const AI_OVERRIDE_EXPORTS = [
|
|
1649
|
-
{ exportName: 'aiAgentOverrides', target: 'agent' },
|
|
1650
|
-
{ exportName: 'aiToolOverrides', target: 'tool' },
|
|
1651
|
-
] as const
|
|
1652
|
-
|
|
1653
|
-
function extractAiExtensionContracts(
|
|
1654
|
-
moduleRoot: string,
|
|
1655
|
-
sourceRoot: string,
|
|
1656
|
-
): ModuleOwnedContractFact[] {
|
|
1657
|
-
const facts: ModuleOwnedContractFact[] = []
|
|
1658
|
-
const sources: Array<{ basename: string; relativeDir: string }> = [
|
|
1659
|
-
{ basename: 'ai-agents', relativeDir: '.' },
|
|
1660
|
-
{ basename: 'ai-tools', relativeDir: '.' },
|
|
1661
|
-
{ basename: 'ai-agents.extensions', relativeDir: '.' },
|
|
1662
|
-
{ basename: 'ai-tools.extensions', relativeDir: '.' },
|
|
1663
|
-
]
|
|
1664
|
-
for (const entry of sources) {
|
|
1665
|
-
const filePath = resolveConventionFile(path.join(moduleRoot, entry.relativeDir), entry.basename)
|
|
1666
|
-
if (!filePath) continue
|
|
1667
|
-
const sourceFile = readSourceFile(filePath)
|
|
1668
|
-
if (!sourceFile) continue
|
|
1669
|
-
const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath)
|
|
1670
|
-
const extensionsArray = findArrayLiteralDeclaration(sourceFile, 'aiAgentExtensions')
|
|
1671
|
-
if (extensionsArray) {
|
|
1672
|
-
for (const element of extensionsArray.elements) {
|
|
1673
|
-
const objectLiteral = ts.isObjectLiteralExpression(element)
|
|
1674
|
-
? element
|
|
1675
|
-
: ts.isCallExpression(element) && element.arguments[0] && ts.isObjectLiteralExpression(element.arguments[0])
|
|
1676
|
-
? element.arguments[0]
|
|
1677
|
-
: null
|
|
1678
|
-
if (!objectLiteral) continue
|
|
1679
|
-
const targetAgentId = readStringPropertyInitializer(objectLiteral, 'targetAgentId')
|
|
1680
|
-
if (!targetAgentId) continue
|
|
1681
|
-
facts.push({
|
|
1682
|
-
kind: 'ai-extension',
|
|
1683
|
-
id: `agent-extension:${targetAgentId}`,
|
|
1684
|
-
source: { sourcePath, exportName: 'aiAgentExtensions', line: nodeLine(sourceFile, objectLiteral) },
|
|
1685
|
-
metadata: { target: 'agent', mode: 'extension', targetAgentId },
|
|
1686
|
-
})
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
// `aiAgentOverrides` / `aiToolOverrides` are keyed replace-or-disable maps; only
|
|
1690
|
-
// their keys are contract facts. Values (agent/tool definitions, `null` disables)
|
|
1691
|
-
// are never read or serialized.
|
|
1692
|
-
for (const { exportName, target } of AI_OVERRIDE_EXPORTS) {
|
|
1693
|
-
const overridesObject = findObjectLiteralDeclaration(sourceFile, exportName)
|
|
1694
|
-
if (!overridesObject) continue
|
|
1695
|
-
for (const property of overridesObject.properties) {
|
|
1696
|
-
const overrideId = getPropertyName(property)
|
|
1697
|
-
if (!overrideId) continue
|
|
1698
|
-
facts.push({
|
|
1699
|
-
kind: 'ai-extension',
|
|
1700
|
-
id: `${target}-override:${overrideId}`,
|
|
1701
|
-
source: { sourcePath, exportName, line: nodeLine(sourceFile, property) },
|
|
1702
|
-
metadata: { target, mode: 'override', overrideKey: overrideId },
|
|
1703
|
-
})
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
return dedupeOwnedContractFacts(facts)
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
function extractGeneratorPluginContracts(
|
|
1711
|
-
moduleRoot: string,
|
|
1712
|
-
sourceRoot: string,
|
|
1713
|
-
): { facts: ModuleOwnedContractFact[]; unresolved: ModuleFactDiagnostic[] } {
|
|
1714
|
-
const filePath = resolveConventionFile(moduleRoot, 'generators')
|
|
1715
|
-
if (!filePath) return { facts: [], unresolved: [] }
|
|
1716
|
-
const sourceFile = readSourceFile(filePath)
|
|
1717
|
-
if (!sourceFile) return { facts: [], unresolved: [] }
|
|
1718
|
-
const pluginsArray = findArrayLiteralDeclaration(sourceFile, 'generatorPlugins')
|
|
1719
|
-
if (!pluginsArray) return { facts: [], unresolved: [] }
|
|
1720
|
-
const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath)
|
|
1721
|
-
const initializers = buildVariableInitializerMap(sourceFile)
|
|
1722
|
-
const facts: ModuleOwnedContractFact[] = []
|
|
1723
|
-
const unresolved: ModuleFactDiagnostic[] = []
|
|
1724
|
-
let index = 0
|
|
1725
|
-
for (const element of pluginsArray.elements) {
|
|
1726
|
-
const objectLiteral = resolveToObjectLiteral(element, initializers)
|
|
1727
|
-
if (!objectLiteral) {
|
|
1728
|
-
index += 1
|
|
1729
|
-
continue
|
|
1730
|
-
}
|
|
1731
|
-
const pluginId = readStringPropertyInitializer(objectLiteral, 'id')
|
|
1732
|
-
if (!pluginId) {
|
|
1733
|
-
unresolved.push({
|
|
1734
|
-
code: 'unresolved-static-contract',
|
|
1735
|
-
kind: 'generator-plugin',
|
|
1736
|
-
id: `${sourcePath}#${index}`,
|
|
1737
|
-
source: { sourcePath, exportName: 'generatorPlugins', line: nodeLine(sourceFile, objectLiteral) },
|
|
1738
|
-
})
|
|
1739
|
-
index += 1
|
|
1740
|
-
continue
|
|
1741
|
-
}
|
|
1742
|
-
const metadata: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]> = {}
|
|
1743
|
-
const conventionFile = readStringPropertyInitializer(objectLiteral, 'conventionFile')
|
|
1744
|
-
if (conventionFile) metadata.conventionFile = conventionFile
|
|
1745
|
-
const outputFileName = readStringPropertyInitializer(objectLiteral, 'outputFileName')
|
|
1746
|
-
if (outputFileName) metadata.outputFileName = outputFileName
|
|
1747
|
-
facts.push({
|
|
1748
|
-
kind: 'generator-plugin',
|
|
1749
|
-
id: pluginId,
|
|
1750
|
-
source: { sourcePath, exportName: 'generatorPlugins', line: nodeLine(sourceFile, objectLiteral) },
|
|
1751
|
-
...(Object.keys(metadata).length > 0 ? { metadata } : {}),
|
|
1752
|
-
})
|
|
1753
|
-
index += 1
|
|
1754
|
-
}
|
|
1755
|
-
return { facts: dedupeOwnedContractFacts(facts), unresolved }
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
function compareSourceRefs(left: ModuleFactSourceRef, right: ModuleFactSourceRef): number {
|
|
1759
|
-
return (
|
|
1760
|
-
left.sourcePath.localeCompare(right.sourcePath) ||
|
|
1761
|
-
(left.line ?? 0) - (right.line ?? 0) ||
|
|
1762
|
-
(left.exportName ?? '').localeCompare(right.exportName ?? '')
|
|
1763
|
-
)
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
function sameSourceRef(left: ModuleFactSourceRef, right: ModuleFactSourceRef): boolean {
|
|
1767
|
-
return (
|
|
1768
|
-
left.sourcePath === right.sourcePath &&
|
|
1769
|
-
(left.line ?? null) === (right.line ?? null) &&
|
|
1770
|
-
(left.exportName ?? null) === (right.exportName ?? null)
|
|
1771
|
-
)
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
// Every proven `(kind, id)` reaches `factSources`. Facts whose declaration site is already
|
|
1775
|
-
// serialized inline (pages, CLI commands, routes, AI tools/agents, reused contributions) and
|
|
1776
|
-
// the owned families (which carry `source` inside `ownedContracts`) are emitted as a typed
|
|
1777
|
-
// `factRef` pointer instead of a duplicated source ref, so the index stays uniform without
|
|
1778
|
-
// copying provenance payloads past the determinism byte guard. Duplicate detection runs over
|
|
1779
|
-
// ALL candidates regardless of which projection they take.
|
|
1780
|
-
type ProvenanceCandidate = {
|
|
1781
|
-
kind: ModuleFactSourceKind
|
|
1782
|
-
id: string
|
|
1783
|
-
source?: ModuleFactSourceRef
|
|
1784
|
-
factRef?: ModuleFactRef
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
function provenanceCandidateOrderKey(candidate: ProvenanceCandidate): string {
|
|
1788
|
-
if (candidate.source) {
|
|
1789
|
-
return JSON.stringify([
|
|
1790
|
-
0,
|
|
1791
|
-
candidate.source.sourcePath,
|
|
1792
|
-
candidate.source.line ?? 0,
|
|
1793
|
-
candidate.source.exportName ?? '',
|
|
1794
|
-
])
|
|
1795
|
-
}
|
|
1796
|
-
return JSON.stringify([1, candidate.factRef?.factSection ?? '', candidate.factRef?.factKey ?? ''])
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
function buildProvenanceIndex(candidates: readonly ProvenanceCandidate[]): {
|
|
1800
|
-
factSources: ModuleFactSourceEntry[]
|
|
1801
|
-
diagnostics: ModuleFactDiagnostic[]
|
|
1802
|
-
} {
|
|
1803
|
-
const grouped = new Map<string, ProvenanceCandidate[]>()
|
|
1804
|
-
for (const candidate of candidates) {
|
|
1805
|
-
const key = JSON.stringify([candidate.kind, candidate.id])
|
|
1806
|
-
const group = grouped.get(key) ?? []
|
|
1807
|
-
group.push(candidate)
|
|
1808
|
-
grouped.set(key, group)
|
|
1809
|
-
}
|
|
1810
|
-
// Keep the provenance index compact: sourcePath is the provenance and (kind,id) the
|
|
1811
|
-
// stable identity, so exportName and line (recoverable from source) are dropped here.
|
|
1812
|
-
// Owned families retain the richer source ref inside `ownedContracts`.
|
|
1813
|
-
const factSources: ModuleFactSourceEntry[] = []
|
|
1814
|
-
const diagnostics: ModuleFactDiagnostic[] = []
|
|
1815
|
-
for (const group of grouped.values()) {
|
|
1816
|
-
const sorted = [...group].sort(
|
|
1817
|
-
(left, right) => provenanceCandidateOrderKey(left).localeCompare(provenanceCandidateOrderKey(right)),
|
|
1818
|
-
)
|
|
1819
|
-
const canonical = sorted[0]
|
|
1820
|
-
factSources.push(
|
|
1821
|
-
canonical.factRef
|
|
1822
|
-
? {
|
|
1823
|
-
kind: canonical.kind,
|
|
1824
|
-
id: canonical.id,
|
|
1825
|
-
factRef: {
|
|
1826
|
-
factSection: canonical.factRef.factSection,
|
|
1827
|
-
...(canonical.factRef.factKey === canonical.id
|
|
1828
|
-
? {}
|
|
1829
|
-
: { factKey: canonical.factRef.factKey }),
|
|
1830
|
-
},
|
|
1831
|
-
}
|
|
1832
|
-
: {
|
|
1833
|
-
kind: canonical.kind,
|
|
1834
|
-
id: canonical.id,
|
|
1835
|
-
source: { sourcePath: (canonical.source as ModuleFactSourceRef).sourcePath },
|
|
1836
|
-
},
|
|
1837
|
-
)
|
|
1838
|
-
const canonicalKey = provenanceCandidateOrderKey(canonical)
|
|
1839
|
-
for (const rejected of sorted.slice(1)) {
|
|
1840
|
-
if (provenanceCandidateOrderKey(rejected) === canonicalKey) continue
|
|
1841
|
-
diagnostics.push({
|
|
1842
|
-
code: 'duplicate-source',
|
|
1843
|
-
kind: rejected.kind,
|
|
1844
|
-
id: rejected.id,
|
|
1845
|
-
...(rejected.source ? { source: rejected.source } : {}),
|
|
1846
|
-
})
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
factSources.sort(
|
|
1850
|
-
(left, right) => left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id),
|
|
1851
|
-
)
|
|
1852
|
-
diagnostics.sort(
|
|
1853
|
-
(left, right) =>
|
|
1854
|
-
left.code.localeCompare(right.code) ||
|
|
1855
|
-
left.kind.localeCompare(right.kind) ||
|
|
1856
|
-
left.id.localeCompare(right.id) ||
|
|
1857
|
-
compareSourceRefs(left.source ?? { sourcePath: '' }, right.source ?? { sourcePath: '' }),
|
|
1858
|
-
)
|
|
1859
|
-
return { factSources, diagnostics }
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
function buildOwnedContracts(
|
|
1863
|
-
families: ReadonlyArray<[ModuleOwnedContractKind, ModuleOwnedContractFact[]]>,
|
|
1864
|
-
): ModuleOwnedContracts | undefined {
|
|
1865
|
-
const result: ModuleOwnedContracts = {}
|
|
1866
|
-
for (const [kind, facts] of families) {
|
|
1867
|
-
if (facts.length === 0) continue
|
|
1868
|
-
// Drop the (constant/redundant) exportName from the emitted source ref; the line
|
|
1869
|
-
// anchor plus sourcePath already pin the declaration and keep the JSON compact.
|
|
1870
|
-
result[kind] = facts.map((fact) => ({
|
|
1871
|
-
...fact,
|
|
1872
|
-
source: {
|
|
1873
|
-
sourcePath: fact.source.sourcePath,
|
|
1874
|
-
...(fact.source.line ? { line: fact.source.line } : {}),
|
|
1875
|
-
},
|
|
1876
|
-
}))
|
|
1877
|
-
}
|
|
1878
|
-
return Object.keys(result).length > 0 ? result : undefined
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
const CONTRIBUTION_KIND_TO_SOURCE_KIND: Partial<Record<string, ModuleFactSourceKind>> = {
|
|
1882
|
-
subscriber: 'subscriber',
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
const REGISTRY_TO_SOURCE_KIND: Partial<Record<string, ModuleFactSourceKind>> = {
|
|
1886
|
-
vector: 'vector',
|
|
1887
|
-
integration: 'integration',
|
|
1888
|
-
payment: 'integration',
|
|
1889
|
-
shipping: 'integration',
|
|
1890
|
-
currency: 'integration',
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
726
|
export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFacts {
|
|
1894
|
-
const { moduleId, coreVersion = null
|
|
727
|
+
const { moduleId, coreVersion = null } = options
|
|
1895
728
|
const moduleRoot = options.moduleRoot
|
|
1896
729
|
?? (options.coreSrcRoot ? path.join(options.coreSrcRoot, moduleId) : null)
|
|
1897
730
|
if (!moduleRoot) {
|
|
1898
731
|
throw new Error(`[internal] extractModuleFacts requires moduleRoot or coreSrcRoot for module "${moduleId}"`)
|
|
1899
732
|
}
|
|
1900
|
-
const sourceRoot = toPortableSourceRoot(moduleId, sourcePackage)
|
|
1901
733
|
|
|
1902
734
|
const entitiesFilePath =
|
|
1903
735
|
resolveConventionFile(path.join(moduleRoot, 'data'), 'entities') ??
|
|
@@ -1911,11 +743,7 @@ export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFa
|
|
|
1911
743
|
const notificationsFilePath = resolveConventionFile(moduleRoot, 'notifications')
|
|
1912
744
|
const cliFilePath = resolveConventionFile(moduleRoot, 'cli')
|
|
1913
745
|
const indexFilePath = resolveConventionFile(moduleRoot, 'index')
|
|
1914
|
-
const
|
|
1915
|
-
const encryptionFilePath = resolveConventionFile(moduleRoot, 'encryption')
|
|
1916
|
-
|
|
1917
|
-
const portableOf = (filePath: string | null): string | null =>
|
|
1918
|
-
filePath ? toPortableSourcePath(moduleRoot, sourceRoot, filePath) : null
|
|
746
|
+
const backendDir = path.join(moduleRoot, 'backend')
|
|
1919
747
|
|
|
1920
748
|
const warnings: string[] = []
|
|
1921
749
|
const { title, description } = extractModuleMeta(indexFilePath)
|
|
@@ -1925,247 +753,21 @@ export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFa
|
|
|
1925
753
|
const aclFeatures = extractAclFeatures(aclFilePath)
|
|
1926
754
|
|
|
1927
755
|
const { source: registrySource, description: registryDescription } = resolveRegistrySource(options)
|
|
1928
|
-
const
|
|
1929
|
-
const
|
|
1930
|
-
const diRegistrations = extractDiRegistrationFacts(diFilePath, portableOf(diFilePath) ?? '')
|
|
1931
|
-
const diTokens = deriveLegacyDiTokens(diRegistrations.facts)
|
|
1932
|
-
for (const legacyToken of extractDiTokens(diFilePath)) {
|
|
1933
|
-
if (!diTokens.includes(legacyToken)) diTokens.push(legacyToken)
|
|
1934
|
-
}
|
|
756
|
+
const apiRoutes = extractApiRoutes(moduleId, registrySource, registryDescription, warnings)
|
|
757
|
+
const diTokens = extractDiTokens(diFilePath)
|
|
1935
758
|
const searchEntities = extractSearchEntities(searchFilePath, warnings)
|
|
1936
759
|
const notifications = extractNotifications(notificationsFilePath, warnings)
|
|
1937
760
|
const cli = extractCli(cliFilePath, warnings)
|
|
1938
|
-
const cliCommands = cliFilePath
|
|
1939
|
-
? cli.map((command) => ({
|
|
1940
|
-
command,
|
|
1941
|
-
sourcePath: toPortableSourcePath(moduleRoot, sourceRoot, cliFilePath),
|
|
1942
|
-
}))
|
|
1943
|
-
: []
|
|
1944
|
-
const backendPages = extractModulePages(moduleId, moduleRoot, sourceRoot, 'backend')
|
|
1945
|
-
const frontendPages = extractModulePages(moduleId, moduleRoot, sourceRoot, 'frontend')
|
|
1946
|
-
const aiTools = extractAiTools(moduleRoot, sourceRoot)
|
|
1947
|
-
const aiAgents = extractAiAgents(moduleRoot, sourceRoot)
|
|
1948
761
|
const hostTokens: ModuleHostTokens = {
|
|
1949
762
|
entityIds: extractHostEntityIds(entities),
|
|
1950
|
-
tableIds: extractTableIds(
|
|
1951
|
-
}
|
|
1952
|
-
const extensionSurfaces = extractModuleExtensionFacts({
|
|
1953
|
-
moduleId,
|
|
1954
|
-
moduleRoot,
|
|
1955
|
-
sourceRoot,
|
|
1956
|
-
entities,
|
|
1957
|
-
events,
|
|
1958
|
-
apiRoutes,
|
|
1959
|
-
searchEntities,
|
|
1960
|
-
notifications,
|
|
1961
|
-
aiTools,
|
|
1962
|
-
aiAgents,
|
|
1963
|
-
})
|
|
1964
|
-
|
|
1965
|
-
const indexSourcePath = portableOf(indexFilePath)
|
|
1966
|
-
const diSourcePath = portableOf(diFilePath)
|
|
1967
|
-
const moduleMetadataFact = extractModuleMetadataContract(indexFilePath, moduleId, indexSourcePath)
|
|
1968
|
-
const commandCandidates = extractCommandContractCandidates(moduleId, moduleRoot, sourceRoot)
|
|
1969
|
-
const commandFacts = dedupeOwnedContractFacts(
|
|
1970
|
-
commandCandidates.map((candidate) => ({
|
|
1971
|
-
kind: 'command' as const,
|
|
1972
|
-
id: candidate.id,
|
|
1973
|
-
source: { sourcePath: candidate.sourcePath },
|
|
1974
|
-
})),
|
|
1975
|
-
)
|
|
1976
|
-
const workers = extractWorkerContracts(moduleId, moduleRoot, sourceRoot)
|
|
1977
|
-
const pageMiddleware = extractPageMiddlewareContracts(moduleRoot, sourceRoot)
|
|
1978
|
-
const setupFact = extractSetupContract(moduleId, setupFilePath, portableOf(setupFilePath))
|
|
1979
|
-
const encryptionFacts = extractEncryptionContracts(encryptionFilePath, portableOf(encryptionFilePath))
|
|
1980
|
-
const customEntityFacts = extractCustomEntityContracts(ceFilePath, portableOf(ceFilePath))
|
|
1981
|
-
const aiExtensionFacts = extractAiExtensionContracts(moduleRoot, sourceRoot)
|
|
1982
|
-
const generatorPlugins = extractGeneratorPluginContracts(moduleRoot, sourceRoot)
|
|
1983
|
-
const diContractFacts = dedupeOwnedContractFacts(
|
|
1984
|
-
diRegistrations.facts.map((fact) => ({
|
|
1985
|
-
kind: 'di-registration' as const,
|
|
1986
|
-
id: fact.token,
|
|
1987
|
-
source: fact.source,
|
|
1988
|
-
metadata: {
|
|
1989
|
-
registrationKind: fact.registrationKind,
|
|
1990
|
-
...(fact.providerSymbol ? { providerSymbol: fact.providerSymbol } : {}),
|
|
1991
|
-
...(fact.lifetime ? { lifetime: fact.lifetime } : {}),
|
|
1992
|
-
...(fact.injectionMode ? { injectionMode: fact.injectionMode } : {}),
|
|
1993
|
-
},
|
|
1994
|
-
})),
|
|
1995
|
-
)
|
|
1996
|
-
|
|
1997
|
-
const ownedContracts = buildOwnedContracts([
|
|
1998
|
-
['module-metadata', moduleMetadataFact ? [moduleMetadataFact] : []],
|
|
1999
|
-
['command', commandFacts],
|
|
2000
|
-
['worker', workers.facts],
|
|
2001
|
-
['page-middleware', pageMiddleware.facts],
|
|
2002
|
-
['setup', setupFact ? [setupFact] : []],
|
|
2003
|
-
['encryption', encryptionFacts],
|
|
2004
|
-
['di-registration', diContractFacts],
|
|
2005
|
-
['custom-entity', customEntityFacts],
|
|
2006
|
-
['ai-extension', aiExtensionFacts],
|
|
2007
|
-
['generator-plugin', generatorPlugins.facts],
|
|
2008
|
-
])
|
|
2009
|
-
|
|
2010
|
-
const provenanceCandidates: ProvenanceCandidate[] = []
|
|
2011
|
-
const pushSource = (kind: ModuleFactSourceKind, id: string, source: ModuleFactSourceRef): void => {
|
|
2012
|
-
provenanceCandidates.push({ kind, id, source })
|
|
2013
|
-
}
|
|
2014
|
-
const pushRef = (
|
|
2015
|
-
kind: ModuleFactSourceKind,
|
|
2016
|
-
id: string,
|
|
2017
|
-
source: ModuleFactSourceRef,
|
|
2018
|
-
factSection: string,
|
|
2019
|
-
factKey: string,
|
|
2020
|
-
): void => {
|
|
2021
|
-
provenanceCandidates.push({ kind, id, source, factRef: { factSection, factKey } })
|
|
2022
|
-
}
|
|
2023
|
-
const pushOwnedRefs = (kind: ModuleOwnedContractKind, facts: readonly ModuleOwnedContractFact[]): void => {
|
|
2024
|
-
for (const fact of facts) pushRef(kind, fact.id, fact.source, `ownedContracts.${kind}`, fact.id)
|
|
2025
|
-
}
|
|
2026
|
-
if (moduleMetadataFact) {
|
|
2027
|
-
pushRef(
|
|
2028
|
-
'module-metadata',
|
|
2029
|
-
moduleId,
|
|
2030
|
-
moduleMetadataFact.source,
|
|
2031
|
-
'ownedContracts.module-metadata',
|
|
2032
|
-
moduleMetadataFact.id,
|
|
2033
|
-
)
|
|
2034
|
-
}
|
|
2035
|
-
if (entitiesFilePath) {
|
|
2036
|
-
const entitiesSourcePath = portableOf(entitiesFilePath) as string
|
|
2037
|
-
for (const entity of entities) {
|
|
2038
|
-
pushSource('entity', entity.id, { sourcePath: entitiesSourcePath, exportName: entity.class })
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
if (eventsFilePath) {
|
|
2042
|
-
const eventsSourcePath = portableOf(eventsFilePath) as string
|
|
2043
|
-
for (const event of events) pushSource('event', event.id, { sourcePath: eventsSourcePath, exportName: 'events' })
|
|
2044
|
-
}
|
|
2045
|
-
if (aclFilePath) {
|
|
2046
|
-
const aclSourcePath = portableOf(aclFilePath) as string
|
|
2047
|
-
for (const feature of aclFeatures) pushSource('acl-feature', feature, { sourcePath: aclSourcePath, exportName: 'features' })
|
|
2048
|
-
}
|
|
2049
|
-
for (const route of apiRoutes) {
|
|
2050
|
-
if (route.sourcePath) {
|
|
2051
|
-
pushRef('api-route', route.path, { sourcePath: route.sourcePath }, 'apiRoutes', route.path)
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
if (searchFilePath) {
|
|
2055
|
-
const searchSourcePath = portableOf(searchFilePath) as string
|
|
2056
|
-
for (const entityId of searchEntities) pushSource('search', entityId, { sourcePath: searchSourcePath, exportName: 'searchConfig' })
|
|
2057
|
-
}
|
|
2058
|
-
if (notificationsFilePath) {
|
|
2059
|
-
const notificationsSourcePath = portableOf(notificationsFilePath) as string
|
|
2060
|
-
for (const id of notifications) pushSource('notification', id, { sourcePath: notificationsSourcePath })
|
|
2061
|
-
}
|
|
2062
|
-
for (const command of cliCommands) {
|
|
2063
|
-
pushRef('cli-command', command.command, { sourcePath: command.sourcePath }, 'cliCommands', command.command)
|
|
2064
|
-
}
|
|
2065
|
-
for (const page of backendPages) {
|
|
2066
|
-
pushRef('backend-page', page.path, { sourcePath: page.sourcePath }, 'backendPages', page.path)
|
|
2067
|
-
}
|
|
2068
|
-
for (const page of frontendPages) {
|
|
2069
|
-
pushRef('frontend-page', page.path, { sourcePath: page.sourcePath }, 'frontendPages', page.path)
|
|
2070
|
-
}
|
|
2071
|
-
for (const tool of aiTools) pushRef('ai-tool', tool.name, { sourcePath: tool.sourcePath }, 'aiTools', tool.name)
|
|
2072
|
-
for (const agent of aiAgents) pushRef('ai-agent', agent.id, { sourcePath: agent.sourcePath }, 'aiAgents', agent.id)
|
|
2073
|
-
for (const candidate of commandCandidates) {
|
|
2074
|
-
pushRef('command', candidate.id, { sourcePath: candidate.sourcePath }, 'ownedContracts.command', candidate.id)
|
|
2075
|
-
}
|
|
2076
|
-
pushOwnedRefs('worker', workers.facts)
|
|
2077
|
-
pushOwnedRefs('page-middleware', pageMiddleware.facts)
|
|
2078
|
-
if (setupFact) pushOwnedRefs('setup', [setupFact])
|
|
2079
|
-
pushOwnedRefs('encryption', encryptionFacts)
|
|
2080
|
-
for (const fact of diRegistrations.facts) {
|
|
2081
|
-
pushRef('di-registration', fact.token, fact.source, 'ownedContracts.di-registration', fact.token)
|
|
2082
|
-
}
|
|
2083
|
-
pushOwnedRefs('custom-entity', customEntityFacts)
|
|
2084
|
-
pushOwnedRefs('ai-extension', aiExtensionFacts)
|
|
2085
|
-
pushOwnedRefs('generator-plugin', generatorPlugins.facts)
|
|
2086
|
-
const contributionRef = (contributionId: string): { factSection: string; factKey: string } => ({
|
|
2087
|
-
factSection: 'extensionSurfaces.contributions',
|
|
2088
|
-
factKey: contributionId,
|
|
2089
|
-
})
|
|
2090
|
-
for (const contribution of extensionSurfaces.contributions) {
|
|
2091
|
-
const contributionSource: ModuleFactSourceRef = {
|
|
2092
|
-
sourcePath: contribution.source.path,
|
|
2093
|
-
...(contribution.source.symbol ? { exportName: contribution.source.symbol } : {}),
|
|
2094
|
-
}
|
|
2095
|
-
const ref = contributionRef(contribution.id)
|
|
2096
|
-
if (contribution.kind === 'subscriber') {
|
|
2097
|
-
pushRef('subscriber', contribution.id, contributionSource, ref.factSection, ref.factKey)
|
|
2098
|
-
continue
|
|
2099
|
-
}
|
|
2100
|
-
if (contribution.kind === 'specialized-registry') {
|
|
2101
|
-
const details = contribution.details as { registry?: string; registryId?: string }
|
|
2102
|
-
const sourceKind = details.registry ? REGISTRY_TO_SOURCE_KIND[details.registry] : undefined
|
|
2103
|
-
if (sourceKind) {
|
|
2104
|
-
pushRef(
|
|
2105
|
-
sourceKind,
|
|
2106
|
-
details.registryId ?? contribution.id,
|
|
2107
|
-
contributionSource,
|
|
2108
|
-
ref.factSection,
|
|
2109
|
-
ref.factKey,
|
|
2110
|
-
)
|
|
2111
|
-
}
|
|
2112
|
-
continue
|
|
2113
|
-
}
|
|
2114
|
-
const mapped = CONTRIBUTION_KIND_TO_SOURCE_KIND[contribution.kind]
|
|
2115
|
-
if (mapped) pushRef(mapped, contribution.id, contributionSource, ref.factSection, ref.factKey)
|
|
2116
|
-
}
|
|
2117
|
-
// extension-host / extension-contribution provenance is emitted as typed `factRef` pointers
|
|
2118
|
-
// rather than duplicated source refs: those facts already carry their own `source` inside
|
|
2119
|
-
// `extensionSurfaces.{hosts,contributions}`, and copying every declared host spot (hundreds
|
|
2120
|
-
// per large module) would push the shared JSON past the determinism byte guard asserted in
|
|
2121
|
-
// `module-facts.bc-guard.test.ts`.
|
|
2122
|
-
// Hosts are indexed by their registry `key`: `id` repeats across families (an entity id is
|
|
2123
|
-
// also an api-entity id), while `key` is unique inside a module.
|
|
2124
|
-
for (const host of extensionSurfaces.hosts) {
|
|
2125
|
-
provenanceCandidates.push({
|
|
2126
|
-
kind: 'extension-host',
|
|
2127
|
-
id: host.key,
|
|
2128
|
-
...(host.source.kind === 'fact-ref' ? {} : { source: { sourcePath: host.source.path } }),
|
|
2129
|
-
factRef: { factSection: 'extensionSurfaces.hosts', factKey: host.key },
|
|
2130
|
-
})
|
|
2131
|
-
}
|
|
2132
|
-
for (const contribution of extensionSurfaces.contributions) {
|
|
2133
|
-
pushRef(
|
|
2134
|
-
'extension-contribution',
|
|
2135
|
-
contribution.id,
|
|
2136
|
-
{ sourcePath: contribution.source.path },
|
|
2137
|
-
'extensionSurfaces.contributions',
|
|
2138
|
-
contribution.id,
|
|
2139
|
-
)
|
|
763
|
+
tableIds: extractTableIds(backendDir),
|
|
2140
764
|
}
|
|
2141
765
|
|
|
2142
|
-
|
|
2143
|
-
const factDiagnostics = [
|
|
2144
|
-
...provenance.diagnostics,
|
|
2145
|
-
...workers.unresolved,
|
|
2146
|
-
...pageMiddleware.unresolved,
|
|
2147
|
-
...generatorPlugins.unresolved,
|
|
2148
|
-
...diRegistrations.unresolvedTokens.map((token): ModuleFactDiagnostic => ({
|
|
2149
|
-
code: 'unresolved-static-contract',
|
|
2150
|
-
kind: 'di-registration',
|
|
2151
|
-
id: token,
|
|
2152
|
-
...(diSourcePath ? { source: { sourcePath: diSourcePath, exportName: token } } : {}),
|
|
2153
|
-
})),
|
|
2154
|
-
].sort(
|
|
2155
|
-
(left, right) =>
|
|
2156
|
-
left.code.localeCompare(right.code) ||
|
|
2157
|
-
left.kind.localeCompare(right.kind) ||
|
|
2158
|
-
left.id.localeCompare(right.id),
|
|
2159
|
-
)
|
|
2160
|
-
|
|
2161
|
-
const facts: ModuleFacts = {
|
|
766
|
+
return {
|
|
2162
767
|
module: moduleId,
|
|
2163
768
|
title,
|
|
2164
769
|
description,
|
|
2165
770
|
coreVersion,
|
|
2166
|
-
sourcePackage,
|
|
2167
|
-
sourceVersion,
|
|
2168
|
-
sourceRoot,
|
|
2169
771
|
entities,
|
|
2170
772
|
events,
|
|
2171
773
|
aclFeatures,
|
|
@@ -2175,43 +777,20 @@ export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFa
|
|
|
2175
777
|
hostTokens,
|
|
2176
778
|
notifications,
|
|
2177
779
|
cli,
|
|
2178
|
-
backendPages,
|
|
2179
|
-
frontendPages,
|
|
2180
|
-
cliCommands,
|
|
2181
|
-
aiTools,
|
|
2182
|
-
aiAgents,
|
|
2183
|
-
extensionSurfaces,
|
|
2184
|
-
...(provenance.factSources.length > 0 ? { factSources: provenance.factSources } : {}),
|
|
2185
|
-
...(ownedContracts ? { ownedContracts } : {}),
|
|
2186
|
-
...(factDiagnostics.length > 0 ? { factDiagnostics } : {}),
|
|
2187
780
|
warnings,
|
|
2188
781
|
}
|
|
2189
|
-
|
|
2190
|
-
// Exact unified override targets (spec 2026-08-02-module-facts-exact-override-targets).
|
|
2191
|
-
// Adapters consume the fully-populated owned facts + contributions above.
|
|
2192
|
-
const overrides = collectModuleOverrideTargets(facts, buildFactSourceLookup(facts))
|
|
2193
|
-
if (overrides.overrideTargets.length > 0) facts.overrideTargets = overrides.overrideTargets
|
|
2194
|
-
if (overrides.overrideTargetDiagnostics.length > 0) {
|
|
2195
|
-
facts.overrideTargetDiagnostics = overrides.overrideTargetDiagnostics
|
|
2196
|
-
}
|
|
2197
|
-
return facts
|
|
2198
782
|
}
|
|
2199
783
|
|
|
2200
784
|
export interface ModuleFactsJsonEvent {
|
|
2201
785
|
id: string
|
|
2202
786
|
category: string | null
|
|
2203
787
|
entity: string | null
|
|
2204
|
-
clientBroadcast?: boolean
|
|
2205
|
-
portalBroadcast?: boolean
|
|
2206
788
|
}
|
|
2207
789
|
|
|
2208
790
|
export interface ModuleFactsJsonEntry {
|
|
2209
791
|
title: string | null
|
|
2210
792
|
description: string | null
|
|
2211
793
|
coreVersion: string | null
|
|
2212
|
-
sourcePackage: string | null
|
|
2213
|
-
sourceVersion: string | null
|
|
2214
|
-
sourceRoot: string
|
|
2215
794
|
entities: ModuleEntityFact[]
|
|
2216
795
|
events: ModuleFactsJsonEvent[]
|
|
2217
796
|
aclFeatures: string[]
|
|
@@ -2221,54 +800,32 @@ export interface ModuleFactsJsonEntry {
|
|
|
2221
800
|
hostTokens: ModuleHostTokens
|
|
2222
801
|
notifications: string[]
|
|
2223
802
|
cli: string[]
|
|
2224
|
-
backendPages: ModulePageFact[]
|
|
2225
|
-
frontendPages: ModulePageFact[]
|
|
2226
|
-
cliCommands: ModuleCliCommandFact[]
|
|
2227
|
-
aiTools: ModuleAiToolFact[]
|
|
2228
|
-
aiAgents: ModuleAiAgentFact[]
|
|
2229
|
-
extensionSurfaces?: ModuleExtensionSurfaceFacts
|
|
2230
|
-
factSources?: ModuleFactSourceEntry[]
|
|
2231
|
-
ownedContracts?: ModuleOwnedContracts
|
|
2232
|
-
factDiagnostics?: ModuleFactDiagnostic[]
|
|
2233
|
-
overrideTargets?: ModuleOverrideTarget[]
|
|
2234
|
-
overrideTargetDiagnostics?: ModuleOverrideTargetDiagnostic[]
|
|
2235
803
|
}
|
|
2236
804
|
|
|
2237
805
|
const EMPTY_SECTION_MARKER = '_none_'
|
|
2238
806
|
|
|
2239
|
-
function renderVersionStamp(
|
|
2240
|
-
coreVersion: string | null,
|
|
2241
|
-
sourcePackage: string | null,
|
|
2242
|
-
sourceVersion: string | null,
|
|
2243
|
-
): string {
|
|
2244
|
-
if (sourcePackage) {
|
|
2245
|
-
return `<!-- generated from ${sourcePackage} ${sourceVersion || '<unknown>'}; core ${coreVersion || '<unknown>'} — R1 staleness stamp -->`
|
|
2246
|
-
}
|
|
807
|
+
function renderVersionStamp(coreVersion: string | null): string {
|
|
2247
808
|
const version = coreVersion && coreVersion.length > 0 ? coreVersion : '<unknown>'
|
|
2248
809
|
return `<!-- generated from @open-mercato/core ${version} — R1 staleness stamp -->`
|
|
2249
810
|
}
|
|
2250
811
|
|
|
2251
|
-
function renderEntitiesSection(entities: ModuleEntityFact[]
|
|
812
|
+
function renderEntitiesSection(entities: ModuleEntityFact[]): string {
|
|
2252
813
|
if (entities.length === 0) return `## Entities\n\n${EMPTY_SECTION_MARKER}`
|
|
2253
|
-
const header = '| Entity ID | Class | Table | Editable | CustomFields |
|
|
2254
|
-
const divider = '
|
|
814
|
+
const header = '| Entity ID | Class | Table | Editable | CustomFields |'
|
|
815
|
+
const divider = '|---|---|---|---|---|'
|
|
2255
816
|
const rows = entities.map(
|
|
2256
817
|
(entity) =>
|
|
2257
|
-
`| ${entity.id} | ${entity.class} | ${entity.table} | ${entity.editable ? 'yes' : 'no'} | ${entity.customFields ? 'yes' : 'no'}
|
|
818
|
+
`| ${entity.id} | ${entity.class} | ${entity.table} | ${entity.editable ? 'yes' : 'no'} | ${entity.customFields ? 'yes' : 'no'} |`,
|
|
2258
819
|
)
|
|
2259
820
|
return ['## Entities', '', header, divider, ...rows].join('\n')
|
|
2260
821
|
}
|
|
2261
822
|
|
|
2262
|
-
function renderEventsSection(events: ModuleEventFact[]
|
|
823
|
+
function renderEventsSection(events: ModuleEventFact[]): string {
|
|
2263
824
|
const heading = `## Events (${events.length})`
|
|
2264
825
|
if (events.length === 0) return `${heading}\n\n${EMPTY_SECTION_MARKER}`
|
|
2265
|
-
const header = '| ID | Category | Entity |
|
|
2266
|
-
const divider = '
|
|
2267
|
-
const rows = events.map((event) => {
|
|
2268
|
-
const transports = [event.clientBroadcast ? 'client' : null, event.portalBroadcast ? 'portal' : null]
|
|
2269
|
-
.filter((value): value is string => value !== null)
|
|
2270
|
-
return `| ${event.id} | ${event.category ?? '—'} | ${event.entity ?? '—'} | ${transports.join(', ') || '—'} | ${renderLookedUpSource(lookup, 'event', event.id)} |`
|
|
2271
|
-
})
|
|
826
|
+
const header = '| ID | Category | Entity |'
|
|
827
|
+
const divider = '|---|---|---|'
|
|
828
|
+
const rows = events.map((event) => `| ${event.id} | ${event.category ?? '—'} | ${event.entity ?? '—'} |`)
|
|
2272
829
|
return [heading, '', header, divider, ...rows].join('\n')
|
|
2273
830
|
}
|
|
2274
831
|
|
|
@@ -2277,73 +834,6 @@ function renderInlineListSection(heading: string, values: string[]): string {
|
|
|
2277
834
|
return `${heading}\n\n${values.join(' · ')}`
|
|
2278
835
|
}
|
|
2279
836
|
|
|
2280
|
-
function renderSourceLinkedListSection(
|
|
2281
|
-
heading: string,
|
|
2282
|
-
values: readonly string[],
|
|
2283
|
-
lookup: FactSourceLookup,
|
|
2284
|
-
kind: ModuleFactSourceKind,
|
|
2285
|
-
idColumnLabel: string,
|
|
2286
|
-
): string {
|
|
2287
|
-
if (values.length === 0) return `${heading}\n\n${EMPTY_SECTION_MARKER}`
|
|
2288
|
-
const rows = values.map((value) => `| ${value} | ${renderLookedUpSource(lookup, kind, value)} |`)
|
|
2289
|
-
return [heading, '', `| ${idColumnLabel} | Source |`, '|---|---|', ...rows].join('\n')
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
function renderSourceLink(sourcePath: string): string {
|
|
2293
|
-
return `[${sourcePath}](../../../${sourcePath})`
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
function renderSourceRefLink(source: ModuleFactSourceRef): string {
|
|
2297
|
-
const anchor = source.line ? `#L${source.line}` : ''
|
|
2298
|
-
const label = source.line ? `${source.sourcePath}:${source.line}` : source.sourcePath
|
|
2299
|
-
return `[${label}](../../../${source.sourcePath}${anchor})`
|
|
2300
|
-
}
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
function renderLookedUpSource(lookup: FactSourceLookup, kind: ModuleFactSourceKind, id: string): string {
|
|
2304
|
-
const source = lookup(kind, id)
|
|
2305
|
-
return source ? renderSourceRefLink(source) : '—'
|
|
2306
|
-
}
|
|
2307
|
-
|
|
2308
|
-
function renderSafeScalar(value: ModuleFactSafeScalar | ModuleFactSafeScalar[] | undefined): string {
|
|
2309
|
-
if (value === undefined || value === null) return '—'
|
|
2310
|
-
if (Array.isArray(value)) return value.length > 0 ? value.map((entry) => String(entry)).join(', ') : '—'
|
|
2311
|
-
return String(value)
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
function renderOwnedContractMetadata(metadata: ModuleOwnedContractFact['metadata']): string {
|
|
2315
|
-
if (!metadata) return '—'
|
|
2316
|
-
const keys = Object.keys(metadata).sort((left, right) => left.localeCompare(right))
|
|
2317
|
-
if (keys.length === 0) return '—'
|
|
2318
|
-
return keys.map((key) => `${key}=${renderSafeScalar(metadata[key])}`).join('; ')
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
|
-
function renderOwnedContractSection(
|
|
2322
|
-
heading: string,
|
|
2323
|
-
facts: readonly ModuleOwnedContractFact[] | undefined,
|
|
2324
|
-
): string {
|
|
2325
|
-
if (!facts || facts.length === 0) return ''
|
|
2326
|
-
const rows = facts.map(
|
|
2327
|
-
(fact) => `| ${fact.id} | ${renderOwnedContractMetadata(fact.metadata)} | ${renderSourceRefLink(fact.source)} |`,
|
|
2328
|
-
)
|
|
2329
|
-
return [heading, '', '| ID | Metadata | Source |', '|---|---|---|', ...rows].join('\n')
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
|
-
function renderDiRegistrationsSection(facts: readonly ModuleOwnedContractFact[] | undefined): string {
|
|
2333
|
-
if (!facts || facts.length === 0) return ''
|
|
2334
|
-
const rows = facts.map((fact) => {
|
|
2335
|
-
const metadata = fact.metadata ?? {}
|
|
2336
|
-
return `| ${fact.id} | ${renderSafeScalar(metadata.registrationKind)} | ${renderSafeScalar(metadata.lifetime)} | ${renderSafeScalar(metadata.injectionMode)} | ${renderSafeScalar(metadata.providerSymbol)} | ${renderSourceRefLink(fact.source)} |`
|
|
2337
|
-
})
|
|
2338
|
-
return [
|
|
2339
|
-
'## DI registrations (rich)',
|
|
2340
|
-
'',
|
|
2341
|
-
'| Token | Kind | Lifetime | Injection | Provider | Source |',
|
|
2342
|
-
'|---|---|---|---|---|---|',
|
|
2343
|
-
...rows,
|
|
2344
|
-
].join('\n')
|
|
2345
|
-
}
|
|
2346
|
-
|
|
2347
837
|
function describeAuthRule(rule: ApiRouteAuthRule | undefined): string {
|
|
2348
838
|
if (!rule) return 'public'
|
|
2349
839
|
if (rule.requireFeatures && rule.requireFeatures.length > 0) return rule.requireFeatures.join(', ')
|
|
@@ -2366,253 +856,44 @@ function renderApiRouteAuthCell(route: ModuleApiRouteFact): string {
|
|
|
2366
856
|
|
|
2367
857
|
function renderApiRoutesSection(routes: ModuleApiRouteFact[]): string {
|
|
2368
858
|
if (routes.length === 0) return `## API routes\n\n${EMPTY_SECTION_MARKER}`
|
|
2369
|
-
const header = '| Path | Methods | Auth (per-method requireFeatures) |
|
|
2370
|
-
const divider = '
|
|
859
|
+
const header = '| Path | Methods | Auth (per-method requireFeatures) |'
|
|
860
|
+
const divider = '|---|---|---|'
|
|
2371
861
|
const rows = routes.map(
|
|
2372
|
-
(route) => `| ${route.path} | ${route.methods.join(' ')} | ${renderApiRouteAuthCell(route)}
|
|
862
|
+
(route) => `| ${route.path} | ${route.methods.join(' ')} | ${renderApiRouteAuthCell(route)} |`,
|
|
2373
863
|
)
|
|
2374
864
|
return ['## API routes', '', header, divider, ...rows].join('\n')
|
|
2375
865
|
}
|
|
2376
866
|
|
|
2377
|
-
function renderLinkedFactsSection(
|
|
2378
|
-
heading: string,
|
|
2379
|
-
facts: ReadonlyArray<{ label: string; sourcePath: string }>,
|
|
2380
|
-
): string {
|
|
2381
|
-
if (facts.length === 0) return `${heading}\n\n${EMPTY_SECTION_MARKER}`
|
|
2382
|
-
const rows = facts.map((fact) => `| ${fact.label} | ${renderSourceLink(fact.sourcePath)} |`)
|
|
2383
|
-
return [heading, '', '| ID / path | Source |', '|---|---|', ...rows].join('\n')
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
867
|
function renderHostTokensSection(hostTokens: ModuleHostTokens): string {
|
|
2387
868
|
const entityIdsLine = hostTokens.entityIds.length > 0 ? hostTokens.entityIds.join(' · ') : EMPTY_SECTION_MARKER
|
|
2388
869
|
const tableIdsLine = hostTokens.tableIds.length > 0 ? hostTokens.tableIds.join(' · ') : EMPTY_SECTION_MARKER
|
|
2389
870
|
return ['## Host extension points', '', `- Entity IDs: ${entityIdsLine}`, `- Table IDs: ${tableIdsLine}`].join('\n')
|
|
2390
871
|
}
|
|
2391
872
|
|
|
2392
|
-
function renderExtensionHostContext(host: ModuleExtensionSurfaceFacts['hosts'][number]): string {
|
|
2393
|
-
return host.contextContract ?? host.runtimeContract ?? host.scopeContract ?? '—'
|
|
2394
|
-
}
|
|
2395
|
-
|
|
2396
|
-
function renderExtensionHostSource(host: ModuleExtensionHostFact): string {
|
|
2397
|
-
if (host.source.kind === 'fact-ref') return `${host.source.factSection}:${host.source.factKey}`
|
|
2398
|
-
return renderSourceRefLink({ sourcePath: host.source.path })
|
|
2399
|
-
}
|
|
2400
|
-
|
|
2401
|
-
function renderExtensionHostsSection(extensionSurfaces: ModuleExtensionSurfaceFacts): string {
|
|
2402
|
-
const boundHosts = extensionSurfaces.hosts.filter((host) => host.bound)
|
|
2403
|
-
if (boundHosts.length === 0) return `## UMES hosts\n\n${EMPTY_SECTION_MARKER}`
|
|
2404
|
-
const rows = boundHosts.map((host) =>
|
|
2405
|
-
`| ${host.id} | ${host.family} | ${host.capabilities.join(', ') || '—'} | ${renderExtensionHostContext(host)} | ${host.stability.toUpperCase()} | ${renderExtensionHostSource(host)} |`,
|
|
2406
|
-
)
|
|
2407
|
-
return [
|
|
2408
|
-
'## UMES hosts',
|
|
2409
|
-
'',
|
|
2410
|
-
'| ID / pattern | Family | Supports | Context | Stability | Source |',
|
|
2411
|
-
'|---|---|---|---|---|---|',
|
|
2412
|
-
...rows,
|
|
2413
|
-
].join('\n')
|
|
2414
|
-
}
|
|
2415
|
-
|
|
2416
|
-
function compactContributionDetails(contribution: ModuleExtensionContributionFact): string {
|
|
2417
|
-
const details = contribution.details as unknown as Record<string, unknown>
|
|
2418
|
-
return Object.keys(details).sort((left, right) => left.localeCompare(right)).flatMap((key) => {
|
|
2419
|
-
const value = details[key]
|
|
2420
|
-
if (value === undefined) return []
|
|
2421
|
-
if (Array.isArray(value)) return [`${key}=${value.join(',') || 'none'}`]
|
|
2422
|
-
if (value && typeof value === 'object') {
|
|
2423
|
-
const nested = Object.keys(value as Record<string, unknown>).sort((left, right) => left.localeCompare(right)).map((nestedKey) => {
|
|
2424
|
-
const nestedValue = (value as Record<string, unknown>)[nestedKey]
|
|
2425
|
-
return `${nestedKey}:${Array.isArray(nestedValue) ? nestedValue.join(',') : String(nestedValue)}`
|
|
2426
|
-
})
|
|
2427
|
-
return [`${key}={${nested.join(';')}}`]
|
|
2428
|
-
}
|
|
2429
|
-
return [`${key}=${String(value)}`]
|
|
2430
|
-
}).join('; ')
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
function renderExtensionContributionsSection(extensionSurfaces: ModuleExtensionSurfaceFacts): string {
|
|
2434
|
-
if (extensionSurfaces.contributions.length === 0) return `## UMES contributions\n\n${EMPTY_SECTION_MARKER}`
|
|
2435
|
-
const rows = extensionSurfaces.contributions.map((contribution) => {
|
|
2436
|
-
const targets = contribution.targets.map((entry) => entry.id).join(', ')
|
|
2437
|
-
const resolution = contribution.targets.map((entry) => entry.resolution).join(', ')
|
|
2438
|
-
const phases = [...(contribution.phases ?? []), ...(contribution.operations ?? [])].join(', ') || '—'
|
|
2439
|
-
const source = renderSourceRefLink({ sourcePath: contribution.source.path })
|
|
2440
|
-
return `| ${contribution.id} | ${contribution.kind} | ${targets || '—'} | ${phases} | ${compactContributionDetails(contribution)} | ${resolution || '—'} | ${source} |`
|
|
2441
|
-
})
|
|
2442
|
-
return [
|
|
2443
|
-
'## UMES contributions',
|
|
2444
|
-
'',
|
|
2445
|
-
'| ID | Kind | Target | Phase / operations | Contract | Resolution | Source |',
|
|
2446
|
-
'|---|---|---|---|---|---|---|',
|
|
2447
|
-
...rows,
|
|
2448
|
-
].join('\n')
|
|
2449
|
-
}
|
|
2450
|
-
|
|
2451
|
-
function renderExtensionDiagnosticsSection(extensionSurfaces: ModuleExtensionSurfaceFacts): string {
|
|
2452
|
-
const unbound = extensionSurfaces.hosts.filter((host) => !host.bound)
|
|
2453
|
-
if (unbound.length === 0 && extensionSurfaces.unresolved.length === 0) return ''
|
|
2454
|
-
const diagnostics = [
|
|
2455
|
-
...unbound.map((host) => `- unbound-helper: ${host.id}`),
|
|
2456
|
-
...extensionSurfaces.unresolved.map((entry) => `- ${entry.reason}: ${entry.key} (${entry.source.path})`),
|
|
2457
|
-
]
|
|
2458
|
-
return ['## UMES diagnostics', '', ...diagnostics].join('\n')
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
|
-
function renderExtensionTargetRef(target: ModuleExtensionTargetRef): string {
|
|
2462
|
-
const method = target.method ? ` ${target.method}` : ''
|
|
2463
|
-
const owner = target.moduleId ? ` @${target.moduleId}` : ''
|
|
2464
|
-
return `${target.kind}:${target.id}${method}${owner}`
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
function renderActivationBindingsSection(extensionSurfaces: ModuleExtensionSurfaceFacts): string {
|
|
2468
|
-
const activations = extensionSurfaces.activations ?? []
|
|
2469
|
-
if (activations.length === 0) return `## Active extension bindings\n\n${EMPTY_SECTION_MARKER}`
|
|
2470
|
-
const rows = activations.map((activation) => {
|
|
2471
|
-
const phases = activation.phases && activation.phases.length > 0 ? activation.phases.join(', ') : '—'
|
|
2472
|
-
const source = activation.source ? renderSourceRefLink(activation.source) : '—'
|
|
2473
|
-
const bridge = activation.bridge ? `${activation.bridge.factSection}:${activation.bridge.factKey}` : '—'
|
|
2474
|
-
return `| ${activation.id} | ${activation.kind} | ${renderExtensionTargetRef(activation.host)} | ${activation.contributionKinds.join(', ') || '—'} | ${phases} | ${bridge} | ${source} |`
|
|
2475
|
-
})
|
|
2476
|
-
return [
|
|
2477
|
-
'## Active extension bindings',
|
|
2478
|
-
'',
|
|
2479
|
-
'| Activation | Kind | Host | Contribution kinds | Phases | Bridge | Source |',
|
|
2480
|
-
'|---|---|---|---|---|---|---|',
|
|
2481
|
-
...rows,
|
|
2482
|
-
].join('\n')
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
|
-
function renderIncomingContributionsSection(extensionSurfaces: ModuleExtensionSurfaceFacts): string {
|
|
2486
|
-
const incoming = extensionSurfaces.incoming ?? []
|
|
2487
|
-
if (incoming.length === 0) return `## Incoming installed contributions\n\n${EMPTY_SECTION_MARKER}`
|
|
2488
|
-
const rows = incoming.map((entry) => {
|
|
2489
|
-
const activation = entry.activationId ?? '—'
|
|
2490
|
-
const source = entry.source ? renderSourceRefLink(entry.source) : '—'
|
|
2491
|
-
return `| ${entry.contributorModuleId} | ${entry.contributionKind} | ${renderExtensionTargetRef(entry.target)} | ${entry.resolution} | ${activation} | ${entry.contributionId} · ${source} |`
|
|
2492
|
-
})
|
|
2493
|
-
return [
|
|
2494
|
-
'## Incoming installed contributions',
|
|
2495
|
-
'',
|
|
2496
|
-
'| Contributor | Kind | Target | Resolution | Activation | Contribution · Source |',
|
|
2497
|
-
'|---|---|---|---|---|---|',
|
|
2498
|
-
...rows,
|
|
2499
|
-
].join('\n')
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
function renderContributionResolutionsSection(
|
|
2503
|
-
extensionSurfaces: ModuleExtensionSurfaceFacts,
|
|
2504
|
-
lookup: FactSourceLookup,
|
|
2505
|
-
): string {
|
|
2506
|
-
const resolutions = extensionSurfaces.contributionResolutions ?? []
|
|
2507
|
-
if (resolutions.length === 0) return `## Contribution resolutions\n\n${EMPTY_SECTION_MARKER}`
|
|
2508
|
-
const rows = resolutions.map((resolution) => {
|
|
2509
|
-
const activations = resolution.activationIds.length > 0 ? resolution.activationIds.join(', ') : '—'
|
|
2510
|
-
const source = renderLookedUpSource(lookup, 'extension-contribution', resolution.contributionId)
|
|
2511
|
-
return `| ${resolution.contributionId} | ${renderExtensionTargetRef(resolution.target)} | ${resolution.resolution} | ${activations} | ${source} |`
|
|
2512
|
-
})
|
|
2513
|
-
return [
|
|
2514
|
-
'## Contribution resolutions',
|
|
2515
|
-
'',
|
|
2516
|
-
'| Contribution | Target | Resolution | Activations | Source |',
|
|
2517
|
-
'|---|---|---|---|---|',
|
|
2518
|
-
...rows,
|
|
2519
|
-
].join('\n')
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
function renderOverrideTargetPath(target: ModuleOverrideTarget): string {
|
|
2523
|
-
let display = 'overrides'
|
|
2524
|
-
for (let index = 0; index < target.path.length; index += 1) {
|
|
2525
|
-
const segment = target.path[index]
|
|
2526
|
-
const isKeyedTerminal = target.key !== undefined && index === target.path.length - 1
|
|
2527
|
-
display += isKeyedTerminal ? `[${JSON.stringify(segment)}]` : `.${segment}`
|
|
2528
|
-
}
|
|
2529
|
-
return display
|
|
2530
|
-
}
|
|
2531
|
-
|
|
2532
|
-
function renderOverrideTargetsSection(targets: readonly ModuleOverrideTarget[] | undefined): string {
|
|
2533
|
-
if (!targets || targets.length === 0) return `## Exact override targets\n\n${EMPTY_SECTION_MARKER}`
|
|
2534
|
-
const header = '| Domain | Path / key | Supported modes | Referenced fact | Source |'
|
|
2535
|
-
const divider = '|---|---|---|---|---|'
|
|
2536
|
-
const rows = targets.map((target) => {
|
|
2537
|
-
const path = `\`${renderOverrideTargetPath(target)}\``
|
|
2538
|
-
const modes = target.modes.join(', ') || '—'
|
|
2539
|
-
const fact = `${target.factRef.factSection}:${target.factRef.factKey}`
|
|
2540
|
-
return `| ${target.domain} | ${path} | ${modes} | ${fact} | ${renderSourceRefLink(target.source)} |`
|
|
2541
|
-
})
|
|
2542
|
-
return ['## Exact override targets', '', header, divider, ...rows].join('\n')
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
873
|
export function renderModuleFactsMarkdown(facts: ModuleFacts): string {
|
|
2546
|
-
const extensionSurfaces = facts.extensionSurfaces ?? { hosts: [], contributions: [], unresolved: [] }
|
|
2547
|
-
const lookup = buildFactSourceLookup(facts)
|
|
2548
874
|
const sections = [
|
|
2549
875
|
`# ${facts.module} — module facts (generated, do not edit)`,
|
|
2550
|
-
renderVersionStamp(facts.coreVersion
|
|
2551
|
-
`Source root: ${renderSourceLink(facts.sourceRoot)}`,
|
|
876
|
+
renderVersionStamp(facts.coreVersion),
|
|
2552
877
|
'',
|
|
2553
|
-
renderEntitiesSection(facts.entities
|
|
878
|
+
renderEntitiesSection(facts.entities),
|
|
2554
879
|
'',
|
|
2555
|
-
renderEventsSection(facts.events
|
|
880
|
+
renderEventsSection(facts.events),
|
|
2556
881
|
'',
|
|
2557
|
-
|
|
2558
|
-
`## ACL features (${facts.aclFeatures.length})`,
|
|
2559
|
-
facts.aclFeatures,
|
|
2560
|
-
lookup,
|
|
2561
|
-
'acl-feature',
|
|
2562
|
-
'Feature',
|
|
2563
|
-
),
|
|
882
|
+
renderInlineListSection(`## ACL features (${facts.aclFeatures.length})`, facts.aclFeatures),
|
|
2564
883
|
'',
|
|
2565
884
|
renderApiRoutesSection(facts.apiRoutes),
|
|
2566
885
|
'',
|
|
2567
|
-
|
|
2568
|
-
'',
|
|
2569
|
-
renderLinkedFactsSection('## Frontend pages', facts.frontendPages.map((page) => ({ label: page.path, sourcePath: page.sourcePath }))),
|
|
886
|
+
renderInlineListSection('## DI service tokens', facts.diTokens),
|
|
2570
887
|
'',
|
|
2571
|
-
|
|
2572
|
-
'',
|
|
2573
|
-
renderSourceLinkedListSection('## Search entities', facts.searchEntities, lookup, 'search', 'Entity ID'),
|
|
888
|
+
renderInlineListSection('## Search entities', facts.searchEntities),
|
|
2574
889
|
'',
|
|
2575
890
|
renderHostTokensSection(facts.hostTokens),
|
|
2576
891
|
'',
|
|
2577
|
-
|
|
2578
|
-
'',
|
|
2579
|
-
renderExtensionContributionsSection(extensionSurfaces),
|
|
2580
|
-
'',
|
|
2581
|
-
renderActivationBindingsSection(extensionSurfaces),
|
|
2582
|
-
'',
|
|
2583
|
-
renderIncomingContributionsSection(extensionSurfaces),
|
|
2584
|
-
'',
|
|
2585
|
-
renderContributionResolutionsSection(extensionSurfaces, lookup),
|
|
892
|
+
renderInlineListSection('## Notifications', facts.notifications),
|
|
2586
893
|
'',
|
|
2587
|
-
|
|
2588
|
-
'',
|
|
2589
|
-
renderSourceLinkedListSection('## Notifications', facts.notifications, lookup, 'notification', 'Notification ID'),
|
|
2590
|
-
'',
|
|
2591
|
-
renderLinkedFactsSection('## CLI commands', facts.cliCommands.map((command) => ({ label: command.command, sourcePath: command.sourcePath }))),
|
|
2592
|
-
'',
|
|
2593
|
-
renderLinkedFactsSection('## AI tools / MCP capabilities', facts.aiTools.map((tool) => ({ label: tool.name, sourcePath: tool.sourcePath }))),
|
|
2594
|
-
'',
|
|
2595
|
-
renderLinkedFactsSection('## AI agents', facts.aiAgents.map((agent) => ({ label: agent.id, sourcePath: agent.sourcePath }))),
|
|
2596
|
-
'',
|
|
2597
|
-
renderOverrideTargetsSection(facts.overrideTargets),
|
|
894
|
+
renderInlineListSection('## CLI', facts.cli),
|
|
2598
895
|
'',
|
|
2599
896
|
]
|
|
2600
|
-
const owned = facts.ownedContracts ?? {}
|
|
2601
|
-
const ownedSections = [
|
|
2602
|
-
renderOwnedContractSection('## Owned contract — module metadata', owned['module-metadata']),
|
|
2603
|
-
renderOwnedContractSection('## Domain commands', owned.command),
|
|
2604
|
-
renderOwnedContractSection('## Workers', owned.worker),
|
|
2605
|
-
renderOwnedContractSection('## Page middleware', owned['page-middleware']),
|
|
2606
|
-
renderOwnedContractSection('## Setup', owned.setup),
|
|
2607
|
-
renderOwnedContractSection('## Encryption', owned.encryption),
|
|
2608
|
-
renderDiRegistrationsSection(owned['di-registration']),
|
|
2609
|
-
renderOwnedContractSection('## Custom entities', owned['custom-entity']),
|
|
2610
|
-
renderOwnedContractSection('## AI extensions', owned['ai-extension']),
|
|
2611
|
-
renderOwnedContractSection('## Generator plugins', owned['generator-plugin']),
|
|
2612
|
-
].filter((section) => section.length > 0)
|
|
2613
|
-
for (const section of ownedSections) {
|
|
2614
|
-
sections.push(section, '')
|
|
2615
|
-
}
|
|
2616
897
|
return sections.join('\n')
|
|
2617
898
|
}
|
|
2618
899
|
|
|
@@ -2621,17 +902,8 @@ export function toModuleFactsJsonEntry(facts: ModuleFacts): ModuleFactsJsonEntry
|
|
|
2621
902
|
title: facts.title,
|
|
2622
903
|
description: facts.description,
|
|
2623
904
|
coreVersion: facts.coreVersion,
|
|
2624
|
-
sourcePackage: facts.sourcePackage,
|
|
2625
|
-
sourceVersion: facts.sourceVersion,
|
|
2626
|
-
sourceRoot: facts.sourceRoot,
|
|
2627
905
|
entities: facts.entities,
|
|
2628
|
-
events: facts.events.map((event) => ({
|
|
2629
|
-
id: event.id,
|
|
2630
|
-
category: event.category,
|
|
2631
|
-
entity: event.entity,
|
|
2632
|
-
...(event.clientBroadcast !== undefined ? { clientBroadcast: event.clientBroadcast } : {}),
|
|
2633
|
-
...(event.portalBroadcast !== undefined ? { portalBroadcast: event.portalBroadcast } : {}),
|
|
2634
|
-
})),
|
|
906
|
+
events: facts.events.map((event) => ({ id: event.id, category: event.category, entity: event.entity })),
|
|
2635
907
|
aclFeatures: facts.aclFeatures,
|
|
2636
908
|
apiRoutes: facts.apiRoutes,
|
|
2637
909
|
diTokens: facts.diTokens,
|
|
@@ -2639,25 +911,6 @@ export function toModuleFactsJsonEntry(facts: ModuleFacts): ModuleFactsJsonEntry
|
|
|
2639
911
|
hostTokens: facts.hostTokens,
|
|
2640
912
|
notifications: facts.notifications,
|
|
2641
913
|
cli: facts.cli,
|
|
2642
|
-
backendPages: facts.backendPages,
|
|
2643
|
-
frontendPages: facts.frontendPages,
|
|
2644
|
-
cliCommands: facts.cliCommands,
|
|
2645
|
-
aiTools: facts.aiTools,
|
|
2646
|
-
aiAgents: facts.aiAgents,
|
|
2647
|
-
...(facts.extensionSurfaces ? { extensionSurfaces: facts.extensionSurfaces } : {}),
|
|
2648
|
-
...(facts.factSources && facts.factSources.length > 0 ? { factSources: facts.factSources } : {}),
|
|
2649
|
-
...(facts.ownedContracts && Object.keys(facts.ownedContracts).length > 0
|
|
2650
|
-
? { ownedContracts: facts.ownedContracts }
|
|
2651
|
-
: {}),
|
|
2652
|
-
...(facts.factDiagnostics && facts.factDiagnostics.length > 0
|
|
2653
|
-
? { factDiagnostics: facts.factDiagnostics }
|
|
2654
|
-
: {}),
|
|
2655
|
-
...(facts.overrideTargets && facts.overrideTargets.length > 0
|
|
2656
|
-
? { overrideTargets: facts.overrideTargets }
|
|
2657
|
-
: {}),
|
|
2658
|
-
...(facts.overrideTargetDiagnostics && facts.overrideTargetDiagnostics.length > 0
|
|
2659
|
-
? { overrideTargetDiagnostics: facts.overrideTargetDiagnostics }
|
|
2660
|
-
: {}),
|
|
2661
914
|
}
|
|
2662
915
|
}
|
|
2663
916
|
|
|
@@ -2694,14 +947,9 @@ export interface ExtractAllModuleFactsResult {
|
|
|
2694
947
|
factsByModule: Record<string, ModuleFacts>
|
|
2695
948
|
markdownByModule: Record<string, string>
|
|
2696
949
|
warnings: string[]
|
|
2697
|
-
frameworkMarkdown: string
|
|
2698
950
|
}
|
|
2699
951
|
|
|
2700
952
|
export function extractAllModuleFacts(options: ExtractAllModuleFactsOptions): ExtractAllModuleFactsResult {
|
|
2701
|
-
return withModuleExtensionFactExtractionCache(() => extractAllModuleFactsWithCache(options))
|
|
2702
|
-
}
|
|
2703
|
-
|
|
2704
|
-
function extractAllModuleFactsWithCache(options: ExtractAllModuleFactsOptions): ExtractAllModuleFactsResult {
|
|
2705
953
|
const sources: ModuleFactSource[] = options.sources
|
|
2706
954
|
? [...options.sources]
|
|
2707
955
|
: (options.coreSrcRoot
|
|
@@ -2719,65 +967,12 @@ function extractAllModuleFactsWithCache(options: ExtractAllModuleFactsOptions):
|
|
|
2719
967
|
moduleId: source.moduleId,
|
|
2720
968
|
moduleRoot: source.moduleRoot,
|
|
2721
969
|
coreVersion: options.coreVersion ?? null,
|
|
2722
|
-
sourcePackage: source.from ?? null,
|
|
2723
|
-
sourceVersion: source.packageVersion ?? null,
|
|
2724
970
|
registryPath: options.registryPath ?? null,
|
|
2725
971
|
registrySource: options.registrySource ?? null,
|
|
2726
972
|
})
|
|
2727
973
|
factsByModule[source.moduleId] = facts
|
|
974
|
+
markdownByModule[source.moduleId] = renderModuleFactsMarkdown(facts)
|
|
2728
975
|
warnings.push(...facts.warnings)
|
|
2729
976
|
}
|
|
2730
|
-
|
|
2731
|
-
Object.entries(factsByModule).map(([moduleId, facts]) => [
|
|
2732
|
-
moduleId,
|
|
2733
|
-
facts.extensionSurfaces ?? { hosts: [], contributions: [], unresolved: [] },
|
|
2734
|
-
]),
|
|
2735
|
-
)
|
|
2736
|
-
const correlated = correlateModuleExtensionFacts({
|
|
2737
|
-
surfacesByModule,
|
|
2738
|
-
entityIds: new Set(Object.values(factsByModule).flatMap((facts) => facts.entities.map((entity) => entity.id))),
|
|
2739
|
-
eventIds: new Set(Object.values(factsByModule).flatMap((facts) => facts.events.map((event) => event.id))),
|
|
2740
|
-
apiRoutes: new Set([
|
|
2741
|
-
...Object.values(factsByModule).flatMap((facts) => facts.apiRoutes.map((route) => route.path)),
|
|
2742
|
-
...sources.flatMap((source) => extractKnownApiRouteIds(source.moduleId, source.moduleRoot)),
|
|
2743
|
-
]),
|
|
2744
|
-
commandIds: new Set(sources.flatMap((source) => extractKnownCommandIds(source.moduleId, source.moduleRoot))),
|
|
2745
|
-
})
|
|
2746
|
-
assertNoUnresolvedExtensionTargets(correlated)
|
|
2747
|
-
const apiRouteOwners = new Map<string, {
|
|
2748
|
-
moduleId: string
|
|
2749
|
-
source: ModuleFactSourceRef
|
|
2750
|
-
bridges: ApiRouteInterceptorBridge[]
|
|
2751
|
-
}>()
|
|
2752
|
-
const commandOwners = new Map<string, { moduleId: string; source: ModuleFactSourceRef }>()
|
|
2753
|
-
for (const source of [...sources].sort((left, right) => left.moduleId.localeCompare(right.moduleId))) {
|
|
2754
|
-
const facts = factsByModule[source.moduleId]
|
|
2755
|
-
const sourceRoot = facts ? facts.sourceRoot : source.moduleId
|
|
2756
|
-
const owners = extractActivationTargetOwners({
|
|
2757
|
-
moduleId: source.moduleId,
|
|
2758
|
-
moduleRoot: source.moduleRoot,
|
|
2759
|
-
sourceRoot,
|
|
2760
|
-
})
|
|
2761
|
-
for (const route of owners.apiRoutes) {
|
|
2762
|
-
if (!apiRouteOwners.has(route.id)) {
|
|
2763
|
-
apiRouteOwners.set(route.id, { moduleId: source.moduleId, source: route.source, bridges: route.bridges })
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
for (const command of owners.commands) {
|
|
2767
|
-
if (!commandOwners.has(command.id)) commandOwners.set(command.id, { moduleId: source.moduleId, source: command.source })
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
const withIncoming = correlateIncomingExtensions({
|
|
2771
|
-
surfacesByModule: correlated,
|
|
2772
|
-
apiRouteOwners,
|
|
2773
|
-
commandOwners,
|
|
2774
|
-
})
|
|
2775
|
-
for (const moduleId of Object.keys(factsByModule).sort((left, right) => left.localeCompare(right))) {
|
|
2776
|
-
factsByModule[moduleId].extensionSurfaces = withIncoming[moduleId]
|
|
2777
|
-
markdownByModule[moduleId] = renderModuleFactsMarkdown(factsByModule[moduleId])
|
|
2778
|
-
warnings.push(...correlated[moduleId].unresolved.map((entry) =>
|
|
2779
|
-
`[module-facts] ${moduleId} ${entry.reason}: ${entry.key} (${entry.source.path})`,
|
|
2780
|
-
))
|
|
2781
|
-
}
|
|
2782
|
-
return { factsByModule, markdownByModule, warnings, frameworkMarkdown: renderFrameworkExtensionPointsMarkdown() }
|
|
977
|
+
return { factsByModule, markdownByModule, warnings }
|
|
2783
978
|
}
|