@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.
Files changed (287) hide show
  1. package/.turbo/turbo-build.log +77 -4
  2. package/build.mjs +68 -11
  3. package/dist/agentic/claude-code/hooks/entity-migration-check.ts +32 -88
  4. package/dist/agentic/codex/enforcement-rules.md +2 -16
  5. package/dist/agentic/cursor/hooks/entity-migration-check.mjs +19 -50
  6. package/dist/agentic/cursor/rules/entity-guard.mdc +9 -21
  7. package/dist/agentic/cursor/rules/generated-guard.mdc +3 -5
  8. package/dist/agentic/cursor/rules/open-mercato.mdc +3 -29
  9. package/dist/agentic/guides/ai-workflows.md +76 -0
  10. package/dist/agentic/guides/architecture.md +107 -0
  11. package/dist/agentic/guides/backend-ui.md +84 -0
  12. package/dist/agentic/guides/contracts.md +92 -0
  13. package/dist/agentic/guides/extensions.md +121 -0
  14. package/dist/agentic/guides/framework-extension-points.md +36 -0
  15. package/dist/agentic/guides/integrations.md +81 -0
  16. package/dist/agentic/guides/module-facts.json +116757 -3951
  17. package/dist/agentic/guides/module-system.md +12 -127
  18. package/dist/agentic/guides/modules/ai_assistant.md +227 -28
  19. package/dist/agentic/guides/modules/api_docs.md +58 -2
  20. package/dist/agentic/guides/modules/api_keys.md +92 -7
  21. package/dist/agentic/guides/modules/attachments.md +119 -14
  22. package/dist/agentic/guides/modules/audit_logs.md +131 -10
  23. package/dist/agentic/guides/modules/auth.md +276 -33
  24. package/dist/agentic/guides/modules/business_rules.md +149 -14
  25. package/dist/agentic/guides/modules/catalog.md +482 -43
  26. package/dist/agentic/guides/modules/channel_gmail.md +82 -3
  27. package/dist/agentic/guides/modules/channel_imap.md +82 -3
  28. package/dist/agentic/guides/modules/checkout.md +305 -27
  29. package/dist/agentic/guides/modules/communication_channels.md +315 -33
  30. package/dist/agentic/guides/modules/configs.md +104 -8
  31. package/dist/agentic/guides/modules/content.md +57 -2
  32. package/dist/agentic/guides/modules/currencies.md +152 -17
  33. package/dist/agentic/guides/modules/customer_accounts.md +387 -45
  34. package/dist/agentic/guides/modules/customers.md +746 -85
  35. package/dist/agentic/guides/modules/dashboards.md +155 -10
  36. package/dist/agentic/guides/modules/data_sync.md +149 -16
  37. package/dist/agentic/guides/modules/design_system.md +102 -0
  38. package/dist/agentic/guides/modules/dictionaries.md +107 -12
  39. package/dist/agentic/guides/modules/directory.md +148 -15
  40. package/dist/agentic/guides/modules/entities.md +145 -13
  41. package/dist/agentic/guides/modules/events.md +59 -2
  42. package/dist/agentic/guides/modules/feature_toggles.md +119 -9
  43. package/dist/agentic/guides/modules/gateway_stripe.md +92 -4
  44. package/dist/agentic/guides/modules/generators.md +45 -2
  45. package/dist/agentic/guides/modules/inbox_ops.md +207 -27
  46. package/dist/agentic/guides/modules/integrations.md +149 -16
  47. package/dist/agentic/guides/modules/messages.md +223 -24
  48. package/dist/agentic/guides/modules/notifications.md +96 -8
  49. package/dist/agentic/guides/modules/onboarding.md +77 -6
  50. package/dist/agentic/guides/modules/payment_gateways.md +168 -21
  51. package/dist/agentic/guides/modules/perspectives.md +77 -7
  52. package/dist/agentic/guides/modules/planner.md +136 -18
  53. package/dist/agentic/guides/modules/portal.md +93 -2
  54. package/dist/agentic/guides/modules/progress.md +104 -14
  55. package/dist/agentic/guides/modules/query_index.md +154 -15
  56. package/dist/agentic/guides/modules/record_locks.md +206 -21
  57. package/dist/agentic/guides/modules/resources.md +197 -30
  58. package/dist/agentic/guides/modules/sales.md +687 -84
  59. package/dist/agentic/guides/modules/scheduler.md +133 -14
  60. package/dist/agentic/guides/modules/search.md +170 -6
  61. package/dist/agentic/guides/modules/security.md +294 -35
  62. package/dist/agentic/guides/modules/shipping_carriers.md +134 -18
  63. package/dist/agentic/guides/modules/sso.md +151 -26
  64. package/dist/agentic/guides/modules/staff.md +372 -57
  65. package/dist/agentic/guides/modules/storage_s3.md +95 -5
  66. package/dist/agentic/guides/modules/sync_akeneo.md +85 -3
  67. package/dist/agentic/guides/modules/sync_excel.md +75 -6
  68. package/dist/agentic/guides/modules/system_status_overlays.md +60 -2
  69. package/dist/agentic/guides/modules/translations.md +104 -10
  70. package/dist/agentic/guides/modules/webhooks.md +203 -24
  71. package/dist/agentic/guides/modules/wms.md +469 -47
  72. package/dist/agentic/guides/modules/workflows.md +302 -43
  73. package/dist/agentic/guides/reference-module-facts.json +6370 -0
  74. package/dist/agentic/guides/reference-modules/example.md +451 -0
  75. package/dist/agentic/guides/spec-delivery.md +42 -0
  76. package/dist/agentic/guides/testing-debugging.md +80 -0
  77. package/dist/agentic/guides/upstream/AGENTS.md +299 -0
  78. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +316 -0
  79. package/dist/agentic/guides/upstream/manifest.json +8 -0
  80. package/dist/agentic/shared/AGENTS.md.template +120 -311
  81. package/dist/agentic/shared/ai/agentic.config.json +2 -0
  82. package/dist/agentic/shared/ai/harness/README.md +50 -0
  83. package/dist/agentic/shared/ai/harness/RELEASE.md +57 -0
  84. package/dist/agentic/shared/ai/harness/cases.json +15050 -0
  85. package/dist/agentic/shared/ai/harness/cases.schema.json +188 -0
  86. package/dist/agentic/shared/ai/harness/fixtures/index.json +51 -0
  87. package/dist/agentic/shared/ai/harness/fixtures/seeds.json +166 -0
  88. package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +21 -0
  89. package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +96 -0
  90. package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +172 -0
  91. package/dist/agentic/shared/ai/harness/knowledge-change.schema.json +228 -0
  92. package/dist/agentic/shared/ai/harness/release-matrix.json +163 -0
  93. package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
  94. package/dist/agentic/shared/ai/harness/result.schema.json +109 -0
  95. package/dist/agentic/shared/ai/harness/routing-response.schema.json +22 -0
  96. package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
  97. package/dist/agentic/shared/ai/harness/validators.json +82 -0
  98. package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +2121 -0
  99. package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +1096 -0
  100. package/dist/agentic/shared/ai/harness/writable-spec-oracles.mjs +412 -0
  101. package/dist/agentic/shared/ai/lessons/_template.md +16 -0
  102. package/dist/agentic/shared/ai/lessons.md +26 -3
  103. package/dist/agentic/shared/ai/review-checklist.md +36 -0
  104. package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +21 -0
  105. package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +4 -0
  106. package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +98 -0
  107. package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +21 -241
  108. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +31 -0
  109. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +53 -0
  110. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +15 -0
  111. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +11 -0
  112. package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +30 -0
  113. package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +12 -0
  114. package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +13 -0
  115. package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +14 -0
  116. package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +28 -0
  117. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +12 -0
  118. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +22 -0
  119. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +12 -0
  120. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +19 -0
  121. package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +17 -601
  122. package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +21 -0
  123. package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +15 -0
  124. package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +16 -0
  125. package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +10 -0
  126. package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +15 -310
  127. package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +19 -0
  128. package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +31 -0
  129. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +100 -0
  130. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +16 -0
  131. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/knowledge-change.md +115 -0
  132. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +17 -0
  133. package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +25 -0
  134. package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +11 -0
  135. package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +13 -0
  136. package/dist/agentic/shared/ai/skills/om-help/SKILL.md +15 -97
  137. package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +18 -0
  138. package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +20 -0
  139. package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +33 -152
  140. package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +33 -0
  141. package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +40 -0
  142. package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +27 -0
  143. package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +24 -0
  144. package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +25 -708
  145. package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +16 -0
  146. package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +25 -0
  147. package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +15 -0
  148. package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +12 -0
  149. package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +34 -0
  150. package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +4 -0
  151. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +11 -0
  152. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +31 -0
  153. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +39 -0
  154. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +43 -0
  155. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +11 -0
  156. package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +20 -820
  157. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +32 -0
  158. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +172 -0
  159. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +14 -0
  160. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +83 -0
  161. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +40 -0
  162. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +11 -0
  163. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +22 -0
  164. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +13 -0
  165. package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +41 -0
  166. package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +19 -0
  167. package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +44 -0
  168. package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +39 -0
  169. package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +20 -0
  170. package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +57 -0
  171. package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +11 -0
  172. package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +486 -0
  173. package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +20 -843
  174. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +42 -0
  175. package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +27 -0
  176. package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +13 -0
  177. package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +43 -0
  178. package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +11 -0
  179. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +15 -63
  180. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +15 -0
  181. package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +17 -467
  182. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +29 -0
  183. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +14 -0
  184. package/dist/agentic/shared/ai/skills/tiers.json +143 -21
  185. package/dist/agentic/shared/ai/skills/tiers.schema.json +52 -40
  186. package/dist/agentic/shared/ai/specs/README.md +4 -3
  187. package/dist/agentic/shared/ai/specs/SPEC-000-template.md +226 -10
  188. package/dist/agentic/shared/ai/trackers/github.md +108 -4
  189. package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +224 -0
  190. package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
  191. package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +3585 -0
  192. package/dist/agentic/shared/scripts/execution-sandbox.mjs +329 -0
  193. package/dist/agentic/shared/scripts/framework-context.mjs +888 -0
  194. package/dist/agentic/shared/scripts/install-skills.mjs +1029 -0
  195. package/dist/agentic/shared/scripts/install-skills.sh +4 -694
  196. package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +222 -0
  197. package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1774 -0
  198. package/dist/agentic/shared/scripts/validate-knowledge-change.mjs +911 -0
  199. package/dist/bin.js +13 -1
  200. package/dist/bin.js.map +2 -2
  201. package/dist/lib/__integration__/TC-INT-008.spec.js +88 -8
  202. package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
  203. package/dist/lib/agentic-init.js +13 -3
  204. package/dist/lib/agentic-init.js.map +2 -2
  205. package/dist/lib/agentic-setup.js +434 -186
  206. package/dist/lib/agentic-setup.js.map +2 -2
  207. package/dist/lib/generators/extensions/injection-widgets.js +20 -0
  208. package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
  209. package/dist/lib/generators/module-extension-facts.js +1872 -0
  210. package/dist/lib/generators/module-extension-facts.js.map +7 -0
  211. package/dist/lib/generators/module-fact-sources.js +70 -0
  212. package/dist/lib/generators/module-fact-sources.js.map +7 -0
  213. package/dist/lib/generators/module-facts-discovery.js +64 -7
  214. package/dist/lib/generators/module-facts-discovery.js.map +2 -2
  215. package/dist/lib/generators/module-facts.js +1629 -52
  216. package/dist/lib/generators/module-facts.js.map +3 -3
  217. package/dist/lib/generators/module-override-targets.js +688 -0
  218. package/dist/lib/generators/module-override-targets.js.map +7 -0
  219. package/dist/lib/generators/module-registry.js +11 -5
  220. package/dist/lib/generators/module-registry.js.map +2 -2
  221. package/dist/lib/load-env.js +24 -0
  222. package/dist/lib/load-env.js.map +7 -0
  223. package/dist/lib/resolver.js +2 -0
  224. package/dist/lib/resolver.js.map +2 -2
  225. package/dist/lib/telemetry-init.js +358 -0
  226. package/dist/lib/telemetry-init.js.map +7 -0
  227. package/dist/lib/testing/integration.js +5 -2
  228. package/dist/lib/testing/integration.js.map +2 -2
  229. package/dist/mercato.js +23 -1
  230. package/dist/mercato.js.map +2 -2
  231. package/package.json +13 -11
  232. package/src/bin.ts +33 -1
  233. package/src/lib/__integration__/TC-INT-008.spec.ts +107 -9
  234. package/src/lib/__tests__/agentic-init.test.ts +41 -5
  235. package/src/lib/__tests__/agentic-setup.ownership.test.ts +386 -0
  236. package/src/lib/__tests__/injection-widget-id-validation.test.ts +184 -0
  237. package/src/lib/__tests__/load-env.test.ts +65 -0
  238. package/src/lib/__tests__/telemetry-init.test.ts +298 -0
  239. package/src/lib/agentic-init.ts +15 -3
  240. package/src/lib/agentic-setup.ts +573 -222
  241. package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
  242. package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
  243. package/src/lib/generators/__tests__/module-extension-facts.test.ts +603 -0
  244. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +139 -2
  245. package/src/lib/generators/__tests__/module-facts.computed-di-token.test.ts +88 -0
  246. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +1 -0
  247. package/src/lib/generators/__tests__/module-facts.discovery.test.ts +54 -10
  248. package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +108 -0
  249. package/src/lib/generators/__tests__/module-facts.local-reference.test.ts +391 -0
  250. package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
  251. package/src/lib/generators/__tests__/module-facts.search-entity-id.test.ts +151 -0
  252. package/src/lib/generators/__tests__/module-facts.source-links.test.ts +160 -0
  253. package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
  254. package/src/lib/generators/__tests__/module-override-targets.test.ts +328 -0
  255. package/src/lib/generators/__tests__/module-subset.test.ts +18 -0
  256. package/src/lib/generators/extensions/injection-widgets.ts +51 -0
  257. package/src/lib/generators/module-extension-facts.ts +2433 -0
  258. package/src/lib/generators/module-fact-sources.ts +108 -0
  259. package/src/lib/generators/module-facts-discovery.ts +104 -7
  260. package/src/lib/generators/module-facts.ts +2387 -138
  261. package/src/lib/generators/module-override-targets.ts +863 -0
  262. package/src/lib/generators/module-registry.ts +16 -6
  263. package/src/lib/load-env.ts +30 -0
  264. package/src/lib/resolver.ts +3 -0
  265. package/src/lib/telemetry-init.ts +461 -0
  266. package/src/lib/testing/__tests__/integration.test.ts +4 -0
  267. package/src/lib/testing/integration.ts +5 -2
  268. package/src/mercato.ts +26 -1
  269. package/dist/agentic/guides/cache.md +0 -45
  270. package/dist/agentic/guides/core.md +0 -453
  271. package/dist/agentic/guides/events.md +0 -117
  272. package/dist/agentic/guides/queue.md +0 -53
  273. package/dist/agentic/guides/search.md +0 -115
  274. package/dist/agentic/guides/shared.md +0 -108
  275. package/dist/agentic/guides/ui.md +0 -347
  276. package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +0 -27
  277. package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +0 -27
  278. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +0 -260
  279. package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +0 -146
  280. package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +0 -84
  281. package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +0 -193
  282. package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +0 -34
  283. package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +0 -762
  284. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +0 -50
  285. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +0 -97
  286. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +0 -271
  287. 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
