@open-mercato/cli 0.8.1-develop.7296.1.2111d779db → 0.9.0

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 (217) hide show
  1. package/.turbo/turbo-build.log +8 -2
  2. package/AGENTS.md +2 -0
  3. package/dist/agentic/guides/module-facts.json +7612 -848
  4. package/dist/agentic/guides/module-facts.v2.json +7612 -848
  5. package/dist/agentic/guides/modules/agent_orchestrator/active-extension-bindings.md +1 -1
  6. package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
  7. package/dist/agentic/guides/modules/ai_assistant/encryption.md +13 -0
  8. package/dist/agentic/guides/modules/ai_assistant/exact-override-targets.md +3 -0
  9. package/dist/agentic/guides/modules/ai_assistant/index.md +4 -3
  10. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  11. package/dist/agentic/guides/modules/api_keys/domain-commands.md +12 -0
  12. package/dist/agentic/guides/modules/api_keys/index.md +3 -2
  13. package/dist/agentic/guides/modules/attachments/index.md +1 -1
  14. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  15. package/dist/agentic/guides/modules/auth/index.md +1 -1
  16. package/dist/agentic/guides/modules/availability/acl-features.md +13 -0
  17. package/dist/agentic/guides/modules/availability/backend-pages.md +14 -0
  18. package/dist/agentic/guides/modules/availability/di-registrations-rich.md +11 -0
  19. package/dist/agentic/guides/modules/availability/domain-commands.md +13 -0
  20. package/dist/agentic/guides/modules/availability/entities.md +11 -0
  21. package/dist/agentic/guides/modules/availability/events.md +13 -0
  22. package/dist/agentic/guides/modules/availability/exact-override-targets.md +19 -0
  23. package/dist/agentic/guides/modules/availability/host-extension-points.md +10 -0
  24. package/dist/agentic/guides/modules/availability/index.md +22 -0
  25. package/dist/agentic/guides/modules/availability/owned-contract-module-metadata.md +11 -0
  26. package/dist/agentic/guides/modules/availability/setup.md +11 -0
  27. package/dist/agentic/guides/modules/availability/umes-hosts.md +14 -0
  28. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  29. package/dist/agentic/guides/modules/catalog/active-extension-bindings.md +1 -1
  30. package/dist/agentic/guides/modules/catalog/di-registrations-rich.md +1 -0
  31. package/dist/agentic/guides/modules/catalog/exact-override-targets.md +3 -0
  32. package/dist/agentic/guides/modules/catalog/index.md +1 -1
  33. package/dist/agentic/guides/modules/catalog/workers.md +2 -0
  34. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  35. package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
  36. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  37. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  38. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  39. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  40. package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
  41. package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
  42. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  43. package/dist/agentic/guides/modules/communication_channels/acl-features.md +2 -1
  44. package/dist/agentic/guides/modules/communication_channels/contribution-resolutions.md +1 -0
  45. package/dist/agentic/guides/modules/communication_channels/di-registrations-rich.md +11 -10
  46. package/dist/agentic/guides/modules/communication_channels/domain-commands.md +1 -0
  47. package/dist/agentic/guides/modules/communication_channels/events.md +2 -1
  48. package/dist/agentic/guides/modules/communication_channels/exact-override-targets.md +12 -10
  49. package/dist/agentic/guides/modules/communication_channels/incoming-installed-contributions.md +1 -0
  50. package/dist/agentic/guides/modules/communication_channels/index.md +2 -2
  51. package/dist/agentic/guides/modules/communication_channels/umes-contributions.md +1 -0
  52. package/dist/agentic/guides/modules/communication_channels/umes-hosts.md +1 -0
  53. package/dist/agentic/guides/modules/configs/index.md +1 -1
  54. package/dist/agentic/guides/modules/content/index.md +1 -1
  55. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  56. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  57. package/dist/agentic/guides/modules/customer_groups/acl-features.md +16 -0
  58. package/dist/agentic/guides/modules/customer_groups/active-extension-bindings.md +13 -0
  59. package/dist/agentic/guides/modules/customer_groups/backend-pages.md +14 -0
  60. package/dist/agentic/guides/modules/customer_groups/cli-commands.md +11 -0
  61. package/dist/agentic/guides/modules/customer_groups/contribution-resolutions.md +13 -0
  62. package/dist/agentic/guides/modules/customer_groups/di-registrations-rich.md +13 -0
  63. package/dist/agentic/guides/modules/customer_groups/di-service-tokens.md +11 -0
  64. package/dist/agentic/guides/modules/customer_groups/domain-commands.md +11 -0
  65. package/dist/agentic/guides/modules/customer_groups/entities.md +13 -0
  66. package/dist/agentic/guides/modules/customer_groups/events.md +17 -0
  67. package/dist/agentic/guides/modules/customer_groups/exact-override-targets.md +27 -0
  68. package/dist/agentic/guides/modules/customer_groups/index.md +26 -0
  69. package/dist/agentic/guides/modules/customer_groups/owned-contract-module-metadata.md +11 -0
  70. package/dist/agentic/guides/modules/customer_groups/setup.md +11 -0
  71. package/dist/agentic/guides/modules/customer_groups/umes-contributions.md +13 -0
  72. package/dist/agentic/guides/modules/customer_groups/umes-hosts.md +21 -0
  73. package/dist/agentic/guides/modules/customers/acl-features.md +2 -1
  74. package/dist/agentic/guides/modules/customers/active-extension-bindings.md +8 -7
  75. package/dist/agentic/guides/modules/customers/contribution-resolutions.md +1 -0
  76. package/dist/agentic/guides/modules/customers/domain-commands.md +1 -0
  77. package/dist/agentic/guides/modules/customers/entities.md +1 -0
  78. package/dist/agentic/guides/modules/customers/events.md +2 -1
  79. package/dist/agentic/guides/modules/customers/exact-override-targets.md +2 -0
  80. package/dist/agentic/guides/modules/customers/index.md +3 -3
  81. package/dist/agentic/guides/modules/customers/umes-contributions.md +1 -0
  82. package/dist/agentic/guides/modules/customers/umes-hosts.md +3 -0
  83. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  84. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  85. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  86. package/dist/agentic/guides/modules/devices/index.md +1 -1
  87. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  88. package/dist/agentic/guides/modules/directory/active-extension-bindings.md +1 -1
  89. package/dist/agentic/guides/modules/directory/index.md +1 -1
  90. package/dist/agentic/guides/modules/documents/index.md +1 -1
  91. package/dist/agentic/guides/modules/entities/active-extension-bindings.md +1 -1
  92. package/dist/agentic/guides/modules/entities/index.md +1 -1
  93. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  94. package/dist/agentic/guides/modules/events/index.md +1 -1
  95. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  96. package/dist/agentic/guides/modules/forms/acl-features.md +16 -0
  97. package/dist/agentic/guides/modules/forms/active-extension-bindings.md +14 -0
  98. package/dist/agentic/guides/modules/forms/backend-pages.md +17 -0
  99. package/dist/agentic/guides/modules/forms/contribution-resolutions.md +21 -0
  100. package/dist/agentic/guides/modules/forms/di-registrations-rich.md +27 -0
  101. package/dist/agentic/guides/modules/forms/di-service-tokens.md +19 -0
  102. package/dist/agentic/guides/modules/forms/domain-commands.md +30 -0
  103. package/dist/agentic/guides/modules/forms/encryption.md +11 -0
  104. package/dist/agentic/guides/modules/forms/entities.md +21 -0
  105. package/dist/agentic/guides/modules/forms/events.md +28 -0
  106. package/dist/agentic/guides/modules/forms/exact-override-targets.md +59 -0
  107. package/dist/agentic/guides/modules/forms/frontend-pages.md +16 -0
  108. package/dist/agentic/guides/modules/forms/index.md +28 -0
  109. package/dist/agentic/guides/modules/forms/owned-contract-module-metadata.md +11 -0
  110. package/dist/agentic/guides/modules/forms/setup.md +11 -0
  111. package/dist/agentic/guides/modules/forms/umes-contributions.md +21 -0
  112. package/dist/agentic/guides/modules/forms/umes-hosts.md +43 -0
  113. package/dist/agentic/guides/modules/forms/workers.md +12 -0
  114. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  115. package/dist/agentic/guides/modules/generators/index.md +1 -1
  116. package/dist/agentic/guides/modules/inbox_ops/active-extension-bindings.md +1 -1
  117. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  118. package/dist/agentic/guides/modules/integrations/di-registrations-rich.md +8 -8
  119. package/dist/agentic/guides/modules/integrations/di-service-tokens.md +4 -4
  120. package/dist/agentic/guides/modules/integrations/exact-override-targets.md +8 -8
  121. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  122. package/dist/agentic/guides/modules/messages/index.md +1 -1
  123. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  124. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  125. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  126. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  127. package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
  128. package/dist/agentic/guides/modules/planner/active-extension-bindings.md +1 -1
  129. package/dist/agentic/guides/modules/planner/index.md +1 -1
  130. package/dist/agentic/guides/modules/portal/index.md +1 -1
  131. package/dist/agentic/guides/modules/progress/index.md +1 -1
  132. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  133. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  134. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  135. package/dist/agentic/guides/modules/resources/active-extension-bindings.md +1 -1
  136. package/dist/agentic/guides/modules/resources/index.md +1 -1
  137. package/dist/agentic/guides/modules/sales/active-extension-bindings.md +1 -1
  138. package/dist/agentic/guides/modules/sales/exact-override-targets.md +4 -4
  139. package/dist/agentic/guides/modules/sales/index.md +1 -1
  140. package/dist/agentic/guides/modules/sales/setup.md +1 -1
  141. package/dist/agentic/guides/modules/scheduler/index.md +1 -1
  142. package/dist/agentic/guides/modules/search/index.md +1 -1
  143. package/dist/agentic/guides/modules/security/index.md +1 -1
  144. package/dist/agentic/guides/modules/seeds/index.md +1 -1
  145. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  146. package/dist/agentic/guides/modules/sso/index.md +1 -1
  147. package/dist/agentic/guides/modules/staff/active-extension-bindings.md +1 -1
  148. package/dist/agentic/guides/modules/staff/index.md +1 -1
  149. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  150. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  151. package/dist/agentic/guides/modules/sync_excel/active-extension-bindings.md +11 -0
  152. package/dist/agentic/guides/modules/sync_excel/index.md +3 -2
  153. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  154. package/dist/agentic/guides/modules/telemetry/index.md +1 -1
  155. package/dist/agentic/guides/modules/tillio/index.md +1 -1
  156. package/dist/agentic/guides/modules/translations/index.md +1 -1
  157. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  158. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  159. package/dist/agentic/guides/modules/wms/active-extension-bindings.md +1 -1
  160. package/dist/agentic/guides/modules/wms/contribution-resolutions.md +3 -0
  161. package/dist/agentic/guides/modules/wms/di-registrations-rich.md +8 -8
  162. package/dist/agentic/guides/modules/wms/exact-override-targets.md +11 -8
  163. package/dist/agentic/guides/modules/wms/index.md +3 -3
  164. package/dist/agentic/guides/modules/wms/umes-contributions.md +3 -0
  165. package/dist/agentic/guides/modules/workflows/active-extension-bindings.md +3 -3
  166. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  167. package/dist/agentic/guides/reference-module-facts.json +1 -1
  168. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +73 -1
  169. package/dist/agentic/guides/upstream/manifest.json +2 -2
  170. package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.8.0-to-0.9.0/SKILL.md +112 -0
  171. package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +1 -1
  172. package/dist/agentic/shared/ai/skills/tiers.json +2 -1
  173. package/dist/bin.js +2 -2
  174. package/dist/bin.js.map +2 -2
  175. package/dist/lib/db/commands.js +84 -5
  176. package/dist/lib/db/commands.js.map +2 -2
  177. package/dist/lib/db/encryption-backfill-check.js +112 -0
  178. package/dist/lib/db/encryption-backfill-check.js.map +7 -0
  179. package/dist/lib/db/migration-reindex.js +2 -1
  180. package/dist/lib/db/migration-reindex.js.map +2 -2
  181. package/dist/lib/events-single-delivery.js.map +2 -2
  182. package/dist/lib/flush-before-exit.js +19 -0
  183. package/dist/lib/flush-before-exit.js.map +7 -0
  184. package/dist/lib/generate-watch-structure.js +113 -19
  185. package/dist/lib/generate-watch-structure.js.map +3 -3
  186. package/dist/lib/generators/module-registry.js +4 -2
  187. package/dist/lib/generators/module-registry.js.map +2 -2
  188. package/dist/lib/generators/web-research-adapters.js +32 -11
  189. package/dist/lib/generators/web-research-adapters.js.map +2 -2
  190. package/dist/lib/telemetry-init.js +69 -11
  191. package/dist/lib/telemetry-init.js.map +3 -3
  192. package/dist/lib/testing/integration.js +29 -2
  193. package/dist/lib/testing/integration.js.map +2 -2
  194. package/package.json +8 -8
  195. package/src/bin.ts +4 -4
  196. package/src/lib/__tests__/flush-before-exit.test.ts +32 -0
  197. package/src/lib/__tests__/generate-watch-structure.test.ts +261 -3
  198. package/src/lib/__tests__/telemetry-init.test.ts +89 -0
  199. package/src/lib/db/__tests__/commands.test.ts +43 -0
  200. package/src/lib/db/__tests__/dynamic-import-specifier.test.ts +36 -0
  201. package/src/lib/db/__tests__/encryption-backfill-check.test.ts +146 -0
  202. package/src/lib/db/__tests__/migration-reindex.test.ts +48 -3
  203. package/src/lib/db/commands.ts +113 -10
  204. package/src/lib/db/encryption-backfill-check.ts +163 -0
  205. package/src/lib/db/migration-reindex.ts +2 -1
  206. package/src/lib/events-single-delivery.ts +5 -2
  207. package/src/lib/flush-before-exit.ts +39 -0
  208. package/src/lib/generate-watch-structure.ts +134 -19
  209. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +14 -3
  210. package/src/lib/generators/__tests__/module-package-sources.test.ts +2 -2
  211. package/src/lib/generators/__tests__/registry-variant-parity.test.ts +16 -0
  212. package/src/lib/generators/__tests__/web-research-adapters.test.ts +141 -0
  213. package/src/lib/generators/module-registry.ts +2 -0
  214. package/src/lib/generators/web-research-adapters.ts +41 -20
  215. package/src/lib/telemetry-init.ts +68 -11
  216. package/src/lib/testing/__tests__/integration.test.ts +26 -0
  217. package/src/lib/testing/integration.ts +32 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.8.1-develop.7296.1.2111d779db",
