@open-mercato/cli 0.6.7 → 0.6.8-develop.6875.1.871a4afc94
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 +24 -7
- package/dist/agentic/claude-code/hooks/entity-migration-check.ts +32 -88
- package/dist/agentic/codex/enforcement-rules.md +2 -16
- package/dist/agentic/cursor/hooks/entity-migration-check.mjs +19 -50
- package/dist/agentic/cursor/rules/entity-guard.mdc +9 -21
- package/dist/agentic/cursor/rules/generated-guard.mdc +3 -5
- package/dist/agentic/cursor/rules/open-mercato.mdc +3 -29
- package/dist/agentic/guides/ai-workflows.md +76 -0
- package/dist/agentic/guides/architecture.md +107 -0
- package/dist/agentic/guides/backend-ui.md +80 -0
- package/dist/agentic/guides/contracts.md +92 -0
- package/dist/agentic/guides/extensions.md +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 +115537 -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 +475 -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 +299 -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 +146 -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 +164 -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 +678 -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 +369 -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 +441 -46
- package/dist/agentic/guides/modules/workflows.md +302 -43
- package/dist/agentic/guides/spec-delivery.md +22 -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 +124 -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 +13680 -0
- package/dist/agentic/shared/ai/harness/cases.schema.json +133 -0
- package/dist/agentic/shared/ai/harness/fixtures/index.json +49 -0
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +162 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +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/release-matrix.json +158 -0
- package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
- package/dist/agentic/shared/ai/harness/result.schema.json +76 -0
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +12 -0
- package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
- package/dist/agentic/shared/ai/harness/validators.json +78 -0
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +2121 -0
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +1096 -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 +20 -241
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +16 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +53 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +13 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +11 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +30 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +12 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +13 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +14 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +28 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +22 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +19 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +16 -601
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +19 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +13 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +14 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +10 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +14 -310
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +17 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +29 -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/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 +24 -708
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +16 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +14 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +15 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +12 -0
- package/dist/agentic/shared/ai/skills/om-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 +19 -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 +12 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +83 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +21 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +11 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +22 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +13 -0
- package/dist/agentic/shared/ai/skills/om-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 +19 -843
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +18 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +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 +41 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +11 -0
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +15 -63
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +15 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +16 -467
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +16 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +14 -0
- package/dist/agentic/shared/ai/skills/tiers.json +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 +187 -0
- package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +2958 -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/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 +1840 -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 +41 -7
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +1387 -45
- 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 -0
- 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 +404 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +88 -2
- 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.owned-contracts.test.ts +561 -0
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +119 -0
- package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
- package/src/lib/generators/__tests__/module-override-targets.test.ts +304 -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 +2365 -0
- package/src/lib/generators/module-fact-sources.ts +108 -0
- package/src/lib/generators/module-facts-discovery.ts +58 -7
- package/src/lib/generators/module-facts.ts +1851 -46
- 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 -0
- 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
|
@@ -224,6 +224,8 @@ type SerializableWorkerMetadata = {
|
|
|
224
224
|
id?: string
|
|
225
225
|
queue?: string
|
|
226
226
|
concurrency?: number
|
|
227
|
+
lockDuration?: number
|
|
228
|
+
maxStalledCount?: number
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
type PageMetadataManifestLoadResult = {
|
|
@@ -1155,7 +1157,7 @@ function collectStringArrayElements(expr: ts.Expression, stringConstants: Map<st
|
|
|
1155
1157
|
return values
|
|
1156
1158
|
}
|
|
1157
1159
|
|
|
1158
|
-
function extractCommandIdsFromSource(sourcePath: string): string[] {
|
|
1160
|
+
export function extractCommandIdsFromSource(sourcePath: string): string[] {
|
|
1159
1161
|
const sourceText = fs.readFileSync(sourcePath, 'utf8')
|
|
1160
1162
|
const sourceFile = ts.createSourceFile(sourcePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
|
|
1161
1163
|
const ids = new Set<string>()
|
|
@@ -1553,6 +1555,8 @@ function normalizeWorkerMetadata(raw: unknown): SerializableWorkerMetadata | nul
|
|
|
1553
1555
|
if (typeof source.id === 'string' && source.id.length > 0) normalized.id = source.id
|
|
1554
1556
|
if (typeof source.queue === 'string' && source.queue.length > 0) normalized.queue = source.queue
|
|
1555
1557
|
if (typeof source.concurrency === 'number') normalized.concurrency = source.concurrency
|
|
1558
|
+
if (typeof source.lockDuration === 'number') normalized.lockDuration = source.lockDuration
|
|
1559
|
+
if (typeof source.maxStalledCount === 'number') normalized.maxStalledCount = source.maxStalledCount
|
|
1556
1560
|
return Object.keys(normalized).length > 0 ? normalized : null
|
|
1557
1561
|
}
|
|
1558
1562
|
|
|
@@ -2055,7 +2059,7 @@ function processWorkers(discovered: DiscoveredWorker[]): string[] {
|
|
|
2055
2059
|
for (const { id, importPath, metadata } of discovered) {
|
|
2056
2060
|
const workerId = metadata.id ?? id
|
|
2057
2061
|
workers.push(
|
|
2058
|
-
`{ id: ${toLiteral(workerId)}, queue: ${toLiteral(metadata.queue)}, concurrency: ${toLiteral(metadata.concurrency ?? 1)}, handler: createLazyModuleWorker(() => ${buildDynamicImportExpression(importPath)}, ${toLiteral(workerId)}) }`
|
|
2062
|
+
`{ id: ${toLiteral(workerId)}, queue: ${toLiteral(metadata.queue)}, concurrency: ${toLiteral(metadata.concurrency ?? 1)}${metadata.lockDuration === undefined ? '' : `, lockDuration: ${toLiteral(metadata.lockDuration)}`}${metadata.maxStalledCount === undefined ? '' : `, maxStalledCount: ${toLiteral(metadata.maxStalledCount)}`}, handler: createLazyModuleWorker(() => ${buildDynamicImportExpression(importPath)}, ${toLiteral(workerId)}) }`
|
|
2059
2063
|
)
|
|
2060
2064
|
}
|
|
2061
2065
|
return workers
|
|
@@ -2730,6 +2734,8 @@ function processWorkersAst(discovered: DiscoveredWorker[]): WriterFunction[] {
|
|
|
2730
2734
|
{ name: 'id', value: workerId },
|
|
2731
2735
|
{ name: 'queue', value: metadata.queue },
|
|
2732
2736
|
{ name: 'concurrency', value: metadata.concurrency ?? 1 },
|
|
2737
|
+
...(metadata.lockDuration === undefined ? [] : [{ name: 'lockDuration', value: metadata.lockDuration }]),
|
|
2738
|
+
...(metadata.maxStalledCount === undefined ? [] : [{ name: 'maxStalledCount', value: metadata.maxStalledCount }]),
|
|
2733
2739
|
{
|
|
2734
2740
|
name: 'handler',
|
|
2735
2741
|
value: callExpression(identifier('createLazyModuleWorker'), [
|
|
@@ -3059,6 +3065,10 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
|
|
|
3059
3065
|
if (fields) fieldsImportName = fields.importName
|
|
3060
3066
|
}
|
|
3061
3067
|
|
|
3068
|
+
const integrationExports = integrationImportName
|
|
3069
|
+
? `(${integrationImportName} as unknown as { integrations?: import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]; integration?: import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition; bundles?: import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]; bundle?: import('@open-mercato/shared/modules/integrations/types').IntegrationBundle })`
|
|
3070
|
+
: null
|
|
3071
|
+
|
|
3062
3072
|
// 13. Pages: backend
|
|
3063
3073
|
{
|
|
3064
3074
|
const beApp = path.join(roots.appBase, 'backend')
|
|
@@ -3167,8 +3177,8 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
|
|
|
3167
3177
|
${dashboardWidgets.length ? `dashboardWidgets: [${dashboardWidgets.join(', ')}],` : ''}
|
|
3168
3178
|
${setupImportName ? `setup: (${setupImportName}.default ?? ${setupImportName}.setup) || undefined,` : ''}
|
|
3169
3179
|
${encryptionImportName ? `defaultEncryptionMaps: ((${encryptionImportName}.default ?? ${encryptionImportName}.defaultEncryptionMaps) as import('@open-mercato/shared/modules/encryption').ModuleEncryptionMap[]) || [],` : ''}
|
|
3170
|
-
${
|
|
3171
|
-
${
|
|
3180
|
+
${integrationExports ? `integrations: ((${integrationExports}.integrations ?? (${integrationExports}.integration ? [${integrationExports}.integration] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]),` : ''}
|
|
3181
|
+
${integrationExports ? `bundles: ((${integrationExports}.bundles ?? (${integrationExports}.bundle ? [${integrationExports}.bundle] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]),` : ''}
|
|
3172
3182
|
}`)
|
|
3173
3183
|
runtimeModuleDecls.push(`{
|
|
3174
3184
|
id: ${toLiteral(modId)},
|
|
@@ -3185,8 +3195,8 @@ async function generateModuleRegistryFromDiscovery(options: ModuleRegistryRender
|
|
|
3185
3195
|
${customEntitiesImportName ? `customEntities: ((${customEntitiesImportName}.default ?? ${customEntitiesImportName}.entities) as any) || [],` : ''}
|
|
3186
3196
|
${setupImportName ? `setup: (${setupImportName}.default ?? ${setupImportName}.setup) || undefined,` : ''}
|
|
3187
3197
|
${encryptionImportName ? `defaultEncryptionMaps: ((${encryptionImportName}.default ?? ${encryptionImportName}.defaultEncryptionMaps) as import('@open-mercato/shared/modules/encryption').ModuleEncryptionMap[]) || [],` : ''}
|
|
3188
|
-
${
|
|
3189
|
-
${
|
|
3198
|
+
${integrationExports ? `integrations: ((${integrationExports}.integrations ?? (${integrationExports}.integration ? [${integrationExports}.integration] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]),` : ''}
|
|
3199
|
+
${integrationExports ? `bundles: ((${integrationExports}.bundles ?? (${integrationExports}.bundle ? [${integrationExports}.bundle] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]),` : ''}
|
|
3190
3200
|
}`)
|
|
3191
3201
|
}
|
|
3192
3202
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bootstrap-free `.env` loader for the CLI binary. `bin.ts` must load the app's
|
|
6
|
+
* `.env` BEFORE `initTelemetry()` so `TELEMETRY_*` set only in `.env` resolves a
|
|
7
|
+
* real backend for worker/scheduler processes — `run()`'s `ensureEnvLoaded()`
|
|
8
|
+
* happens after telemetry init, which is too late. Deliberately read-only (no
|
|
9
|
+
* `.env.example` copy — that UX stays in `ensureEnvLoaded`) and pulls in only the
|
|
10
|
+
* resolver + dotenv, so nothing here loads `pg` and the OTEL instrumentation
|
|
11
|
+
* load-order guarantee is preserved.
|
|
12
|
+
*/
|
|
13
|
+
export async function loadAppEnv(options: { cwd?: string } = {}): Promise<void> {
|
|
14
|
+
try {
|
|
15
|
+
const { createResolver } = await import('./resolver.js')
|
|
16
|
+
const appDir = createResolver(options.cwd).getAppDir()
|
|
17
|
+
const envPath = path.join(appDir, '.env')
|
|
18
|
+
if (fs.existsSync(envPath)) {
|
|
19
|
+
const dotenv = await import('dotenv')
|
|
20
|
+
dotenv.config({ path: envPath, quiet: true })
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
// Resolver can fail outside an app dir — fall through to plain cwd loading.
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const dotenv = await import('dotenv')
|
|
28
|
+
dotenv.config({ quiet: true })
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
package/src/lib/resolver.ts
CHANGED
|
@@ -29,6 +29,7 @@ export type ModuleEntry = {
|
|
|
29
29
|
|
|
30
30
|
export type PackageInfo = {
|
|
31
31
|
name: string
|
|
32
|
+
version?: string | null
|
|
32
33
|
path: string
|
|
33
34
|
modulesPath: string
|
|
34
35
|
}
|
|
@@ -356,6 +357,7 @@ function discoverPackagesInMonorepo(rootDir: string): PackageInfo[] {
|
|
|
356
357
|
if (fs.existsSync(modulesPath)) {
|
|
357
358
|
packages.push({
|
|
358
359
|
name: pkgJson.name || `@open-mercato/${entry.name}`,
|
|
360
|
+
version: typeof pkgJson.version === 'string' ? pkgJson.version : null,
|
|
359
361
|
path: pkgPath,
|
|
360
362
|
modulesPath,
|
|
361
363
|
})
|
|
@@ -391,6 +393,7 @@ function discoverPackagesInNodeModules(rootDir: string): PackageInfo[] {
|
|
|
391
393
|
if (fs.existsSync(modulesPath)) {
|
|
392
394
|
packages.push({
|
|
393
395
|
name: pkgJson.name || `@open-mercato/${entry.name}`,
|
|
396
|
+
version: typeof pkgJson.version === 'string' ? pkgJson.version : null,
|
|
394
397
|
path: pkgPath,
|
|
395
398
|
modulesPath,
|
|
396
399
|
})
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
2
|
+
import { dirname, join, resolve } from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `mercato telemetry init` — wire `@open-mercato/telemetry` into an app that was
|
|
6
|
+
* scaffolded before telemetry existed. Deterministic, idempotent, and safe to
|
|
7
|
+
* re-run: each step detects whether it is already applied and skips.
|
|
8
|
+
*
|
|
9
|
+
* The web-tier wiring lives in app-owned source files (a dependency bump cannot
|
|
10
|
+
* touch them), so this patches them in place using the same idioms the rest of
|
|
11
|
+
* the CLI uses: JSON edits, detect-before-append for `.env`, a ts-morph edit for
|
|
12
|
+
* `next.config.ts`, and anchored insertion for `instrumentation.ts` + the API
|
|
13
|
+
* dispatcher. When a file's shape is not recognized (a customized dispatcher, an
|
|
14
|
+
* unusual next.config), the step degrades to printing the exact snippet and
|
|
15
|
+
* flags it as a manual step rather than editing code it does not understand.
|
|
16
|
+
*
|
|
17
|
+
* Worker/scheduler telemetry is not handled here — it ships transitively via
|
|
18
|
+
* `@open-mercato/cli`'s telemetry dependency once the app updates its packages.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const BULLMQ_OTEL_VERSION = '^1.3.1'
|
|
22
|
+
|
|
23
|
+
type StepStatus = 'created' | 'patched' | 'skipped' | 'manual'
|
|
24
|
+
|
|
25
|
+
type StepResult = {
|
|
26
|
+
file: string
|
|
27
|
+
status: StepStatus
|
|
28
|
+
detail: string
|
|
29
|
+
/** Snippet to show when the step needs manual work. */
|
|
30
|
+
manualSnippet?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type TelemetryInitOptions = {
|
|
34
|
+
dryRun: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ENV_BLOCK = `
|
|
38
|
+
# --- Telemetry & Observability (vendor-neutral OTLP: traces, logs, metrics, errors) ---
|
|
39
|
+
# Off by default: leave TELEMETRY_BACKEND unset (or =noop) for a hard no-op
|
|
40
|
+
# (the OpenTelemetry SDK is never loaded). Set to 'console' for local span/metric
|
|
41
|
+
# output, or an OTLP backend to export: 'signoz' | 'newrelic' | 'otlp' (generic).
|
|
42
|
+
# All three OTLP names use the same exporter — they differ only by the endpoint +
|
|
43
|
+
# headers below. Any OTLP backend is a one-line swap; no code change.
|
|
44
|
+
# TELEMETRY_BACKEND=otlp
|
|
45
|
+
# Logs use the shared logger facade and its OM_LOG_LEVEL / OM_LOG_PRETTY /
|
|
46
|
+
# OM_LOG_DESTINATION controls. Telemetry adds remote export; it does not create
|
|
47
|
+
# a second local output path.
|
|
48
|
+
# TELEMETRY_SAMPLING_RATIO=1.0 # 0.0–1.0 (default 1.0 dev / 0.1 prod)
|
|
49
|
+
# TELEMETRY_TRUST_INBOUND_TRACE=false # default false: root-per-request (ignore an
|
|
50
|
+
# # inbound traceparent from a proxy/LB). Set true
|
|
51
|
+
# # only behind a trusted upstream whose trace continues.
|
|
52
|
+
# # This also enables richer bullmq-otel spans;
|
|
53
|
+
# # otherwise async queues use metadata._trace.
|
|
54
|
+
#
|
|
55
|
+
# OTLP endpoint + auth — pick ONE vendor (use your region's host):
|
|
56
|
+
# SigNoz Cloud:
|
|
57
|
+
# OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.<region>.signoz.cloud:443
|
|
58
|
+
# OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key=<your-ingestion-key>
|
|
59
|
+
# New Relic (OTLP-native; EU host shown, US is https://otlp.nr-data.net):
|
|
60
|
+
# OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.eu01.nr-data.net
|
|
61
|
+
# OTEL_EXPORTER_OTLP_HEADERS=api-key=<your-ingest-license-key>
|
|
62
|
+
#
|
|
63
|
+
# Resource identity: keep one stable service name across environments and
|
|
64
|
+
# separate envs via the deployment.environment resource attribute.
|
|
65
|
+
# OTEL_SERVICE_NAME=open-mercato
|
|
66
|
+
# OTEL_RESOURCE_ATTRIBUTES=deployment.environment=local
|
|
67
|
+
`
|
|
68
|
+
|
|
69
|
+
const INSTRUMENTATION_TS = `import { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
70
|
+
|
|
71
|
+
export async function register(): Promise<void> {
|
|
72
|
+
// Initialize telemetry (no-op unless TELEMETRY_BACKEND is set). OTEL's NodeSDK
|
|
73
|
+
// is Node-only and incompatible with the edge runtime, so the telemetry
|
|
74
|
+
// bootstrap — which can pull in the SDK — is imported only on the Node.js
|
|
75
|
+
// runtime. The helper owns init + graceful degrade + shutdown flush.
|
|
76
|
+
if (
|
|
77
|
+
process.env.NEXT_RUNTIME === 'nodejs'
|
|
78
|
+
&& isTelemetryBackendEnabled()
|
|
79
|
+
) {
|
|
80
|
+
const { registerTelemetryForNextjs } = await import('@open-mercato/telemetry/nextjs')
|
|
81
|
+
await registerTelemetryForNextjs()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
`
|
|
85
|
+
|
|
86
|
+
const DISPATCHER_SNIPPET = ` // add near the other imports:
|
|
87
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
88
|
+
|
|
89
|
+
// in the dispatcher's success path, just before returning the response:
|
|
90
|
+
getTelemetryRuntime()?.recordHttpDuration(method, match.route.path, finalResponse.status, startedAt)
|
|
91
|
+
|
|
92
|
+
// in the dispatcher's catch (error) block, before re-throwing:
|
|
93
|
+
getTelemetryRuntime()?.reportError(error, {
|
|
94
|
+
attributes: { 'http.request.method': method, 'http.route': match.route.path, 'http.response.status_code': 500 },
|
|
95
|
+
})
|
|
96
|
+
getTelemetryRuntime()?.recordHttpDuration(method, match.route.path, 500, startedAt)`
|
|
97
|
+
|
|
98
|
+
function parseArgs(args: string[]): TelemetryInitOptions {
|
|
99
|
+
return {
|
|
100
|
+
dryRun: args.includes('--dry-run') || args.includes('-n'),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Read the version an app already pins its `@open-mercato/*` deps to (lockstep). */
|
|
105
|
+
function resolveOpenMercatoVersion(pkg: Record<string, unknown>): string {
|
|
106
|
+
for (const field of ['dependencies', 'devDependencies'] as const) {
|
|
107
|
+
const deps = pkg[field]
|
|
108
|
+
if (deps && typeof deps === 'object') {
|
|
109
|
+
for (const [name, version] of Object.entries(deps as Record<string, string>)) {
|
|
110
|
+
if (name.startsWith('@open-mercato/') && typeof version === 'string') return version
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return '*'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function patchPackageJson(appDir: string, options: TelemetryInitOptions): StepResult {
|
|
118
|
+
const file = 'package.json'
|
|
119
|
+
const path = join(appDir, file)
|
|
120
|
+
const raw = readFileSync(path, 'utf8')
|
|
121
|
+
const pkg = JSON.parse(raw) as {
|
|
122
|
+
dependencies?: Record<string, string>
|
|
123
|
+
optionalDependencies?: Record<string, string>
|
|
124
|
+
}
|
|
125
|
+
const version = resolveOpenMercatoVersion(pkg)
|
|
126
|
+
const hasTelemetry = Boolean(pkg.dependencies?.['@open-mercato/telemetry'])
|
|
127
|
+
const hasBullmqOtel = Boolean(pkg.optionalDependencies?.['bullmq-otel'])
|
|
128
|
+
if (hasTelemetry && hasBullmqOtel) {
|
|
129
|
+
return { file, status: 'skipped', detail: '@open-mercato/telemetry already installed' }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const deps = { ...(pkg.dependencies ?? {}) }
|
|
133
|
+
if (!hasTelemetry) deps['@open-mercato/telemetry'] = version
|
|
134
|
+
const optDeps = { ...(pkg.optionalDependencies ?? {}) }
|
|
135
|
+
if (!hasBullmqOtel) optDeps['bullmq-otel'] = BULLMQ_OTEL_VERSION
|
|
136
|
+
|
|
137
|
+
const next = {
|
|
138
|
+
...pkg,
|
|
139
|
+
dependencies: sortRecord(deps),
|
|
140
|
+
optionalDependencies: sortRecord(optDeps),
|
|
141
|
+
}
|
|
142
|
+
if (!options.dryRun) writeFileSync(path, `${JSON.stringify(next, null, 2)}\n`)
|
|
143
|
+
return {
|
|
144
|
+
file,
|
|
145
|
+
status: 'patched',
|
|
146
|
+
detail: `+@open-mercato/telemetry@${version}${hasBullmqOtel ? '' : ` +bullmq-otel@${BULLMQ_OTEL_VERSION} (optional)`} — run \`yarn install\``,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function sortRecord(record: Record<string, string>): Record<string, string> {
|
|
151
|
+
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function patchEnvFile(appDir: string, relativePath: string, options: TelemetryInitOptions): StepResult | null {
|
|
155
|
+
const path = join(appDir, relativePath)
|
|
156
|
+
if (!existsSync(path)) return null
|
|
157
|
+
const content = readFileSync(path, 'utf8')
|
|
158
|
+
if (content.includes('TELEMETRY_BACKEND')) {
|
|
159
|
+
return { file: relativePath, status: 'skipped', detail: 'TELEMETRY_* block already present' }
|
|
160
|
+
}
|
|
161
|
+
const next = `${content.replace(/\s*$/, '')}\n${ENV_BLOCK}`
|
|
162
|
+
if (!options.dryRun) writeFileSync(path, next)
|
|
163
|
+
return { file: relativePath, status: 'patched', detail: 'appended commented TELEMETRY_* / OTEL_* block' }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function patchInstrumentation(appDir: string, options: TelemetryInitOptions): StepResult {
|
|
167
|
+
const file = 'src/instrumentation.ts'
|
|
168
|
+
const path = join(appDir, file)
|
|
169
|
+
if (!existsSync(path)) {
|
|
170
|
+
if (!options.dryRun) {
|
|
171
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
172
|
+
writeFileSync(path, INSTRUMENTATION_TS)
|
|
173
|
+
}
|
|
174
|
+
return { file, status: 'created', detail: 'Node-runtime-guarded registerTelemetryForNextjs() bootstrap' }
|
|
175
|
+
}
|
|
176
|
+
const content = readFileSync(path, 'utf8')
|
|
177
|
+
if (content.includes('registerTelemetryForNextjs')) {
|
|
178
|
+
if (content.includes('isTelemetryBackendEnabled')) {
|
|
179
|
+
return { file, status: 'skipped', detail: 'telemetry already wired' }
|
|
180
|
+
}
|
|
181
|
+
const migrated =
|
|
182
|
+
`import { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'\n` +
|
|
183
|
+
content.replace(
|
|
184
|
+
`if (process.env.NEXT_RUNTIME === 'nodejs') {`,
|
|
185
|
+
`if (process.env.NEXT_RUNTIME === 'nodejs' && isTelemetryBackendEnabled()) {`,
|
|
186
|
+
)
|
|
187
|
+
if (!options.dryRun) writeFileSync(path, migrated)
|
|
188
|
+
return { file, status: 'patched', detail: 'added the explicit backend import guard' }
|
|
189
|
+
}
|
|
190
|
+
const anchor = content.match(/export\s+async\s+function\s+register\s*\([^)]*\)\s*:\s*Promise<void>\s*\{/)
|
|
191
|
+
if (!anchor || anchor.index === undefined) {
|
|
192
|
+
return {
|
|
193
|
+
file,
|
|
194
|
+
status: 'manual',
|
|
195
|
+
detail: 'register() not found — add the telemetry bootstrap manually',
|
|
196
|
+
manualSnippet: INSTRUMENTATION_TS,
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const insertAt = anchor.index + anchor[0].length
|
|
200
|
+
const block =
|
|
201
|
+
`\n // Initialize telemetry (no-op unless TELEMETRY_BACKEND is set); Node-only.` +
|
|
202
|
+
`\n if (process.env.NEXT_RUNTIME === 'nodejs' && isTelemetryBackendEnabled()) {` +
|
|
203
|
+
`\n const { registerTelemetryForNextjs } = await import('@open-mercato/telemetry/nextjs')` +
|
|
204
|
+
`\n await registerTelemetryForNextjs()` +
|
|
205
|
+
`\n }`
|
|
206
|
+
const importLine = `import { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'\n`
|
|
207
|
+
const withImport = content.includes('isTelemetryBackendEnabled')
|
|
208
|
+
? content
|
|
209
|
+
: importLine + content
|
|
210
|
+
const shiftedInsertAt = insertAt + (withImport.length - content.length)
|
|
211
|
+
const next = withImport.slice(0, shiftedInsertAt) + block + withImport.slice(shiftedInsertAt)
|
|
212
|
+
if (!options.dryRun) writeFileSync(path, next)
|
|
213
|
+
return { file, status: 'patched', detail: 'inserted registerTelemetryForNextjs() into register()' }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function patchNextConfig(appDir: string, options: TelemetryInitOptions): Promise<StepResult> {
|
|
217
|
+
const file = 'next.config.ts'
|
|
218
|
+
const path = join(appDir, file)
|
|
219
|
+
if (!existsSync(path)) {
|
|
220
|
+
return { file, status: 'manual', detail: 'next.config.ts not found', manualSnippet: nextConfigSnippet() }
|
|
221
|
+
}
|
|
222
|
+
// Use ts-morph to *detect* the real `serverExternalPackages` array literal
|
|
223
|
+
// (robust against a match inside a comment/string), then do the *insertion*
|
|
224
|
+
// via a formatting-preserving text splice so the user's config stays tidy.
|
|
225
|
+
const { Project, SyntaxKind } = await import('ts-morph')
|
|
226
|
+
const project = new Project({ skipAddingFilesFromTsConfig: true, skipFileDependencyResolution: true })
|
|
227
|
+
const sf = project.addSourceFileAtPath(path)
|
|
228
|
+
|
|
229
|
+
const telemetryConfigImport = sf
|
|
230
|
+
.getImportDeclarations()
|
|
231
|
+
.find((decl) => [
|
|
232
|
+
'@open-mercato/telemetry/nextjs',
|
|
233
|
+
'@open-mercato/telemetry/nextjs-config',
|
|
234
|
+
].includes(decl.getModuleSpecifierValue()))
|
|
235
|
+
const hasImport = Boolean(telemetryConfigImport)
|
|
236
|
+
const hasCanonicalImport =
|
|
237
|
+
telemetryConfigImport?.getModuleSpecifierValue() === '@open-mercato/telemetry/nextjs-config'
|
|
238
|
+
|
|
239
|
+
const arrayProp = sf
|
|
240
|
+
.getDescendantsOfKind(SyntaxKind.PropertyAssignment)
|
|
241
|
+
.find((prop) => prop.getName() === 'serverExternalPackages')
|
|
242
|
+
const arrayInit = arrayProp?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression)
|
|
243
|
+
if (!arrayInit) {
|
|
244
|
+
return {
|
|
245
|
+
file,
|
|
246
|
+
status: 'manual',
|
|
247
|
+
detail: 'serverExternalPackages array not found',
|
|
248
|
+
manualSnippet: nextConfigSnippet(),
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const hasSpread = arrayInit.getElements().some((el) => el.getText().includes('telemetryServerExternalPackages'))
|
|
252
|
+
if (hasCanonicalImport && hasSpread) {
|
|
253
|
+
return { file, status: 'skipped', detail: 'telemetryServerExternalPackages already wired' }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let text = readFileSync(path, 'utf8')
|
|
257
|
+
// Splice the array first, using the AST node's byte offsets (formatting-agnostic:
|
|
258
|
+
// handles both single-line and multi-line arrays). Do this before modifying
|
|
259
|
+
// imports, because either an inserted import or the legacy import migration
|
|
260
|
+
// shifts the array offsets captured from the original source.
|
|
261
|
+
if (!hasSpread) {
|
|
262
|
+
const start = arrayInit.getStart()
|
|
263
|
+
const end = arrayInit.getEnd()
|
|
264
|
+
text = text.slice(0, start) + insertSpread(text.slice(start, end)) + text.slice(end)
|
|
265
|
+
}
|
|
266
|
+
text = text.replace(
|
|
267
|
+
"from '@open-mercato/telemetry/nextjs'",
|
|
268
|
+
"from '@open-mercato/telemetry/nextjs-config'",
|
|
269
|
+
)
|
|
270
|
+
if (!hasImport) {
|
|
271
|
+
const importRegex = /^import[^\n]*$/gm
|
|
272
|
+
let lastImport: RegExpExecArray | null = null
|
|
273
|
+
let match: RegExpExecArray | null
|
|
274
|
+
while ((match = importRegex.exec(text)) !== null) lastImport = match
|
|
275
|
+
const insertAt = lastImport ? lastImport.index + lastImport[0].length : 0
|
|
276
|
+
text =
|
|
277
|
+
text.slice(0, insertAt) +
|
|
278
|
+
`\nimport { telemetryServerExternalPackages } from '@open-mercato/telemetry/nextjs-config'` +
|
|
279
|
+
text.slice(insertAt)
|
|
280
|
+
}
|
|
281
|
+
if (!options.dryRun) writeFileSync(path, text)
|
|
282
|
+
return { file, status: 'patched', detail: 'spread telemetryServerExternalPackages into serverExternalPackages' }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Add `...telemetryServerExternalPackages` as the last element of an array literal, preserving its style. */
|
|
286
|
+
function insertSpread(arrayText: string): string {
|
|
287
|
+
const inner = arrayText.slice(1, -1)
|
|
288
|
+
if (inner.includes('\n')) {
|
|
289
|
+
const elementIndent = inner.match(/\n([ \t]+)\S/)?.[1] ?? ' '
|
|
290
|
+
const closeIndent = arrayText.match(/\n([ \t]*)\]$/)?.[1] ?? ''
|
|
291
|
+
const trimmed = inner.replace(/\s*$/, '')
|
|
292
|
+
const separator = trimmed === '' || trimmed.endsWith(',') ? '' : ','
|
|
293
|
+
return `[${trimmed}${separator}\n${elementIndent}...telemetryServerExternalPackages,\n${closeIndent}]`
|
|
294
|
+
}
|
|
295
|
+
const flat = inner.trim()
|
|
296
|
+
return flat === '' ? '[...telemetryServerExternalPackages]' : `[${flat}, ...telemetryServerExternalPackages]`
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function nextConfigSnippet(): string {
|
|
300
|
+
return (
|
|
301
|
+
`import { telemetryServerExternalPackages } from '@open-mercato/telemetry/nextjs-config'\n` +
|
|
302
|
+
`// serverExternalPackages: [ ...existing, ...telemetryServerExternalPackages ]`
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Auto-patch the catch-all API dispatcher — but only when it still matches the
|
|
308
|
+
* known scaffold shape (all anchors present, telemetry not yet wired). On any
|
|
309
|
+
* mismatch, print the snippet and mark it manual rather than editing an
|
|
310
|
+
* unrecognized (customized) handler.
|
|
311
|
+
*/
|
|
312
|
+
function patchDispatcher(appDir: string, options: TelemetryInitOptions): StepResult {
|
|
313
|
+
const file = 'src/app/api/[...slug]/route.ts'
|
|
314
|
+
const path = join(appDir, file)
|
|
315
|
+
if (!existsSync(path)) {
|
|
316
|
+
return { file, status: 'manual', detail: 'dispatcher not found', manualSnippet: DISPATCHER_SNIPPET }
|
|
317
|
+
}
|
|
318
|
+
let content = readFileSync(path, 'utf8')
|
|
319
|
+
if (
|
|
320
|
+
content.includes("from '@open-mercato/shared/lib/telemetry/runtime'")
|
|
321
|
+
&& content.includes('getTelemetryRuntime()?.recordHttpDuration')
|
|
322
|
+
) {
|
|
323
|
+
return { file, status: 'skipped', detail: 'telemetry already wired' }
|
|
324
|
+
}
|
|
325
|
+
if (
|
|
326
|
+
content.includes("import { reportError } from '@open-mercato/telemetry'")
|
|
327
|
+
&& content.includes("import { recordHttpDuration } from '@open-mercato/telemetry/nextjs'")
|
|
328
|
+
) {
|
|
329
|
+
content = content
|
|
330
|
+
.replace(
|
|
331
|
+
"import { reportError } from '@open-mercato/telemetry'\nimport { recordHttpDuration } from '@open-mercato/telemetry/nextjs'",
|
|
332
|
+
"import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'",
|
|
333
|
+
)
|
|
334
|
+
.replace(/\brecordHttpDuration\(/g, 'getTelemetryRuntime()?.recordHttpDuration(')
|
|
335
|
+
.replace(/\breportError\(/g, 'getTelemetryRuntime()?.reportError(')
|
|
336
|
+
if (!options.dryRun) writeFileSync(path, content)
|
|
337
|
+
return { file, status: 'patched', detail: 'migrated dispatcher to the default-off runtime bridge' }
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const importAnchor = "import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'"
|
|
341
|
+
const returnMatch = content.match(/^([ \t]*)return finalResponse\s*$/m)
|
|
342
|
+
// The handleRequest catch is the first `} catch (error) {` after the success return.
|
|
343
|
+
const catchRegex = /^([ \t]*)\}\s*catch\s*\(error\)\s*\{[ \t]*$/gm
|
|
344
|
+
const catchMatch = findCatchAfter(content, catchRegex, returnMatch?.index ?? -1)
|
|
345
|
+
|
|
346
|
+
if (!content.includes(importAnchor) || !returnMatch || returnMatch.index === undefined || !catchMatch) {
|
|
347
|
+
return {
|
|
348
|
+
file,
|
|
349
|
+
status: 'manual',
|
|
350
|
+
detail: 'dispatcher shape not recognized — apply the snippet by hand',
|
|
351
|
+
manualSnippet: DISPATCHER_SNIPPET,
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// 1) imports
|
|
356
|
+
content = content.replace(
|
|
357
|
+
importAnchor,
|
|
358
|
+
`${importAnchor}\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'`,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
// 2) success-path metric (re-match after the import edit shifted offsets)
|
|
362
|
+
const ret = content.match(/^([ \t]*)return finalResponse\s*$/m)
|
|
363
|
+
if (!ret || ret.index === undefined) {
|
|
364
|
+
return { file, status: 'manual', detail: 'success-path anchor lost', manualSnippet: DISPATCHER_SNIPPET }
|
|
365
|
+
}
|
|
366
|
+
const retIndent = ret[1]
|
|
367
|
+
content =
|
|
368
|
+
content.slice(0, ret.index) +
|
|
369
|
+
`${retIndent}getTelemetryRuntime()?.recordHttpDuration(method, match.route.path, finalResponse.status, startedAt)\n` +
|
|
370
|
+
content.slice(ret.index)
|
|
371
|
+
|
|
372
|
+
// 3) catch-block error funnel + 500 metric
|
|
373
|
+
const catch2 = findCatchAfter(content, /^([ \t]*)\}\s*catch\s*\(error\)\s*\{[ \t]*$/gm, content.indexOf('return finalResponse'))
|
|
374
|
+
if (!catch2) {
|
|
375
|
+
return { file, status: 'manual', detail: 'catch anchor lost', manualSnippet: DISPATCHER_SNIPPET }
|
|
376
|
+
}
|
|
377
|
+
const bodyIndent = `${catch2.indent} `
|
|
378
|
+
const insertAt = catch2.index + catch2.length
|
|
379
|
+
const block =
|
|
380
|
+
`\n${bodyIndent}getTelemetryRuntime()?.reportError(error, {` +
|
|
381
|
+
`\n${bodyIndent} attributes: { 'http.request.method': method, 'http.route': match.route.path, 'http.response.status_code': 500 },` +
|
|
382
|
+
`\n${bodyIndent}})` +
|
|
383
|
+
`\n${bodyIndent}getTelemetryRuntime()?.recordHttpDuration(method, match.route.path, 500, startedAt)`
|
|
384
|
+
content = content.slice(0, insertAt) + block + content.slice(insertAt)
|
|
385
|
+
|
|
386
|
+
if (!options.dryRun) writeFileSync(path, content)
|
|
387
|
+
return { file, status: 'patched', detail: 'wired reportError (5xx) + http.server.request.duration metric' }
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function findCatchAfter(
|
|
391
|
+
content: string,
|
|
392
|
+
regex: RegExp,
|
|
393
|
+
afterIndex: number,
|
|
394
|
+
): { index: number; length: number; indent: string } | null {
|
|
395
|
+
regex.lastIndex = 0
|
|
396
|
+
let match: RegExpExecArray | null
|
|
397
|
+
while ((match = regex.exec(content)) !== null) {
|
|
398
|
+
if (match.index > afterIndex) {
|
|
399
|
+
return { index: match.index, length: match[0].length, indent: match[1] }
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return null
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export async function runTelemetryInit(args: string[]): Promise<number> {
|
|
406
|
+
const appDir = resolve('.')
|
|
407
|
+
const options = parseArgs(args)
|
|
408
|
+
|
|
409
|
+
if (!existsSync(join(appDir, 'src', 'modules.ts'))) {
|
|
410
|
+
console.error('❌ Not an Open Mercato app directory (src/modules.ts not found)')
|
|
411
|
+
return 1
|
|
412
|
+
}
|
|
413
|
+
if (!existsSync(join(appDir, 'package.json'))) {
|
|
414
|
+
console.error('❌ package.json not found')
|
|
415
|
+
return 1
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
console.log('')
|
|
419
|
+
console.log(options.dryRun ? '🔭 Telemetry init (dry run — no files written)\n' : '🔭 Wiring @open-mercato/telemetry into this app\n')
|
|
420
|
+
|
|
421
|
+
const results: StepResult[] = []
|
|
422
|
+
results.push(patchPackageJson(appDir, options))
|
|
423
|
+
const envExample = patchEnvFile(appDir, '.env.example', options)
|
|
424
|
+
if (envExample) results.push(envExample)
|
|
425
|
+
const env = patchEnvFile(appDir, '.env', options)
|
|
426
|
+
if (env) results.push(env)
|
|
427
|
+
results.push(patchInstrumentation(appDir, options))
|
|
428
|
+
results.push(await patchNextConfig(appDir, options))
|
|
429
|
+
results.push(patchDispatcher(appDir, options))
|
|
430
|
+
|
|
431
|
+
const icon: Record<StepStatus, string> = { created: '✍️ ', patched: '✅', skipped: '⏭️ ', manual: '⚠️ ' }
|
|
432
|
+
for (const result of results) {
|
|
433
|
+
console.log(`${icon[result.status]} ${result.file} — ${result.detail}`)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const manual = results.filter((result) => result.status === 'manual')
|
|
437
|
+
for (const result of manual) {
|
|
438
|
+
if (!result.manualSnippet) continue
|
|
439
|
+
console.log('')
|
|
440
|
+
console.log(`── manual step for ${result.file} ──`)
|
|
441
|
+
console.log(result.manualSnippet)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
console.log('')
|
|
445
|
+
const changed = results.some((result) => result.status === 'patched' || result.status === 'created')
|
|
446
|
+
if (options.dryRun) {
|
|
447
|
+
console.log('Dry run complete — re-run without --dry-run to apply.')
|
|
448
|
+
} else if (changed) {
|
|
449
|
+
console.log('Next steps:')
|
|
450
|
+
console.log(' 1. yarn install (resolve @open-mercato/telemetry + the OTEL SDK)')
|
|
451
|
+
console.log(' 2. set TELEMETRY_BACKEND + OTEL_EXPORTER_OTLP_* in .env')
|
|
452
|
+
console.log(' 3. yarn typecheck && yarn build (verify the wiring)')
|
|
453
|
+
} else {
|
|
454
|
+
console.log('Nothing to do — telemetry is already wired.')
|
|
455
|
+
}
|
|
456
|
+
if (manual.length > 0) {
|
|
457
|
+
console.log('')
|
|
458
|
+
console.log(`⚠️ ${manual.length} file(s) need a manual edit (shown above) — the rest were applied automatically.`)
|
|
459
|
+
}
|
|
460
|
+
return 0
|
|
461
|
+
}
|
|
@@ -186,6 +186,10 @@ describe('integration cache and options', () => {
|
|
|
186
186
|
expect(environment?.commandEnvironment.QUEUE_BASE_DIR).toBe('/tmp/open-mercato-queue')
|
|
187
187
|
expect(environment?.commandEnvironment[PRIVATE_ATTACHMENTS_PARTITION_ENV_KEY]).toBe(defaultPrivateAttachmentsRoot)
|
|
188
188
|
expect(environment?.commandEnvironment.PW_CAPTURE_SCREENSHOTS).toBe('1')
|
|
189
|
+
expect(environment?.commandEnvironment.PLATFORM_PORTAL_BASE_URL).toBe(baseUrl)
|
|
190
|
+
expect(environment?.commandEnvironment.OM_TEST_EMAIL_CAPTURE_PATH).toBe(
|
|
191
|
+
path.join(projectRootDirectory, '.ai', 'qa', 'email-capture.jsonl'),
|
|
192
|
+
)
|
|
189
193
|
expect(environment?.commandEnvironment.NEXT_PUBLIC_OM_EXAMPLE_CHECKOUT_TEST_INJECTIONS_ENABLED).toBeUndefined()
|
|
190
194
|
} finally {
|
|
191
195
|
fetchSpy.mockRestore()
|
|
@@ -271,6 +271,7 @@ const EPHEMERAL_ENV_LOCK_PATH = path.join(projectRootDirectory, '.ai', 'qa', 'ep
|
|
|
271
271
|
const LEGACY_EPHEMERAL_ENV_FILE_PATH = path.join(projectRootDirectory, '.ai', 'qa', 'ephemeral-env.md')
|
|
272
272
|
const EPHEMERAL_BUILD_CACHE_STATE_PATH = path.join(projectRootDirectory, '.ai', 'qa', 'ephemeral-build-cache.json')
|
|
273
273
|
const EPHEMERAL_CACHE_DB_PATH = path.join(projectRootDirectory, '.ai', 'qa', 'ephemeral-cache.sqlite')
|
|
274
|
+
const EPHEMERAL_EMAIL_CAPTURE_PATH = path.join(projectRootDirectory, '.ai', 'qa', 'email-capture.jsonl')
|
|
274
275
|
const EPHEMERAL_QUEUE_BASE_DIR = path.join(appDirectory, '.mercato', 'queue')
|
|
275
276
|
const PRIVATE_ATTACHMENTS_PARTITION_ENV_KEY = 'ATTACHMENTS_PARTITION_PRIVATE_ATTACHMENTS_ROOT'
|
|
276
277
|
const EPHEMERAL_PRIVATE_ATTACHMENTS_ROOT = path.join(
|
|
@@ -1943,6 +1944,7 @@ function buildReusableEnvironment(
|
|
|
1943
1944
|
BASE_URL: baseUrl,
|
|
1944
1945
|
APP_URL: baseUrl,
|
|
1945
1946
|
NEXT_PUBLIC_APP_URL: baseUrl,
|
|
1947
|
+
PLATFORM_PORTAL_BASE_URL: baseUrl,
|
|
1946
1948
|
NODE_ENV: 'production',
|
|
1947
1949
|
// Share the app server's cache backend with the test process and the
|
|
1948
1950
|
// queue-drain runners it spawns (drainIntegrationQueue children inherit
|
|
@@ -1968,6 +1970,7 @@ function buildReusableEnvironment(
|
|
|
1968
1970
|
OM_ENABLE_ENTERPRISE_MODULES_SSO: process.env.OM_ENABLE_ENTERPRISE_MODULES_SSO ?? enterpriseModulesFlag,
|
|
1969
1971
|
OM_ENABLE_ENTERPRISE_MODULES_SECURITY: process.env.OM_ENABLE_ENTERPRISE_MODULES_SECURITY ?? enterpriseModulesFlag,
|
|
1970
1972
|
OM_TEST_MODE: '1',
|
|
1973
|
+
OM_TEST_EMAIL_CAPTURE_PATH: EPHEMERAL_EMAIL_CAPTURE_PATH,
|
|
1971
1974
|
OM_TEST_AUTH_RATE_LIMIT_MODE: 'opt-in',
|
|
1972
1975
|
// Tests assert on access_logs immediately after CRUD reads; keep the
|
|
1973
1976
|
// blocking write path on inside the integration runtime so tests do
|
|
@@ -3293,6 +3296,7 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
|
|
|
3293
3296
|
BASE_URL: applicationBaseUrl,
|
|
3294
3297
|
APP_URL: applicationBaseUrl,
|
|
3295
3298
|
NEXT_PUBLIC_APP_URL: applicationBaseUrl,
|
|
3299
|
+
PLATFORM_PORTAL_BASE_URL: applicationBaseUrl,
|
|
3296
3300
|
JWT_SECRET: process.env.JWT_SECRET ?? 'om-ephemeral-integration-jwt-secret',
|
|
3297
3301
|
OM_SECURITY_MFA_SETUP_SECRET: process.env.OM_SECURITY_MFA_SETUP_SECRET ?? 'om-ephemeral-integration-mfa-setup-secret',
|
|
3298
3302
|
NODE_ENV: 'production',
|
|
@@ -3321,6 +3325,7 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
|
|
|
3321
3325
|
OM_ENABLE_ENTERPRISE_MODULES_SSO: process.env.OM_ENABLE_ENTERPRISE_MODULES_SSO ?? enterpriseModulesFlag,
|
|
3322
3326
|
OM_ENABLE_ENTERPRISE_MODULES_SECURITY: process.env.OM_ENABLE_ENTERPRISE_MODULES_SECURITY ?? enterpriseModulesFlag,
|
|
3323
3327
|
OM_TEST_MODE: '1',
|
|
3328
|
+
OM_TEST_EMAIL_CAPTURE_PATH: EPHEMERAL_EMAIL_CAPTURE_PATH,
|
|
3324
3329
|
OM_TEST_AUTH_RATE_LIMIT_MODE: 'opt-in',
|
|
3325
3330
|
OM_DISABLE_EMAIL_DELIVERY: '1',
|
|
3326
3331
|
OM_WEBHOOKS_ALLOW_PRIVATE_URLS: process.env.OM_WEBHOOKS_ALLOW_PRIVATE_URLS ?? '1',
|