@open-mercato/cli 0.6.7 → 0.6.8-canary.319.1.5b4be4bc52
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 +77 -4
- package/build.mjs +68 -11
- package/dist/agentic/claude-code/hooks/entity-migration-check.ts +32 -88
- package/dist/agentic/codex/enforcement-rules.md +2 -16
- package/dist/agentic/cursor/hooks/entity-migration-check.mjs +19 -50
- package/dist/agentic/cursor/rules/entity-guard.mdc +9 -21
- package/dist/agentic/cursor/rules/generated-guard.mdc +3 -5
- package/dist/agentic/cursor/rules/open-mercato.mdc +3 -29
- package/dist/agentic/guides/ai-workflows.md +76 -0
- package/dist/agentic/guides/architecture.md +107 -0
- package/dist/agentic/guides/backend-ui.md +84 -0
- package/dist/agentic/guides/contracts.md +92 -0
- package/dist/agentic/guides/extensions.md +121 -0
- package/dist/agentic/guides/framework-extension-points.md +36 -0
- package/dist/agentic/guides/integrations.md +81 -0
- package/dist/agentic/guides/module-facts.json +116757 -3951
- package/dist/agentic/guides/module-system.md +12 -127
- package/dist/agentic/guides/modules/ai_assistant.md +227 -28
- package/dist/agentic/guides/modules/api_docs.md +58 -2
- package/dist/agentic/guides/modules/api_keys.md +92 -7
- package/dist/agentic/guides/modules/attachments.md +119 -14
- package/dist/agentic/guides/modules/audit_logs.md +131 -10
- package/dist/agentic/guides/modules/auth.md +276 -33
- package/dist/agentic/guides/modules/business_rules.md +149 -14
- package/dist/agentic/guides/modules/catalog.md +482 -43
- package/dist/agentic/guides/modules/channel_gmail.md +82 -3
- package/dist/agentic/guides/modules/channel_imap.md +82 -3
- package/dist/agentic/guides/modules/checkout.md +305 -27
- package/dist/agentic/guides/modules/communication_channels.md +315 -33
- package/dist/agentic/guides/modules/configs.md +104 -8
- package/dist/agentic/guides/modules/content.md +57 -2
- package/dist/agentic/guides/modules/currencies.md +152 -17
- package/dist/agentic/guides/modules/customer_accounts.md +387 -45
- package/dist/agentic/guides/modules/customers.md +746 -85
- package/dist/agentic/guides/modules/dashboards.md +155 -10
- package/dist/agentic/guides/modules/data_sync.md +149 -16
- package/dist/agentic/guides/modules/design_system.md +102 -0
- package/dist/agentic/guides/modules/dictionaries.md +107 -12
- package/dist/agentic/guides/modules/directory.md +148 -15
- package/dist/agentic/guides/modules/entities.md +145 -13
- package/dist/agentic/guides/modules/events.md +59 -2
- package/dist/agentic/guides/modules/feature_toggles.md +119 -9
- package/dist/agentic/guides/modules/gateway_stripe.md +92 -4
- package/dist/agentic/guides/modules/generators.md +45 -2
- package/dist/agentic/guides/modules/inbox_ops.md +207 -27
- package/dist/agentic/guides/modules/integrations.md +149 -16
- package/dist/agentic/guides/modules/messages.md +223 -24
- package/dist/agentic/guides/modules/notifications.md +96 -8
- package/dist/agentic/guides/modules/onboarding.md +77 -6
- package/dist/agentic/guides/modules/payment_gateways.md +168 -21
- package/dist/agentic/guides/modules/perspectives.md +77 -7
- package/dist/agentic/guides/modules/planner.md +136 -18
- package/dist/agentic/guides/modules/portal.md +93 -2
- package/dist/agentic/guides/modules/progress.md +104 -14
- package/dist/agentic/guides/modules/query_index.md +154 -15
- package/dist/agentic/guides/modules/record_locks.md +206 -21
- package/dist/agentic/guides/modules/resources.md +197 -30
- package/dist/agentic/guides/modules/sales.md +687 -84
- package/dist/agentic/guides/modules/scheduler.md +133 -14
- package/dist/agentic/guides/modules/search.md +170 -6
- package/dist/agentic/guides/modules/security.md +294 -35
- package/dist/agentic/guides/modules/shipping_carriers.md +134 -18
- package/dist/agentic/guides/modules/sso.md +151 -26
- package/dist/agentic/guides/modules/staff.md +372 -57
- package/dist/agentic/guides/modules/storage_s3.md +95 -5
- package/dist/agentic/guides/modules/sync_akeneo.md +85 -3
- package/dist/agentic/guides/modules/sync_excel.md +75 -6
- package/dist/agentic/guides/modules/system_status_overlays.md +60 -2
- package/dist/agentic/guides/modules/translations.md +104 -10
- package/dist/agentic/guides/modules/webhooks.md +203 -24
- package/dist/agentic/guides/modules/wms.md +469 -47
- package/dist/agentic/guides/modules/workflows.md +302 -43
- package/dist/agentic/guides/reference-module-facts.json +6370 -0
- package/dist/agentic/guides/reference-modules/example.md +451 -0
- package/dist/agentic/guides/spec-delivery.md +42 -0
- package/dist/agentic/guides/testing-debugging.md +80 -0
- package/dist/agentic/guides/upstream/AGENTS.md +299 -0
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +316 -0
- package/dist/agentic/guides/upstream/manifest.json +8 -0
- package/dist/agentic/shared/AGENTS.md.template +120 -311
- package/dist/agentic/shared/ai/agentic.config.json +2 -0
- package/dist/agentic/shared/ai/harness/README.md +50 -0
- package/dist/agentic/shared/ai/harness/RELEASE.md +57 -0
- package/dist/agentic/shared/ai/harness/cases.json +15050 -0
- package/dist/agentic/shared/ai/harness/cases.schema.json +188 -0
- package/dist/agentic/shared/ai/harness/fixtures/index.json +51 -0
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +166 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +21 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +96 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +172 -0
- package/dist/agentic/shared/ai/harness/knowledge-change.schema.json +228 -0
- package/dist/agentic/shared/ai/harness/release-matrix.json +163 -0
- package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
- package/dist/agentic/shared/ai/harness/result.schema.json +109 -0
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +22 -0
- package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
- package/dist/agentic/shared/ai/harness/validators.json +82 -0
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +2121 -0
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +1096 -0
- package/dist/agentic/shared/ai/harness/writable-spec-oracles.mjs +412 -0
- package/dist/agentic/shared/ai/lessons/_template.md +16 -0
- package/dist/agentic/shared/ai/lessons.md +26 -3
- package/dist/agentic/shared/ai/review-checklist.md +36 -0
- package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +21 -0
- package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +4 -0
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +98 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +21 -241
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +31 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +53 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +15 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +11 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +30 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +12 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +13 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +14 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +28 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +22 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +19 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +17 -601
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +21 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +15 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +16 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +10 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +15 -310
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +19 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +31 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +100 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +16 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/knowledge-change.md +115 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +17 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +25 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +11 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +13 -0
- package/dist/agentic/shared/ai/skills/om-help/SKILL.md +15 -97
- package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +18 -0
- package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +20 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +33 -152
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +33 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +40 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +27 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +24 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +25 -708
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +16 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +25 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +15 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +12 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +34 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +4 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +11 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +31 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +39 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +43 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +11 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +20 -820
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +32 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +172 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +14 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +83 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +40 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +11 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +22 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +13 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +41 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +19 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +44 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +39 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +20 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +57 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +11 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +486 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +20 -843
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +42 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +27 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +13 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +43 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +11 -0
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +15 -63
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +15 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +17 -467
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +29 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +14 -0
- package/dist/agentic/shared/ai/skills/tiers.json +143 -21
- package/dist/agentic/shared/ai/skills/tiers.schema.json +52 -40
- package/dist/agentic/shared/ai/specs/README.md +4 -3
- package/dist/agentic/shared/ai/specs/SPEC-000-template.md +226 -10
- package/dist/agentic/shared/ai/trackers/github.md +108 -4
- package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +224 -0
- package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +3585 -0
- package/dist/agentic/shared/scripts/execution-sandbox.mjs +329 -0
- package/dist/agentic/shared/scripts/framework-context.mjs +888 -0
- package/dist/agentic/shared/scripts/install-skills.mjs +1029 -0
- package/dist/agentic/shared/scripts/install-skills.sh +4 -694
- package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +222 -0
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1774 -0
- package/dist/agentic/shared/scripts/validate-knowledge-change.mjs +911 -0
- package/dist/bin.js +13 -1
- package/dist/bin.js.map +2 -2
- package/dist/lib/__integration__/TC-INT-008.spec.js +88 -8
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-init.js +13 -3
- package/dist/lib/agentic-init.js.map +2 -2
- package/dist/lib/agentic-setup.js +434 -186
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/extensions/injection-widgets.js +20 -0
- package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
- package/dist/lib/generators/module-extension-facts.js +1872 -0
- package/dist/lib/generators/module-extension-facts.js.map +7 -0
- package/dist/lib/generators/module-fact-sources.js +70 -0
- package/dist/lib/generators/module-fact-sources.js.map +7 -0
- package/dist/lib/generators/module-facts-discovery.js +64 -7
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +1629 -52
- package/dist/lib/generators/module-facts.js.map +3 -3
- package/dist/lib/generators/module-override-targets.js +688 -0
- package/dist/lib/generators/module-override-targets.js.map +7 -0
- package/dist/lib/generators/module-registry.js +11 -5
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/load-env.js +24 -0
- package/dist/lib/load-env.js.map +7 -0
- package/dist/lib/resolver.js +2 -0
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/telemetry-init.js +358 -0
- package/dist/lib/telemetry-init.js.map +7 -0
- package/dist/lib/testing/integration.js +5 -2
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +23 -1
- package/dist/mercato.js.map +2 -2
- package/package.json +13 -11
- package/src/bin.ts +33 -1
- package/src/lib/__integration__/TC-INT-008.spec.ts +107 -9
- package/src/lib/__tests__/agentic-init.test.ts +41 -5
- package/src/lib/__tests__/agentic-setup.ownership.test.ts +386 -0
- package/src/lib/__tests__/injection-widget-id-validation.test.ts +184 -0
- package/src/lib/__tests__/load-env.test.ts +65 -0
- package/src/lib/__tests__/telemetry-init.test.ts +298 -0
- package/src/lib/agentic-init.ts +15 -3
- package/src/lib/agentic-setup.ts +573 -222
- package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
- package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +603 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +139 -2
- package/src/lib/generators/__tests__/module-facts.computed-di-token.test.ts +88 -0
- package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +1 -0
- package/src/lib/generators/__tests__/module-facts.discovery.test.ts +54 -10
- package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +108 -0
- package/src/lib/generators/__tests__/module-facts.local-reference.test.ts +391 -0
- package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
- package/src/lib/generators/__tests__/module-facts.search-entity-id.test.ts +151 -0
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +160 -0
- package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
- package/src/lib/generators/__tests__/module-override-targets.test.ts +328 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +18 -0
- package/src/lib/generators/extensions/injection-widgets.ts +51 -0
- package/src/lib/generators/module-extension-facts.ts +2433 -0
- package/src/lib/generators/module-fact-sources.ts +108 -0
- package/src/lib/generators/module-facts-discovery.ts +104 -7
- package/src/lib/generators/module-facts.ts +2387 -138
- package/src/lib/generators/module-override-targets.ts +863 -0
- package/src/lib/generators/module-registry.ts +16 -6
- package/src/lib/load-env.ts +30 -0
- package/src/lib/resolver.ts +3 -0
- package/src/lib/telemetry-init.ts +461 -0
- package/src/lib/testing/__tests__/integration.test.ts +4 -0
- package/src/lib/testing/integration.ts +5 -2
- package/src/mercato.ts +26 -1
- package/dist/agentic/guides/cache.md +0 -45
- package/dist/agentic/guides/core.md +0 -453
- package/dist/agentic/guides/events.md +0 -117
- package/dist/agentic/guides/queue.md +0 -53
- package/dist/agentic/guides/search.md +0 -115
- package/dist/agentic/guides/shared.md +0 -108
- package/dist/agentic/guides/ui.md +0 -347
- package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +0 -27
- package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +0 -27
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +0 -260
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +0 -146
- package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +0 -84
- package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +0 -193
- package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +0 -34
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +0 -762
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +0 -50
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +0 -97
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +0 -271
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +0 -101
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import type { ModuleFactsJsonEntry } from '../module-facts'
|
|
2
|
+
import {
|
|
3
|
+
ALL_OVERRIDE_DOMAINS,
|
|
4
|
+
MODULE_OVERRIDE_TARGET_ADAPTERS,
|
|
5
|
+
collectModuleOverrideTargets,
|
|
6
|
+
computeOverrideTargetId,
|
|
7
|
+
} from '../module-override-targets'
|
|
8
|
+
|
|
9
|
+
const SOURCE_ROOT = 'node_modules/@open-mercato/core/src/modules/demo'
|
|
10
|
+
const MODULE_ID = 'demo'
|
|
11
|
+
|
|
12
|
+
function baseFacts(overrides: Partial<ModuleFactsJsonEntry> = {}): ModuleFactsJsonEntry {
|
|
13
|
+
return {
|
|
14
|
+
title: 'Demo',
|
|
15
|
+
description: null,
|
|
16
|
+
coreVersion: null,
|
|
17
|
+
sourcePackage: '@open-mercato/core',
|
|
18
|
+
sourceVersion: null,
|
|
19
|
+
sourceRoot: SOURCE_ROOT,
|
|
20
|
+
entities: [],
|
|
21
|
+
events: [],
|
|
22
|
+
aclFeatures: ['demo.view'],
|
|
23
|
+
apiRoutes: [
|
|
24
|
+
{ path: '/demo/items', methods: ['GET', 'POST'], auth: {}, sourcePath: `${SOURCE_ROOT}/api/items/route.ts` },
|
|
25
|
+
],
|
|
26
|
+
diTokens: ['demoService'],
|
|
27
|
+
searchEntities: [],
|
|
28
|
+
hostTokens: { entityIds: [], tableIds: [] },
|
|
29
|
+
notifications: ['demo.alert'],
|
|
30
|
+
cli: ['demo:seed'],
|
|
31
|
+
backendPages: [{ path: '/backend/demo/list', sourcePath: `${SOURCE_ROOT}/backend/demo/list/page.tsx` }],
|
|
32
|
+
frontendPages: [{ path: '/demo-portal', sourcePath: `${SOURCE_ROOT}/frontend/demo-portal/page.tsx` }],
|
|
33
|
+
cliCommands: [{ command: 'demo:seed', sourcePath: `${SOURCE_ROOT}/cli.ts` }],
|
|
34
|
+
aiTools: [{ name: 'demo.tool', sourcePath: `${SOURCE_ROOT}/ai-tools.ts` }],
|
|
35
|
+
aiAgents: [{ id: 'demo.agent', sourcePath: `${SOURCE_ROOT}/ai-agents.ts` }],
|
|
36
|
+
factSources: [
|
|
37
|
+
{ kind: 'acl-feature', id: 'demo.view', source: { sourcePath: `${SOURCE_ROOT}/acl.ts` } },
|
|
38
|
+
{ kind: 'notification', id: 'demo.alert', source: { sourcePath: `${SOURCE_ROOT}/notifications.ts` } },
|
|
39
|
+
],
|
|
40
|
+
ownedContracts: {
|
|
41
|
+
worker: [{ kind: 'worker', id: 'demo.worker', source: { sourcePath: `${SOURCE_ROOT}/workers/demo.ts`, line: 3 } }],
|
|
42
|
+
'page-middleware': [{ kind: 'page-middleware', id: 'demo.guard', source: { sourcePath: `${SOURCE_ROOT}/backend/middleware.ts`, line: 2 }, metadata: { surface: 'backend' } }],
|
|
43
|
+
setup: [{ kind: 'setup', id: 'demo:setup', source: { sourcePath: `${SOURCE_ROOT}/setup.ts`, line: 1 }, metadata: { hooks: ['seedDefaults', 'seedExamples', 'onTenantCreated', 'installStuff'], roles: ['admin'] } }],
|
|
44
|
+
'di-registration': [{ kind: 'di-registration', id: 'demoService', source: { sourcePath: `${SOURCE_ROOT}/di.ts`, line: 5 }, metadata: { registrationKind: 'class', lifetime: 'scoped' } }],
|
|
45
|
+
encryption: [{ kind: 'encryption', id: 'demo:secret', source: { sourcePath: `${SOURCE_ROOT}/encryption.ts`, line: 2 }, metadata: { fields: ['token'] } }],
|
|
46
|
+
'ai-extension': [
|
|
47
|
+
{ kind: 'ai-extension', id: 'agent-extension:other.agent', source: { sourcePath: `${SOURCE_ROOT}/ai-agents.extensions.ts`, line: 1 }, metadata: { target: 'agent', mode: 'extension', targetAgentId: 'other.agent' } },
|
|
48
|
+
{ kind: 'ai-extension', id: 'agent-override:other.assistant', source: { sourcePath: `${SOURCE_ROOT}/ai-agents.ts`, line: 7 }, metadata: { target: 'agent', mode: 'override', overrideKey: 'other.assistant' } },
|
|
49
|
+
{ kind: 'ai-extension', id: 'tool-override:other.tool', source: { sourcePath: `${SOURCE_ROOT}/ai-tools.ts`, line: 9 }, metadata: { target: 'tool', mode: 'override', overrideKey: 'other.tool' } },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
extensionSurfaces: {
|
|
53
|
+
hosts: [],
|
|
54
|
+
unresolved: [],
|
|
55
|
+
contributions: [
|
|
56
|
+
{ id: 'demo:sub', kind: 'subscriber', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/subscribers/demo.ts`, symbol: 'metadata' }, details: { event: 'demo.e', subscriberId: 'demo:sub', persistent: true, sync: false } },
|
|
57
|
+
{ id: 'demo.interceptor', kind: 'api-interceptor', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/api/interceptors.ts`, symbol: 'demo.interceptor' }, details: { route: 'items', methods: ['POST'], phases: ['before'], activation: 'crud-pipeline', timeoutMs: 2000, failurePosture: 'fallback' } },
|
|
58
|
+
{ id: 'demo.cmd', kind: 'command-interceptor', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/commands/interceptors.ts`, symbol: 'demo.cmd' }, details: { targetCommand: 'demo.thing.update', phases: ['before-execute'] } },
|
|
59
|
+
{ id: 'demo.enricher', kind: 'response-enricher', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/data/enrichers.ts`, symbol: 'demo.enricher' }, details: { targetEntity: 'demo.entity', surfaces: ['list'], timeoutMs: 2000, fallback: 'none', critical: false, cachePosture: 'record-pure' } },
|
|
60
|
+
{ id: 'demo.widget@profile:demo:slot', kind: 'widget', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/widgets/injection-table.ts`, symbol: 'injectionTable' }, details: { payload: 'render', registryKey: 'demo.widget', executionGuard: 'host' } },
|
|
61
|
+
{ id: 'dashboard:demo.card', kind: 'widget', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/widgets/dashboard/card/widget.ts`, symbol: 'widget.metadata' }, details: { payload: 'dashboard', registryKey: 'demo.card', executionGuard: 'both' } },
|
|
62
|
+
{ id: 'demo.table-widget@data-table:demo.list:columns', kind: 'data-table', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/widgets/injection-table.ts`, symbol: 'injectionTable' }, details: { payload: 'column', tableId: 'demo.list', executionGuard: 'host' } },
|
|
63
|
+
{ id: 'demo.override.0:section:demo.login', kind: 'component-override', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/widgets/components.ts`, symbol: 'componentOverrides' }, details: { handle: 'section:demo.login', mode: 'replace', propsContract: 'x' } },
|
|
64
|
+
{ id: 'demo.mutation-guard', kind: 'mutation-guard', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/data/guards.ts`, symbol: 'demo.mutation-guard' }, details: { entityId: 'demo.entity', operations: ['create'], capabilities: ['block'], optimisticLock: 'preserved' } },
|
|
65
|
+
{ id: 'notification:demo.alert', kind: 'specialized-registry', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/notifications.ts`, symbol: 'demo.alert' }, details: { registry: 'notification', registryId: 'demo.alert', specialistRoute: 'notifications' } },
|
|
66
|
+
{ id: 'demo.alert-handler', kind: 'browser-reaction', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/notifications.handlers.ts`, symbol: 'notificationHandlers' }, details: { transports: ['notification-effect'], hooks: ['useNotificationEffect'], audienceScopeContract: 'tenant', overrideKey: 'demo.alert-handler' } },
|
|
67
|
+
{ id: 'demo.notification-handler.1', kind: 'browser-reaction', targets: [], scopeContract: '', source: { path: `${SOURCE_ROOT}/notifications.handlers.ts`, symbol: 'notificationHandlers' }, details: { transports: ['notification-effect'], hooks: ['useNotificationEffect'], audienceScopeContract: 'tenant' } },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
...overrides,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function targetFor(facts: ModuleFactsJsonEntry, domain: string, key?: string) {
|
|
75
|
+
const { overrideTargets } = collectModuleOverrideTargets(facts)
|
|
76
|
+
return overrideTargets.find((target) => target.domain === domain && (key === undefined || target.key === key))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
describe('module override targets — adapter registry coverage', () => {
|
|
80
|
+
it('covers every runtime override domain with an adapter or explicit framework-only classification', () => {
|
|
81
|
+
const adaptersByDomain = new Map(MODULE_OVERRIDE_TARGET_ADAPTERS.map((adapter) => [adapter.domain, adapter]))
|
|
82
|
+
for (const domain of ALL_OVERRIDE_DOMAINS) {
|
|
83
|
+
const adapter = adaptersByDomain.get(domain)
|
|
84
|
+
expect(adapter).toBeDefined()
|
|
85
|
+
}
|
|
86
|
+
for (const adapter of MODULE_OVERRIDE_TARGET_ADAPTERS) {
|
|
87
|
+
expect(ALL_OVERRIDE_DOMAINS).toContain(adapter.domain)
|
|
88
|
+
}
|
|
89
|
+
// exactly the 16 runtime domains, one adapter each
|
|
90
|
+
expect(ALL_OVERRIDE_DOMAINS).toHaveLength(16)
|
|
91
|
+
expect(MODULE_OVERRIDE_TARGET_ADAPTERS).toHaveLength(16)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('classifies nav as framework-only (never a module target)', () => {
|
|
95
|
+
const navAdapter = MODULE_OVERRIDE_TARGET_ADAPTERS.find((adapter) => adapter.domain === 'nav')
|
|
96
|
+
expect(navAdapter?.frameworkOnly).toBe(true)
|
|
97
|
+
expect(navAdapter?.collect(baseFacts())).toEqual([])
|
|
98
|
+
const { overrideTargets } = collectModuleOverrideTargets(baseFacts())
|
|
99
|
+
expect(overrideTargets.some((target) => target.domain === 'nav')).toBe(false)
|
|
100
|
+
expect(overrideTargets.some((target) => JSON.stringify(target.path) === JSON.stringify(['nav', 'groupOrder']))).toBe(false)
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('module override targets — golden target per domain family', () => {
|
|
105
|
+
const { overrideTargets } = collectModuleOverrideTargets(baseFacts())
|
|
106
|
+
const byPath = (path: string[]) => overrideTargets.find((target) => JSON.stringify(target.path) === JSON.stringify(path))
|
|
107
|
+
|
|
108
|
+
it.each([
|
|
109
|
+
['ai', ['ai', 'agents', 'demo.agent'], 'demo.agent', ['disable-replace']],
|
|
110
|
+
['ai', ['ai', 'tools', 'demo.tool'], 'demo.tool', ['disable-replace']],
|
|
111
|
+
['ai', ['ai', 'extensions'], undefined, ['additive']],
|
|
112
|
+
['routes', ['routes', 'api', 'GET /api/demo/items'], 'GET /api/demo/items', ['disable-replace']],
|
|
113
|
+
['routes', ['routes', 'api', 'POST /api/demo/items'], 'POST /api/demo/items', ['disable-replace']],
|
|
114
|
+
['routes', ['routes', 'pages', 'backend:/backend/demo/list'], 'backend:/backend/demo/list', ['disable-replace']],
|
|
115
|
+
['routes', ['routes', 'pages', 'frontend:/demo-portal'], 'frontend:/demo-portal', ['disable-replace']],
|
|
116
|
+
['events', ['events', 'subscribers', 'demo:sub'], 'demo:sub', ['disable-replace']],
|
|
117
|
+
['workers', ['workers', 'demo.worker'], 'demo.worker', ['disable-replace']],
|
|
118
|
+
['widgets', ['widgets', 'injection', 'demo.widget'], 'demo.widget', ['disable-replace']],
|
|
119
|
+
['widgets', ['widgets', 'injection', 'demo.table-widget'], 'demo.table-widget', ['disable-replace']],
|
|
120
|
+
['widgets', ['widgets', 'dashboard', 'demo.card'], 'demo.card', ['disable-replace']],
|
|
121
|
+
['widgets', ['widgets', 'components', 'section:demo.login'], 'section:demo.login', ['disable-replace']],
|
|
122
|
+
['notifications', ['notifications', 'types', 'demo.alert'], 'demo.alert', ['disable-replace']],
|
|
123
|
+
['interceptors', ['interceptors', 'demo.interceptor'], 'demo.interceptor', ['disable-replace']],
|
|
124
|
+
['commandInterceptors', ['commandInterceptors', 'demo.cmd'], 'demo.cmd', ['disable-replace']],
|
|
125
|
+
['enrichers', ['enrichers', 'demo.enricher'], 'demo.enricher', ['disable-replace']],
|
|
126
|
+
['guards', ['guards', 'demo.guard'], 'demo.guard', ['disable-replace']],
|
|
127
|
+
['cli', ['cli', 'demo:seed'], 'demo:seed', ['disable-replace']],
|
|
128
|
+
['setup', ['setup', 'seedDefaults'], undefined, ['replace']],
|
|
129
|
+
['acl', ['acl', 'features', 'demo.view'], 'demo.view', ['disable-replace']],
|
|
130
|
+
['di', ['di', 'demoService'], 'demoService', ['disable-replace']],
|
|
131
|
+
['encryption', ['encryption', 'maps', 'demo:secret'], 'demo:secret', ['disable-replace']],
|
|
132
|
+
])('emits %s target at %j', (domain, path, key, modes) => {
|
|
133
|
+
const target = byPath(path as string[])
|
|
134
|
+
expect(target).toBeDefined()
|
|
135
|
+
expect(target?.domain).toBe(domain)
|
|
136
|
+
expect(target?.key).toBe(key)
|
|
137
|
+
expect(target?.modes).toEqual(modes)
|
|
138
|
+
expect(target?.factRef.factSection.length).toBeGreaterThan(0)
|
|
139
|
+
expect(target?.source.sourcePath.length).toBeGreaterThan(0)
|
|
140
|
+
expect(target?.id).toBe(computeOverrideTargetId(MODULE_ID, domain as never, path as string[]))
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('emits the supported setup hook/profile paths only (not unsupported hooks)', () => {
|
|
144
|
+
const setupPaths = overrideTargets.filter((t) => t.domain === 'setup').map((t) => t.path[1]).sort()
|
|
145
|
+
expect(setupPaths).toEqual(['defaultRoleFeatures', 'onTenantCreated', 'seedDefaults', 'seedExamples'])
|
|
146
|
+
expect(setupPaths).not.toContain('installStuff')
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
describe('module override targets — invariants', () => {
|
|
151
|
+
const facts = baseFacts()
|
|
152
|
+
const { overrideTargets } = collectModuleOverrideTargets(facts)
|
|
153
|
+
|
|
154
|
+
it('holds the terminal-key invariant for every adapter/target', () => {
|
|
155
|
+
for (const target of overrideTargets) {
|
|
156
|
+
if (target.key !== undefined) {
|
|
157
|
+
expect(target.path[target.path.length - 1]).toBe(target.key)
|
|
158
|
+
} else {
|
|
159
|
+
// keyless targets are structured singleton properties, never a lookup key
|
|
160
|
+
expect(['ai/extensions', 'setup']).toContain(
|
|
161
|
+
target.path[0] === 'setup' ? 'setup' : target.path.join('/'),
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('preserves nested widget and AI paths without flattening keys', () => {
|
|
168
|
+
const widget = targetFor(facts, 'widgets', 'demo.widget')
|
|
169
|
+
expect(widget?.path).toEqual(['widgets', 'injection', 'demo.widget'])
|
|
170
|
+
const agent = targetFor(facts, 'ai', 'demo.agent')
|
|
171
|
+
expect(agent?.path).toEqual(['ai', 'agents', 'demo.agent'])
|
|
172
|
+
// never collapse to a single flattened dotted segment
|
|
173
|
+
for (const target of overrideTargets) {
|
|
174
|
+
expect(target.path.some((segment) => segment === `${target.domain}.injection` || segment === `${target.domain}.agents`)).toBe(false)
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('points CLI override targets at the source-bearing command fact', () => {
|
|
179
|
+
const cli = targetFor(facts, 'cli', 'demo:seed')
|
|
180
|
+
expect(cli?.factRef).toEqual({ factSection: 'cliCommands', factKey: 'demo:seed' })
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('exposes DI tokens with the safe-metadata-only note and no runtime value/body', () => {
|
|
184
|
+
const di = targetFor(facts, 'di', 'demoService')
|
|
185
|
+
expect(di?.notes).toEqual(['safe-metadata-only'])
|
|
186
|
+
expect(di?.factRef).toEqual({ factSection: 'ownedContracts.di-registration', factKey: 'demoService' })
|
|
187
|
+
const serialized = JSON.stringify(di)
|
|
188
|
+
for (const forbidden of ['register', 'asClass', 'asFunction', 'asValue', 'handler', 'resolve', 'lifetime', 'value']) {
|
|
189
|
+
expect(serialized).not.toContain(forbidden)
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('keeps page middleware under guards and never emits a mutation guard there', () => {
|
|
194
|
+
const guard = targetFor(facts, 'guards', 'demo.guard')
|
|
195
|
+
expect(guard?.notes).toEqual(['page-middleware-not-mutation-guard'])
|
|
196
|
+
expect(guard?.factRef.factSection).toBe('ownedContracts.page-middleware')
|
|
197
|
+
const guardsTargets = overrideTargets.filter((t) => t.domain === 'guards')
|
|
198
|
+
expect(guardsTargets.every((t) => t.key !== 'demo.mutation-guard')).toBe(true)
|
|
199
|
+
expect(guardsTargets.every((t) => !t.source.sourcePath.includes('data/guards.ts'))).toBe(true)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('sorts deterministically and regenerates byte-identically', () => {
|
|
203
|
+
const first = collectModuleOverrideTargets(baseFacts())
|
|
204
|
+
const second = collectModuleOverrideTargets(baseFacts())
|
|
205
|
+
expect(JSON.stringify(second)).toBe(JSON.stringify(first))
|
|
206
|
+
const domains = first.overrideTargets.map((t) => t.domain)
|
|
207
|
+
expect(domains).toEqual([...domains].sort((a, b) => a.localeCompare(b)))
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe('module override targets — deterministic diagnostics (never a guessed key)', () => {
|
|
212
|
+
it('emits missing-source and no target when the route source cannot be proven', () => {
|
|
213
|
+
const facts = baseFacts({
|
|
214
|
+
apiRoutes: [{ path: '/demo/items', methods: ['GET'], auth: {}, sourcePath: null }],
|
|
215
|
+
})
|
|
216
|
+
const { overrideTargets, overrideTargetDiagnostics } = collectModuleOverrideTargets(facts)
|
|
217
|
+
expect(overrideTargets.some((t) => t.domain === 'routes' && t.path[1] === 'api')).toBe(false)
|
|
218
|
+
const diag = overrideTargetDiagnostics.find((d) => d.domain === 'routes' && d.code === 'missing-source')
|
|
219
|
+
expect(diag).toBeDefined()
|
|
220
|
+
expect(diag?.moduleId).toBe(MODULE_ID)
|
|
221
|
+
expect(diag?.candidatePath).toEqual(['routes', 'api', 'GET /api/demo/items'])
|
|
222
|
+
expect(diag?.source).toBeUndefined()
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('never guesses a mode: the framework catalog describes every adapter host', () => {
|
|
226
|
+
const { overrideTargets, overrideTargetDiagnostics } = collectModuleOverrideTargets(baseFacts())
|
|
227
|
+
expect(overrideTargetDiagnostics.filter((diagnostic) => diagnostic.code === 'unknown-framework-domain')).toEqual([])
|
|
228
|
+
expect(overrideTargets.length).toBeGreaterThan(0)
|
|
229
|
+
for (const target of overrideTargets) {
|
|
230
|
+
expect(target.modes).toHaveLength(1)
|
|
231
|
+
expect(['disable-replace', 'replace', 'additive']).toContain(target.modes[0])
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('routes every widget contribution by its own kind/payload discriminant', () => {
|
|
236
|
+
const { overrideTargets } = collectModuleOverrideTargets(baseFacts())
|
|
237
|
+
const widgets = overrideTargets
|
|
238
|
+
.filter((target) => target.domain === 'widgets')
|
|
239
|
+
.map((target) => `${target.path.slice(0, 2).join('/')} ${target.key}`)
|
|
240
|
+
.sort()
|
|
241
|
+
expect(widgets).toEqual([
|
|
242
|
+
'widgets/components section:demo.login',
|
|
243
|
+
'widgets/dashboard demo.card',
|
|
244
|
+
'widgets/injection demo.table-widget',
|
|
245
|
+
'widgets/injection demo.widget',
|
|
246
|
+
])
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('emits missing-source when an acl feature has no indexed provenance', () => {
|
|
250
|
+
const facts = baseFacts({ factSources: [] })
|
|
251
|
+
const { overrideTargets, overrideTargetDiagnostics } = collectModuleOverrideTargets(facts)
|
|
252
|
+
expect(overrideTargets.some((t) => t.domain === 'acl')).toBe(false)
|
|
253
|
+
expect(overrideTargetDiagnostics.some((d) => d.domain === 'acl' && d.code === 'missing-source')).toBe(true)
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
describe('module override targets — keyed AI, notification handlers, setup profiles', () => {
|
|
258
|
+
it('emits exact ai.agents/ai.tools targets for keyed overrides and keeps extensions additive', () => {
|
|
259
|
+
const { overrideTargets } = collectModuleOverrideTargets(baseFacts())
|
|
260
|
+
const aiPaths = overrideTargets
|
|
261
|
+
.filter((target) => target.domain === 'ai')
|
|
262
|
+
.map((target) => target.path.join('.'))
|
|
263
|
+
.sort()
|
|
264
|
+
expect(aiPaths).toEqual([
|
|
265
|
+
'ai.agents.demo.agent',
|
|
266
|
+
'ai.agents.other.assistant',
|
|
267
|
+
'ai.extensions',
|
|
268
|
+
'ai.tools.demo.tool',
|
|
269
|
+
'ai.tools.other.tool',
|
|
270
|
+
])
|
|
271
|
+
|
|
272
|
+
const agentOverride = overrideTargets.find(
|
|
273
|
+
(target) => target.domain === 'ai' && target.key === 'other.assistant',
|
|
274
|
+
)
|
|
275
|
+
expect(agentOverride?.factRef).toEqual({
|
|
276
|
+
factSection: 'ownedContracts.ai-extension',
|
|
277
|
+
factKey: 'agent-override:other.assistant',
|
|
278
|
+
})
|
|
279
|
+
expect(agentOverride?.source.sourcePath).toBe(`${SOURCE_ROOT}/ai-agents.ts`)
|
|
280
|
+
|
|
281
|
+
const extensions = overrideTargets.find(
|
|
282
|
+
(target) => target.domain === 'ai' && target.path.join('.') === 'ai.extensions',
|
|
283
|
+
)
|
|
284
|
+
expect(extensions?.key).toBeUndefined()
|
|
285
|
+
expect(extensions?.factRef.factKey).toBe('agent-extension:other.agent')
|
|
286
|
+
expect(JSON.stringify(overrideTargets)).not.toContain('replacement')
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('emits notifications.handlers targets keyed by the declared handler id', () => {
|
|
290
|
+
const { overrideTargets, overrideTargetDiagnostics } = collectModuleOverrideTargets(baseFacts())
|
|
291
|
+
const handler = overrideTargets.find(
|
|
292
|
+
(target) => target.domain === 'notifications' && target.path[1] === 'handlers',
|
|
293
|
+
)
|
|
294
|
+
expect(handler?.path).toEqual(['notifications', 'handlers', 'demo.alert-handler'])
|
|
295
|
+
expect(handler?.key).toBe('demo.alert-handler')
|
|
296
|
+
expect(handler?.factRef).toEqual({
|
|
297
|
+
factSection: 'extensionSurfaces.contributions',
|
|
298
|
+
factKey: 'demo.alert-handler',
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
// A handler that declares no id cannot be addressed by runtime.
|
|
302
|
+
expect(overrideTargets.some((target) => target.path[2] === 'demo.notification-handler.1')).toBe(false)
|
|
303
|
+
expect(overrideTargetDiagnostics).toEqual(expect.arrayContaining([
|
|
304
|
+
expect.objectContaining({
|
|
305
|
+
code: 'unsupported-dynamic-key',
|
|
306
|
+
domain: 'notifications',
|
|
307
|
+
candidatePath: ['notifications', 'handlers'],
|
|
308
|
+
}),
|
|
309
|
+
]))
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it('emits setup.defaultCustomerRoleFeatures when customer-role profiles are declared', () => {
|
|
313
|
+
const withoutProfiles = collectModuleOverrideTargets(baseFacts()).overrideTargets
|
|
314
|
+
expect(withoutProfiles.some((target) => target.path.join('.') === 'setup.defaultCustomerRoleFeatures')).toBe(false)
|
|
315
|
+
|
|
316
|
+
const facts = baseFacts()
|
|
317
|
+
const setupFact = (facts.ownedContracts?.setup ?? [])[0]
|
|
318
|
+
const withProfiles = collectModuleOverrideTargets(baseFacts({
|
|
319
|
+
ownedContracts: {
|
|
320
|
+
...facts.ownedContracts,
|
|
321
|
+
setup: [{ ...setupFact, metadata: { ...setupFact.metadata, customerRoles: ['portal-admin'] } }],
|
|
322
|
+
},
|
|
323
|
+
})).overrideTargets
|
|
324
|
+
const target = withProfiles.find((entry) => entry.path.join('.') === 'setup.defaultCustomerRoleFeatures')
|
|
325
|
+
expect(target).toBeDefined()
|
|
326
|
+
expect(target?.factRef).toEqual({ factSection: 'ownedContracts.setup', factKey: 'demo:setup' })
|
|
327
|
+
})
|
|
328
|
+
})
|
|
@@ -702,6 +702,24 @@ export function GET() {
|
|
|
702
702
|
expect(output).toContain('features:')
|
|
703
703
|
})
|
|
704
704
|
|
|
705
|
+
it('normalizes singular app-owned integration exports without requiring plural properties', async () => {
|
|
706
|
+
touchFile(
|
|
707
|
+
path.join(tmpDir, 'app', 'src', 'modules', 'mailbox', 'integration.ts'),
|
|
708
|
+
"export const integration = { id: 'mailbox' }\nexport default integration\n",
|
|
709
|
+
)
|
|
710
|
+
const resolver = createMockResolver(tmpDir, [{ id: 'mailbox', from: '@app' }])
|
|
711
|
+
const result = await generateModuleRegistry({ resolver, quiet: true })
|
|
712
|
+
|
|
713
|
+
expect(result.errors).toEqual([])
|
|
714
|
+
for (const file of ['modules.generated.ts', 'modules.runtime.generated.ts']) {
|
|
715
|
+
const output = readGenerated(tmpDir, file)!
|
|
716
|
+
expect(output).toContain('as unknown as { integrations?:')
|
|
717
|
+
expect(output).toContain('.integration ? [')
|
|
718
|
+
expect(output).not.toMatch(/INTEGRATION_[A-Za-z0-9_]+\.integrations/)
|
|
719
|
+
expect(output).not.toMatch(/INTEGRATION_[A-Za-z0-9_]+\.bundles/)
|
|
720
|
+
}
|
|
721
|
+
})
|
|
722
|
+
|
|
705
723
|
it('escapes generated module specifiers for lazy imports', async () => {
|
|
706
724
|
touchFile(
|
|
707
725
|
path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'safe_mod', 'subscribers', `o'hare.ts`),
|
|
@@ -183,9 +183,52 @@ function resolveScannedWidgetPath(roots: ModuleRoots, fromApp: boolean, relPath:
|
|
|
183
183
|
return path.join(base, 'widgets', 'injection', ...relPath.split('/'))
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* A widget's `metadata.id` and the injection-table entry that mounts it are two hand-typed string
|
|
188
|
+
* literals in two different files, and nothing links them. A typo in either fails silently: the spot
|
|
189
|
+
* resolves to no widget and nothing is logged, so it only surfaces as "why isn't my widget
|
|
190
|
+
* rendering" during manual QA.
|
|
191
|
+
*
|
|
192
|
+
* Reports table entries whose `widgetId` no scanned widget declares. Diagnostics only — generated
|
|
193
|
+
* output is unchanged and the build is not failed, since a table entry may legitimately point at a
|
|
194
|
+
* widget supplied by a module that is not enabled in this app.
|
|
195
|
+
*
|
|
196
|
+
* Deliberately one-directional. The mirror check ("a widget nothing references is never mounted")
|
|
197
|
+
* cannot be done reliably yet: `extractInjectionTableWidgetIds` only reads statically-analyzable
|
|
198
|
+
* object literals, so a table assembled by composition or computation contributes **no** visible
|
|
199
|
+
* ids — `example` exports `cond ? { ...a, ...b } : a` and `translations` exports
|
|
200
|
+
* `buildInjectionTable()`, and both would have every one of their widgets reported as unreferenced.
|
|
201
|
+
* Under-reporting is harmless in the direction implemented here (a reference the extractor never saw
|
|
202
|
+
* cannot produce a warning) but fatal in the other. Teaching the extractor to resolve spreads,
|
|
203
|
+
* conditionals and call expressions is the prerequisite for adding it.
|
|
204
|
+
*/
|
|
205
|
+
function reportUnresolvableWidgetReferences(
|
|
206
|
+
widgetEntries: Map<string, InjectionWidgetEntry>,
|
|
207
|
+
tableWidgetRefs: Map<string, Set<string>>,
|
|
208
|
+
): void {
|
|
209
|
+
const declaredIds = new Set<string>()
|
|
210
|
+
for (const entry of widgetEntries.values()) {
|
|
211
|
+
if (entry.widgetId) declaredIds.add(entry.widgetId)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const [widgetId, modules] of Array.from(tableWidgetRefs.entries()).sort(([a], [b]) => a.localeCompare(b))) {
|
|
215
|
+
if (declaredIds.has(widgetId)) continue
|
|
216
|
+
const owners = Array.from(modules).sort((left, right) => left.localeCompare(right)).join(', ')
|
|
217
|
+
console.warn(
|
|
218
|
+
`[generate] ⚠ Injection table in "${owners}" references widget id "${widgetId}", but no scanned ` +
|
|
219
|
+
'widget declares it — that spot will render nothing. Check the id against the widget\'s ' +
|
|
220
|
+
'metadata.id, and that the widget lives in a file the scanner reads ' +
|
|
221
|
+
'(widgets/injection/**/widget.ts|tsx — note that widget.client.tsx alone is not scanned).',
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
186
226
|
export function createInjectionWidgetsExtension(): GeneratorExtension {
|
|
187
227
|
const widgetEntries = new Map<string, InjectionWidgetEntry>()
|
|
188
228
|
const tableEntries: InjectionTableEntry[] = []
|
|
229
|
+
// widgetId -> module ids whose injection table references it. Collected across every module so a
|
|
230
|
+
// table may legitimately reference a widget another module owns.
|
|
231
|
+
const tableWidgetRefs = new Map<string, Set<string>>()
|
|
189
232
|
|
|
190
233
|
return {
|
|
191
234
|
id: 'registry.injection-widgets',
|
|
@@ -217,6 +260,12 @@ export function createInjectionWidgetsExtension(): GeneratorExtension {
|
|
|
217
260
|
moduleWidgetEntries[0].widgetId = tableWidgetIds[0]
|
|
218
261
|
}
|
|
219
262
|
|
|
263
|
+
for (const widgetId of tableWidgetIds) {
|
|
264
|
+
const modules = tableWidgetRefs.get(widgetId) ?? new Set<string>()
|
|
265
|
+
modules.add(ctx.moduleId)
|
|
266
|
+
tableWidgetRefs.set(widgetId, modules)
|
|
267
|
+
}
|
|
268
|
+
|
|
220
269
|
for (const entry of moduleWidgetEntries) {
|
|
221
270
|
const existing = widgetEntries.get(entry.key)
|
|
222
271
|
if (!existing || (existing.source !== 'app' && entry.source === 'app')) {
|
|
@@ -233,6 +282,8 @@ export function createInjectionWidgetsExtension(): GeneratorExtension {
|
|
|
233
282
|
}
|
|
234
283
|
},
|
|
235
284
|
generateOutput() {
|
|
285
|
+
reportUnresolvableWidgetReferences(widgetEntries, tableWidgetRefs)
|
|
286
|
+
|
|
236
287
|
const widgetDecls = Array.from(widgetEntries.entries())
|
|
237
288
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
238
289
|
.map(([, entry]) =>
|