3
+ "version": "0.9.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,9 +60,10 @@
60
60
  "@mikro-orm/decorators": "^7.1.14",
61
61
  "@mikro-orm/migrations": "^7.1.14",
62
62
  "@mikro-orm/postgresql": "^7.1.14",
63
- "@open-mercato/queue": "0.8.1-develop.7296.1.2111d779db",
64
- "@open-mercato/shared": "0.8.1-develop.7296.1.2111d779db",
65
- "@open-mercato/telemetry": "0.8.1-develop.7296.1.2111d779db",
63
+ "@open-mercato/events": "0.9.0",
64
+ "@open-mercato/queue": "0.9.0",
65
+ "@open-mercato/shared": "0.9.0",
66
+ "@open-mercato/telemetry": "0.9.0",
66
67
  "cross-spawn": "^7.0.6",
67
68
  "pg": "8.23.0",
68
69
  "semver": "^7.8.5",
@@ -73,10 +74,10 @@
73
74
  "typescript-js": "npm:typescript@6.0.3"
74
75
  },
75
76
  "peerDependencies": {
76
- "@open-mercato/shared": "0.8.1-develop.7296.1.2111d779db"
77
+ "@open-mercato/shared": "0.9.0"
77
78
  },
78
79
  "devDependencies": {
79
- "@open-mercato/shared": "0.8.1-develop.7296.1.2111d779db",
80
+ "@open-mercato/shared": "0.9.0",
80
81
  "@types/jest": "^30.0.0",
81
82
  "jest": "^30.4.2",
82
83
  "ts-jest": "^29.4.12"
@@ -88,6 +89,5 @@
88
89
  "type": "git",
89
90
  "url": "https://github.com/open-mercato/open-mercato",
90
91
  "directory": "packages/cli"
91
- },
92
- "stableVersion": "0.8.0"
92
+ }
93
93
  }