- ${integrationImportName ? `integrations: (( ${integrationImportName}.integrations ?? (${integrationImportName}.integration ? [${integrationImportName}.integration] : []) ) as import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]),` : ''}
3171
- ${integrationImportName ? `bundles: (( ${integrationImportName}.bundles ?? (${integrationImportName}.bundle ? [${integrationImportName}.bundle] : []) ) as import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]),` : ''}
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
- ${integrationImportName ? `integrations: (( ${integrationImportName}.integrations ?? (${integrationImportName}.integration ? [${integrationImportName}.integration] : []) ) as import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]),` : ''}
3189
- ${integrationImportName ? `bundles: (( ${integrationImportName}.bundles ?? (${integrationImportName}.bundle ? [${integrationImportName}.bundle] : []) ) as import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]),` : ''}
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
+ }
@@ -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
@@ -1983,7 +1986,6 @@ function buildReusableEnvironment(
1983
1986
  MOCK_GATEWAY_WEBHOOK_SECRET: 'open-mercato-mock-dev-webhook-secret',
1984
1987
  MOCK_CARRIER_WEBHOOK_SECRET: 'open-mercato-mock-dev-carrier-webhook-secret',
1985
1988
  MOCK_INBOUND_WEBHOOK_SECRET: 'open-mercato-mock-dev-inbound-webhook-secret',
1986
- NEXT_PUBLIC_OM_EXAMPLE_INJECTION_WIDGETS_ENABLED: 'true',
1987
1989
  NEXT_PUBLIC_UMES_DEVTOOLS: 'true',
1988
1990
  CI: 'true',
1989
1991
  TENANT_DATA_ENCRYPTION_FALLBACK_KEY: process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY ?? 'om-ephemeral-integration-fallback-key',
@@ -3293,6 +3295,7 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
3293
3295
  BASE_URL: applicationBaseUrl,
3294
3296
  APP_URL: applicationBaseUrl,
3295
3297
  NEXT_PUBLIC_APP_URL: applicationBaseUrl,
3298
+ PLATFORM_PORTAL_BASE_URL: applicationBaseUrl,
3296
3299
  JWT_SECRET: process.env.JWT_SECRET ?? 'om-ephemeral-integration-jwt-secret',
3297
3300
  OM_SECURITY_MFA_SETUP_SECRET: process.env.OM_SECURITY_MFA_SETUP_SECRET ?? 'om-ephemeral-integration-mfa-setup-secret',
3298
3301
  NODE_ENV: 'production',
@@ -3321,6 +3324,7 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
3321
3324
  OM_ENABLE_ENTERPRISE_MODULES_SSO: process.env.OM_ENABLE_ENTERPRISE_MODULES_SSO ?? enterpriseModulesFlag,
3322
3325
  OM_ENABLE_ENTERPRISE_MODULES_SECURITY: process.env.OM_ENABLE_ENTERPRISE_MODULES_SECURITY ?? enterpriseModulesFlag,
3323
3326
  OM_TEST_MODE: '1',
3327
+ OM_TEST_EMAIL_CAPTURE_PATH: EPHEMERAL_EMAIL_CAPTURE_PATH,
3324
3328
  OM_TEST_AUTH_RATE_LIMIT_MODE: 'opt-in',
3325
3329
  OM_DISABLE_EMAIL_DELIVERY: '1',
3326
3330
  OM_WEBHOOKS_ALLOW_PRIVATE_URLS: process.env.OM_WEBHOOKS_ALLOW_PRIVATE_URLS ?? '1',
@@ -3333,7 +3337,6 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
3333
3337
  // var at the workflow level, masking the gap). Keep in sync with the
3334
3338
  // Playwright-process env block above.
3335
3339
  MOCK_INBOUND_WEBHOOK_SECRET: 'open-mercato-mock-dev-inbound-webhook-secret',
3336
- NEXT_PUBLIC_OM_EXAMPLE_INJECTION_WIDGETS_ENABLED: 'true',
3337
3340
  NEXT_PUBLIC_UMES_DEVTOOLS: 'true',
3338
3341
  CI: 'true',
3339
3342
  TENANT_DATA_ENCRYPTION_FALLBACK_KEY: process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY ?? 'om-ephemeral-integration-fallback-key',