package/src/bin.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'
8
8
  import { resolveCliBootstrapMode, type CliBootstrapMode } from './lib/cli-bootstrap-mode.js'
9
- import { flushTelemetry } from './lib/flush-telemetry.js'
9
+ import { flushBeforeExit } from './lib/flush-before-exit.js'
10
10
  // `run` is imported dynamically inside `main()` so telemetry can initialize
11
11
  // before the mercato entry (and its Postgres driver) loads — see main().
12
12
 
@@ -114,9 +114,9 @@ async function main(): Promise<void> {
114
114
  // Postgres driver it pulls in — loads only after initTelemetry() above.
115
115
  const { run } = await import('./mercato.js')
116
116
  const code = await run(process.argv)
117
- // Flush spans/logs for commands that return (workers block forever and flush via
118
- // their own shutdown handler instead).
119
- await flushTelemetry()
117
+ // Flush the coalesced browser broadcasts, then spans/logs, for commands that
118
+ // return (workers block forever and flush via their own shutdown handler instead).
119
+ await flushBeforeExit()
120
120
  process.exit(code ?? 0)
121
121
  }
122
122
 
@@ -0,0 +1,32 @@
1
+ import { flushBeforeExit } from '../flush-before-exit'
2
+
3
+ describe('flushBeforeExit', () => {
4
+ let warn: jest.SpyInstance
5
+
6
+ beforeEach(() => {
7
+ warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
8
+ })
9
+
10
+ afterEach(() => {
11
+ warn.mockRestore()
12
+ })
13
+
14
+ it('flushes coalesced broadcasts before telemetry so the final pg_notify is still exported', async () => {
15
+ const order: string[] = []
16
+ await flushBeforeExit({
17
+ flushBroadcasts: async () => { order.push('broadcasts') },
18
+ flushTelemetry: async () => { order.push('telemetry') },
19
+ })
20
+ expect(order).toEqual(['broadcasts', 'telemetry'])
21
+ })
22
+
23
+ it('still flushes telemetry, with a warning, when the broadcast flush fails', async () => {
24
+ const flushTelemetry = jest.fn().mockResolvedValue(undefined)
25
+ await expect(flushBeforeExit({
26
+ flushBroadcasts: async () => { throw new Error('connection terminated') },
27
+ flushTelemetry,
28
+ })).resolves.toBeUndefined()
29
+ expect(flushTelemetry).toHaveBeenCalledTimes(1)
30
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('connection terminated'))
31
+ })
32
+ })
@@ -29,13 +29,35 @@ describe('calculateGenerateWatchStructureChecksum', () => {
29
29
  fs.rmSync(root, { recursive: true, force: true })
30
30
  })
31
31
 
32
- function currentChecksum(): string {
32
+ function checksumModule(modulesFile: string, appBase: string, packageBase: string): string {
33
33
  return calculateGenerateWatchStructureChecksum({
34
- modulesFile: path.join(appDir, 'src', 'modules.ts'),
35
- moduleRoots: [{ appBase: appModule, pkgBase: pkgModule }],
34
+ modulesFile,
35
+ moduleRoots: [{ appBase, pkgBase: packageBase }],
36
36
  })
37
37
  }
38
38
 
39
+ function currentChecksum(): string {
40
+ return checksumModule(path.join(appDir, 'src', 'modules.ts'), appModule, pkgModule)
41
+ }
42
+
43
+ function createStandaloneModule() {
44
+ const standaloneRoot = path.join(root, 'standalone')
45
+ const packageRoot = path.join(standaloneRoot, 'node_modules', '@open-mercato', 'core')
46
+ const sourceModule = path.join(packageRoot, 'src', 'modules', 'customers')
47
+ const distModule = path.join(packageRoot, 'dist', 'modules', 'customers')
48
+ const standaloneAppModule = path.join(standaloneRoot, 'src', 'modules', 'customers')
49
+ const modulesFile = path.join(standaloneRoot, 'src', 'modules.ts')
50
+ write(modulesFile, 'export const enabledModules = ["customers"]\n')
51
+ write(path.join(sourceModule, 'index.ts'), 'export const metadata = { id: "customers" }\n')
52
+ write(path.join(distModule, 'index.js'), 'export const metadata = { id: "customers" }\n')
53
+ return {
54
+ sourceModule,
55
+ distModule,
56
+ appModule: standaloneAppModule,
57
+ modulesFile,
58
+ }
59
+ }
60
+
39
61
  it('ignores ordinary component edits outside generator discovery paths', () => {
40
62
  const before = currentChecksum()
41
63
 
@@ -78,6 +100,32 @@ describe('calculateGenerateWatchStructureChecksum', () => {
78
100
  expect(currentChecksum()).not.toBe(before)
79
101
  })
80
102
 
103
+ it('changes when an identical convention file moves between module roots', () => {
104
+ const ordersPkgModule = path.join(root, 'packages', 'core', 'src', 'modules', 'orders')
105
+ const ordersAppModule = path.join(appDir, 'src', 'modules', 'orders')
106
+ const customersCli = path.join(pkgModule, 'cli.ts')
107
+ const ordersCli = path.join(ordersPkgModule, 'cli.ts')
108
+ const cliSource = 'export default function registerCli() {}\n'
109
+ const moduleRoots = [
110
+ { appBase: appModule, pkgBase: pkgModule },
111
+ { appBase: ordersAppModule, pkgBase: ordersPkgModule },
112
+ ]
113
+ write(path.join(ordersPkgModule, 'index.ts'), 'export const metadata = { id: "orders" }\n')
114
+ write(customersCli, cliSource)
115
+ const before = calculateGenerateWatchStructureChecksum({
116
+ modulesFile: path.join(appDir, 'src', 'modules.ts'),
117
+ moduleRoots,
118
+ })
119
+
120
+ fs.rmSync(customersCli)
121
+ write(ordersCli, cliSource)
122
+
123
+ expect(calculateGenerateWatchStructureChecksum({
124
+ modulesFile: path.join(appDir, 'src', 'modules.ts'),
125
+ moduleRoots,
126
+ })).not.toBe(before)
127
+ })
128
+
81
129
  // A runtime.ts the watcher cannot see is the failure SPEC-072 exists to remove, reappearing in
82
130
  // the dev loop: the registry is not regenerated, `Module.runtime` stays undefined, and the
83
131
  // runtime never starts — with no error and no warning.
@@ -115,4 +163,214 @@ describe('calculateGenerateWatchStructureChecksum', () => {
115
163
 
116
164
  expect(currentChecksum()).not.toBe(before)
117
165
  })
166
+
167
+ it('tracks generator-relevant API route changes from a standalone source mirror', () => {
168
+ const standalone = createStandaloneModule()
169
+ const sourceRoute = path.join(standalone.sourceModule, 'api', 'records', 'route.ts')
170
+ const distRoute = path.join(standalone.distModule, 'api', 'records', 'route.js')
171
+ write(
172
+ sourceRoute,
173
+ 'export const metadata = { path: "/records" }\nexport async function GET() { return null }\n',
174
+ )
175
+ write(
176
+ distRoute,
177
+ 'export const metadata = { path: "/records" };\nexport async function GET() { return null; }\n',
178
+ )
179
+ const before = checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule)
180
+
181
+ write(
182
+ sourceRoute,
183
+ 'export const metadata = { path: "/contacts" }\nexport async function GET() { return null }\n',
184
+ )
185
+ const afterSourceMetadata = checksumModule(
186
+ standalone.modulesFile,
187
+ standalone.appModule,
188
+ standalone.distModule,
189
+ )
190
+ expect(afterSourceMetadata).not.toBe(before)
191
+
192
+ write(
193
+ distRoute,
194
+ 'export const metadata = { path: "/contacts" };\nexport async function GET() { return null; }\n',
195
+ )
196
+ const afterDistMetadata = checksumModule(
197
+ standalone.modulesFile,
198
+ standalone.appModule,
199
+ standalone.distModule,
200
+ )
201
+ expect(afterDistMetadata).not.toBe(afterSourceMetadata)
202
+
203
+ write(
204
+ sourceRoute,
205
+ 'export const metadata = { path: "/contacts" }\nexport async function POST() { return null }\n',
206
+ )
207
+ const afterSourceMethod = checksumModule(
208
+ standalone.modulesFile,
209
+ standalone.appModule,
210
+ standalone.distModule,
211
+ )
212
+ expect(afterSourceMethod).not.toBe(afterDistMetadata)
213
+
214
+ write(
215
+ distRoute,
216
+ 'export const metadata = { path: "/contacts" };\nexport async function POST() { return null; }\n',
217
+ )
218
+ expect(checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule))
219
+ .not.toBe(afterSourceMethod)
220
+ })
221
+
222
+ it('tracks standalone source-mirror page metadata but ignores ordinary page implementation edits', () => {
223
+ const standalone = createStandaloneModule()
224
+ const sourcePage = path.join(standalone.sourceModule, 'backend', 'customers', 'people', 'page.tsx')
225
+ const sourceMeta = path.join(standalone.sourceModule, 'backend', 'customers', 'people', 'page.meta.ts')
226
+ write(sourcePage, 'export default function Page() { return null }\n')
227
+ write(sourceMeta, 'export const metadata = { nav: { label: "People" } }\n')
228
+ write(
229
+ path.join(standalone.distModule, 'backend', 'customers', 'people', 'page.js'),
230
+ 'export default function Page() { return null; }\n',
231
+ )
232
+ write(
233
+ path.join(standalone.distModule, 'backend', 'customers', 'people', 'page.meta.js'),
234
+ 'export const metadata = { nav: { label: "People" } };\n',
235
+ )
236
+ const before = checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule)
237
+
238
+ write(sourcePage, 'export default function Page() { return "implementation changed" }\n')
239
+ expect(checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule)).toBe(before)
240
+
241
+ write(sourceMeta, 'export const metadata = { nav: { label: "Contacts" } }\n')
242
+ const afterSourceMetadata = checksumModule(
243
+ standalone.modulesFile,
244
+ standalone.appModule,
245
+ standalone.distModule,
246
+ )
247
+ expect(afterSourceMetadata).not.toBe(before)
248
+
249
+ write(
250
+ path.join(standalone.distModule, 'backend', 'customers', 'people', 'page.meta.js'),
251
+ 'export const metadata = { nav: { label: "Contacts" } };\n',
252
+ )
253
+ expect(checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule))
254
+ .not.toBe(afterSourceMetadata)
255
+ })
256
+
257
+ it('tracks additions and deletions enumerated from a standalone source mirror', () => {
258
+ const standalone = createStandaloneModule()
259
+ const before = checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule)
260
+ const sourceWorker = path.join(standalone.sourceModule, 'workers', 'sync-customers.ts')
261
+ write(sourceWorker, 'export default async function syncCustomers() {}\n')
262
+ write(
263
+ path.join(standalone.distModule, 'workers', 'sync-customers.js'),
264
+ 'export default async function syncCustomers() {}\n',
265
+ )
266
+
267
+ expect(checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule)).not.toBe(before)
268
+
269
+ fs.rmSync(sourceWorker)
270
+ expect(checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule)).toBe(before)
271
+ })
272
+
273
+ it('tracks stale compiled entities after the source-mirror convention is deleted', () => {
274
+ const standalone = createStandaloneModule()
275
+ const sourceEntities = path.join(standalone.sourceModule, 'data', 'entities.ts')
276
+ const distEntities = path.join(standalone.distModule, 'data', 'entities.js')
277
+ const before = checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule)
278
+
279
+ write(sourceEntities, 'export class Customer {}\n')
280
+ const afterSourceAdd = checksumModule(
281
+ standalone.modulesFile,
282
+ standalone.appModule,
283
+ standalone.distModule,
284
+ )
285
+ expect(afterSourceAdd).not.toBe(before)
286
+
287
+ write(distEntities, 'export class Customer {}\n')
288
+ const afterDistAdd = checksumModule(
289
+ standalone.modulesFile,
290
+ standalone.appModule,
291
+ standalone.distModule,
292
+ )
293
+ expect(afterDistAdd).not.toBe(afterSourceAdd)
294
+
295
+ write(sourceEntities, 'export class CustomerAccount {}\n')
296
+ const afterSourceEdit = checksumModule(
297
+ standalone.modulesFile,
298
+ standalone.appModule,
299
+ standalone.distModule,
300
+ )
301
+ expect(afterSourceEdit).not.toBe(afterDistAdd)
302
+
303
+ write(distEntities, 'export class CustomerAccount {}\n')
304
+ const afterDistEdit = checksumModule(
305
+ standalone.modulesFile,
306
+ standalone.appModule,
307
+ standalone.distModule,
308
+ )
309
+ expect(afterDistEdit).not.toBe(afterSourceEdit)
310
+
311
+ fs.rmSync(sourceEntities)
312
+ const afterSourceDelete = checksumModule(
313
+ standalone.modulesFile,
314
+ standalone.appModule,
315
+ standalone.distModule,
316
+ )
317
+ expect(afterSourceDelete).not.toBe(afterDistEdit)
318
+ expect(afterSourceDelete).not.toBe(before)
319
+
320
+ fs.rmSync(distEntities)
321
+ expect(checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule))
322
+ .toBe(before)
323
+ })
324
+
325
+ it('fingerprints the selected app override instead of the shadowed package route', () => {
326
+ const standalone = createStandaloneModule()
327
+ const routeSource = 'export const metadata = { path: "/records" }\nexport async function GET() { return null }\n'
328
+ const packageRoute = path.join(standalone.sourceModule, 'api', 'records', 'route.js')
329
+ const appRoute = path.join(standalone.appModule, 'api', 'records', 'route.js')
330
+ write(packageRoute, routeSource)
331
+ write(path.join(standalone.distModule, 'api', 'records', 'route.js'), routeSource)
332
+ const beforeOverride = checksumModule(
333
+ standalone.modulesFile,
334
+ standalone.appModule,
335
+ standalone.distModule,
336
+ )
337
+
338
+ write(appRoute, routeSource)
339
+ const withOverride = checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule)
340
+ expect(withOverride).not.toBe(beforeOverride)
341
+
342
+ write(
343
+ packageRoute,
344
+ 'export const metadata = { path: "/shadowed" }\nexport async function POST() { return null }\n',
345
+ )
346
+ expect(checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule))
347
+ .toBe(withOverride)
348
+
349
+ fs.rmSync(appRoute)
350
+ expect(checksumModule(standalone.modulesFile, standalone.appModule, standalone.distModule))
351
+ .not.toBe(withOverride)
352
+ })
353
+
354
+ it('keeps content-sensitive fingerprinting for dist-only installed packages', () => {
355
+ const standaloneRoot = path.join(root, 'dist-only')
356
+ const distModule = path.join(
357
+ standaloneRoot,
358
+ 'node_modules',
359
+ '@open-mercato',
360
+ 'core',
361
+ 'dist',
362
+ 'modules',
363
+ 'customers',
364
+ )
365
+ const modulesFile = path.join(standaloneRoot, 'src', 'modules.ts')
366
+ const routePath = path.join(distModule, 'api', 'records', 'route.js')
367
+ write(modulesFile, 'export const enabledModules = ["customers"]\n')
368
+ write(path.join(distModule, 'index.js'), 'export const metadata = { id: "customers" }\n')
369
+ write(routePath, 'export const metadata = { path: "/records" };\nexport async function GET() {}\n')
370
+ const appModule = path.join(standaloneRoot, 'src', 'modules', 'customers')
371
+ const before = checksumModule(modulesFile, appModule, distModule)
372
+
373
+ write(routePath, 'export const metadata = { path: "/contacts" };\nexport async function GET() {}\n')
374
+ expect(checksumModule(modulesFile, appModule, distModule)).not.toBe(before)
375
+ })
118
376
  })
@@ -99,6 +99,33 @@ async function handleRequest(method: string, req: NextRequest): Promise<Response
99
99
  }
100
100
  `
101
101
 
102
+ const LEGACY_INSTRUMENTATION = `import { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'
103
+
104
+ export async function register(): Promise<void> {
105
+ if (
106
+ process.env.NEXT_RUNTIME === 'nodejs'
107
+ && process.env.NEXT_PHASE !== 'phase-production-build'
108
+ ) {
109
+ const { assertJwtSecretPolicy } = await import('@open-mercato/shared/lib/auth/jwt')
110
+ try {
111
+ assertJwtSecretPolicy()
112
+ } catch (err) {
113
+ const nodeProcess = process
114
+ nodeProcess.stderr.write(\`\${err instanceof Error ? err.message : String(err)}\\n\`)
115
+ nodeProcess.exit(1)
116
+ }
117
+ }
118
+
119
+ if (
120
+ process.env.NEXT_RUNTIME === 'nodejs'
121
+ && isTelemetryBackendEnabled()
122
+ ) {
123
+ const { registerTelemetryForNextjs } = await import('@open-mercato/telemetry/nextjs')
124
+ await registerTelemetryForNextjs()
125
+ }
126
+ }
127
+ `
128
+
102
129
  describe('mercato telemetry init', () => {
103
130
  let tmpDir: string
104
131
  let cwd: string
@@ -142,6 +169,8 @@ describe('mercato telemetry init', () => {
142
169
  expect(read('.env.example')).toContain('TELEMETRY_BACKEND')
143
170
  expect(read('.env.example')).toContain('OTEL_EXPORTER_OTLP_ENDPOINT')
144
171
  expect(read('src/instrumentation.ts')).toContain('registerTelemetryForNextjs')
172
+ expect(read('src/instrumentation.ts')).toContain('nodeProcess.stderr.write')
173
+ expect(read('src/instrumentation.ts')).toContain('nodeProcess.exit(1)')
145
174
 
146
175
  const nextConfig = read('next.config.ts')
147
176
  expect(nextConfig).toContain('@open-mercato/telemetry/nextjs-config')
@@ -277,9 +306,69 @@ describe('mercato telemetry init', () => {
277
306
  const instrumentation = read('src/instrumentation.ts')
278
307
  assertParses(instrumentation, 'custom-instrumentation')
279
308
  expect(instrumentation).toContain('registerTelemetryForNextjs')
309
+ expect(instrumentation).toContain('nodeProcess.stderr.write')
310
+ expect(instrumentation).toContain('nodeProcess.exit(1)')
280
311
  expect(instrumentation).toContain("console.log('custom warmup')") // preserved
281
312
  })
282
313
 
314
+ it('upgrades the legacy generated instrumentation and stays idempotent', async () => {
315
+ baseFixture(tmpDir)
316
+ fs.writeFileSync(path.join(tmpDir, dispatcherPath), SIMPLE_DISPATCHER)
317
+ fs.writeFileSync(path.join(tmpDir, 'src', 'instrumentation.ts'), LEGACY_INSTRUMENTATION)
318
+
319
+ await runTelemetryInit([])
320
+ const migrated = read('src/instrumentation.ts')
321
+ assertParses(migrated, 'legacy-instrumentation-migration')
322
+ expect(migrated).toContain('nodeProcess.stderr.write')
323
+ expect(migrated).toContain('nodeProcess.exit(1)')
324
+ expect((migrated.match(/registerTelemetryForNextjs\(\)/g) ?? []).length).toBe(1)
325
+ expect((migrated.match(/nodeProcess\.exit\(1\)/g) ?? []).length).toBe(2)
326
+
327
+ await runTelemetryInit([])
328
+ expect(read('src/instrumentation.ts')).toBe(migrated)
329
+ })
330
+
331
+ it('upgrades the older Node-only generated bootstrap', async () => {
332
+ baseFixture(tmpDir)
333
+ fs.writeFileSync(path.join(tmpDir, dispatcherPath), SIMPLE_DISPATCHER)
334
+ fs.writeFileSync(
335
+ path.join(tmpDir, 'src', 'instrumentation.ts'),
336
+ `export async function register(): Promise<void> {
337
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
338
+ const { registerTelemetryForNextjs } = await import('@open-mercato/telemetry/nextjs')
339
+ await registerTelemetryForNextjs()
340
+ }
341
+ }
342
+ `,
343
+ )
344
+
345
+ await runTelemetryInit([])
346
+ const migrated = read('src/instrumentation.ts')
347
+
348
+ assertParses(migrated, 'node-only-instrumentation-migration')
349
+ expect(migrated).toContain("import { isTelemetryBackendEnabled }")
350
+ expect(migrated).toContain("process.env.NEXT_RUNTIME === 'nodejs' && isTelemetryBackendEnabled()")
351
+ expect(migrated).toContain('nodeProcess.exit(1)')
352
+ })
353
+
354
+ it('leaves an unrecognized custom telemetry bootstrap for a manual update', async () => {
355
+ baseFixture(tmpDir)
356
+ fs.writeFileSync(path.join(tmpDir, dispatcherPath), SIMPLE_DISPATCHER)
357
+ const customInstrumentation = `export async function register(): Promise<void> {
358
+ const { registerTelemetryForNextjs } = await import('@open-mercato/telemetry/nextjs')
359
+ if (process.env.NEXT_RUNTIME === 'nodejs') await registerTelemetryForNextjs()
360
+ const nodeProcess = process
361
+ nodeProcess.exit(1)
362
+ }
363
+ `
364
+ fs.writeFileSync(path.join(tmpDir, 'src', 'instrumentation.ts'), customInstrumentation)
365
+
366
+ await runTelemetryInit([])
367
+
368
+ expect(read('src/instrumentation.ts')).toBe(customInstrumentation)
369
+ expect(logSpy.mock.calls.flat().join('\n')).toContain('manual step for src/instrumentation.ts')
370
+ })
371
+
283
372
  it('is idempotent — a second run changes nothing and does not double-insert', async () => {
284
373
  baseFixture(tmpDir)
285
374
  fs.writeFileSync(path.join(tmpDir, dispatcherPath), SIMPLE_DISPATCHER)
@@ -7,6 +7,7 @@ import {
7
7
  resolveGeneratedMigrationPath,
8
8
  dbGenerate,
9
9
  dbGreenfield,
10
+ warnAboutMissingEncryptionMapBackfills,
10
11
  } from '../commands'
11
12
  import { MetadataStorage } from '@mikro-orm/core'
12
13
  import fs from 'node:fs'
@@ -399,3 +400,45 @@ describe('dbGenerate metadata isolation (issue #1911)', () => {
399
400
  expect(Object.keys(MetadataStorage.getMetadata())).toHaveLength(0)
400
401
  })
401
402
  })
403
+
404
+ describe('warnAboutMissingEncryptionMapBackfills', () => {
405
+ const tempDirs: string[] = []
406
+
407
+ afterEach(() => {
408
+ for (const dir of tempDirs.splice(0)) {
409
+ try { fs.rmSync(dir, { recursive: true, force: true }) } catch {}
410
+ }
411
+ jest.restoreAllMocks()
412
+ })
413
+
414
+ function createResolver(dir: string): PackageResolver {
415
+ return {
416
+ isMonorepo: () => true,
417
+ getRootDir: () => dir,
418
+ getAppDir: () => dir,
419
+ getOutputDir: () => dir,
420
+ getModulesConfigPath: () => path.join(dir, 'modules.ts'),
421
+ discoverPackages: () => [],
422
+ loadEnabledModules: () => [{ id: 'demo', from: '@app' }],
423
+ getModulePaths: () => ({ appBase: dir, pkgBase: dir }),
424
+ getModuleImportBase: () => ({ appBase: '@/modules/demo', pkgBase: '@open-mercato/core/modules/demo' }),
425
+ getPackageOutputDir: () => dir,
426
+ getPackageRoot: () => dir,
427
+ }
428
+ }
429
+
430
+ function createModule(): string {
431
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mercato-enc-'))
432
+ tempDirs.push(dir)
433
+ return dir
434
+ }
435
+
436
+ it('stays silent without declared encryption maps', async () => {
437
+ const warn = jest.spyOn(console, 'warn').mockImplementation()
438
+ const resolver = createResolver(createModule())
439
+
440
+ await warnAboutMissingEncryptionMapBackfills(resolver.loadEnabledModules(), resolver)
441
+
442
+ expect(warn).not.toHaveBeenCalled()
443
+ })
444
+ })
@@ -0,0 +1,36 @@
1
+ import path from 'node:path'
2
+ import { pathToFileURL } from 'node:url'
3
+ import { toImportableModuleSpecifier } from '../commands'
4
+
5
+ describe('toImportableModuleSpecifier (#6238)', () => {
6
+ it('converts cwd-relative node_modules migration paths to file: URLs', () => {
7
+ const relative = path.join('node_modules', '@open-mercato', 'core', 'dist', 'modules', 'customers', 'migrations', 'Migration20250101000000.js')
8
+ const result = toImportableModuleSpecifier(relative)
9
+
10
+ expect(result.startsWith('file:')).toBe(true)
11
+ expect(result).toBe(pathToFileURL(path.resolve(relative)).href)
12
+ })
13
+
14
+ it('converts absolute filesystem paths to file: URLs on every platform', () => {
15
+ const absolute = path.resolve('migrations', 'Migration20250101000000.js')
16
+ expect(toImportableModuleSpecifier(absolute)).toBe(pathToFileURL(absolute).href)
17
+ })
18
+
19
+ it('converts ./ and ../ relative paths to file: URLs', () => {
20
+ expect(toImportableModuleSpecifier('./Migration.js')).toBe(pathToFileURL(path.resolve('./Migration.js')).href)
21
+ expect(toImportableModuleSpecifier('../migrations/Migration.js')).toBe(
22
+ pathToFileURL(path.resolve('../migrations/Migration.js')).href,
23
+ )
24
+ })
25
+
26
+ it('leaves bare package names and package subpaths alone', () => {
27
+ expect(toImportableModuleSpecifier('zod')).toBe('zod')
28
+ expect(toImportableModuleSpecifier('@mikro-orm/core')).toBe('@mikro-orm/core')
29
+ expect(toImportableModuleSpecifier('next/server')).toBe('next/server')
30
+ })
31
+
32
+ it('leaves existing file: and node: URLs alone', () => {
33
+ expect(toImportableModuleSpecifier('file:///tmp/Migration.js')).toBe('file:///tmp/Migration.js')
34
+ expect(toImportableModuleSpecifier('node:fs')).toBe('node:fs')
35
+ })
36
+ })