@open-mercato/cli 0.10.1-develop.7990.1.82d0a183c5 → 0.10.1-develop.8004.1.a8a79ee43f

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 (182) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/build.mjs +1 -0
  3. package/dist/agentic/guides/module-facts.json +162 -162
  4. package/dist/agentic/guides/module-facts.v2.json +162 -162
  5. package/dist/agentic/guides/module-tool.md +21 -0
  6. package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
  7. package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
  8. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  9. package/dist/agentic/guides/modules/api_keys/index.md +1 -1
  10. package/dist/agentic/guides/modules/attachments/index.md +1 -1
  11. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  12. package/dist/agentic/guides/modules/auth/index.md +1 -1
  13. package/dist/agentic/guides/modules/availability/index.md +1 -1
  14. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  15. package/dist/agentic/guides/modules/carrier_inpost/index.md +1 -1
  16. package/dist/agentic/guides/modules/cart/index.md +1 -1
  17. package/dist/agentic/guides/modules/catalog/index.md +1 -1
  18. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  19. package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
  20. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  21. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  22. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  23. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  24. package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
  25. package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
  26. package/dist/agentic/guides/modules/channel_smtp/index.md +1 -1
  27. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  28. package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
  29. package/dist/agentic/guides/modules/configs/index.md +1 -1
  30. package/dist/agentic/guides/modules/content/index.md +1 -1
  31. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  32. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  33. package/dist/agentic/guides/modules/customer_groups/index.md +1 -1
  34. package/dist/agentic/guides/modules/customers/index.md +1 -1
  35. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  36. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  37. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  38. package/dist/agentic/guides/modules/devices/index.md +1 -1
  39. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  40. package/dist/agentic/guides/modules/directory/index.md +1 -1
  41. package/dist/agentic/guides/modules/documents/index.md +1 -1
  42. package/dist/agentic/guides/modules/ecommerce/index.md +1 -1
  43. package/dist/agentic/guides/modules/entities/index.md +1 -1
  44. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  45. package/dist/agentic/guides/modules/events/index.md +1 -1
  46. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  47. package/dist/agentic/guides/modules/forms/index.md +1 -1
  48. package/dist/agentic/guides/modules/gateway_autopay/index.md +1 -1
  49. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  50. package/dist/agentic/guides/modules/gateway_tpay/index.md +1 -1
  51. package/dist/agentic/guides/modules/generators/index.md +1 -1
  52. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  53. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  54. package/dist/agentic/guides/modules/messages/index.md +1 -1
  55. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  56. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  57. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  58. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  59. package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
  60. package/dist/agentic/guides/modules/planner/index.md +1 -1
  61. package/dist/agentic/guides/modules/portal/index.md +1 -1
  62. package/dist/agentic/guides/modules/progress/index.md +1 -1
  63. package/dist/agentic/guides/modules/promotions/index.md +1 -1
  64. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  65. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  66. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  67. package/dist/agentic/guides/modules/resources/index.md +1 -1
  68. package/dist/agentic/guides/modules/sales/index.md +1 -1
  69. package/dist/agentic/guides/modules/scheduler/index.md +1 -1
  70. package/dist/agentic/guides/modules/search/index.md +1 -1
  71. package/dist/agentic/guides/modules/security/index.md +1 -1
  72. package/dist/agentic/guides/modules/seeds/index.md +1 -1
  73. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  74. package/dist/agentic/guides/modules/sso/index.md +1 -1
  75. package/dist/agentic/guides/modules/staff/index.md +1 -1
  76. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  77. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  78. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  79. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  80. package/dist/agentic/guides/modules/telemetry/index.md +1 -1
  81. package/dist/agentic/guides/modules/tillio/index.md +1 -1
  82. package/dist/agentic/guides/modules/translations/index.md +1 -1
  83. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  84. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  85. package/dist/agentic/guides/modules/wms/index.md +1 -1
  86. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  87. package/dist/agentic/guides/reference-module-facts.json +1 -1
  88. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +4 -0
  89. package/dist/agentic/guides/upstream/manifest.json +2 -2
  90. package/dist/agentic/shared/ai/harness/cases.json +23 -9
  91. package/dist/agentic/shared/ai/harness/source-link-inventory.json +90 -4
  92. package/dist/i18n/de.json +4 -0
  93. package/dist/i18n/en.json +4 -0
  94. package/dist/i18n/es.json +4 -0
  95. package/dist/i18n/ko.json +4 -0
  96. package/dist/i18n/pl.json +4 -0
  97. package/dist/lib/__fixtures__/module-search/built-in.json +235 -0
  98. package/dist/lib/__fixtures__/module-search/catalog-parity.json +4076 -0
  99. package/dist/lib/__fixtures__/module-search/modules-index.json +1980 -0
  100. package/dist/lib/__fixtures__/official-module-package/package.json +33 -0
  101. package/dist/lib/__integration__/TC-INT-007.spec.js +207 -19
  102. package/dist/lib/__integration__/TC-INT-007.spec.js.map +2 -2
  103. package/dist/lib/__integration__/TC-INT-009.spec.js +267 -0
  104. package/dist/lib/__integration__/TC-INT-009.spec.js.map +7 -0
  105. package/dist/lib/eject.js +25 -9
  106. package/dist/lib/eject.js.map +2 -2
  107. package/dist/lib/generators/entity-ids.js +2 -0
  108. package/dist/lib/generators/entity-ids.js.map +2 -2
  109. package/dist/lib/generators/module-di.js +2 -0
  110. package/dist/lib/generators/module-di.js.map +2 -2
  111. package/dist/lib/generators/module-entities.js +2 -0
  112. package/dist/lib/generators/module-entities.js.map +2 -2
  113. package/dist/lib/generators/module-package-sources.js +6 -4
  114. package/dist/lib/generators/module-package-sources.js.map +2 -2
  115. package/dist/lib/generators/module-registry.js +32 -86
  116. package/dist/lib/generators/module-registry.js.map +2 -2
  117. package/dist/lib/generators/openapi.js +2 -0
  118. package/dist/lib/generators/openapi.js.map +2 -2
  119. package/dist/lib/generators/source-exports.js +73 -0
  120. package/dist/lib/generators/source-exports.js.map +7 -0
  121. package/dist/lib/init-secrets.js +33 -1
  122. package/dist/lib/init-secrets.js.map +2 -2
  123. package/dist/lib/init-summary.js +44 -0
  124. package/dist/lib/init-summary.js.map +7 -0
  125. package/dist/lib/module-github.js +98 -0
  126. package/dist/lib/module-github.js.map +7 -0
  127. package/dist/lib/module-identity.js +131 -0
  128. package/dist/lib/module-identity.js.map +7 -0
  129. package/dist/lib/module-inspection.js +124 -0
  130. package/dist/lib/module-inspection.js.map +7 -0
  131. package/dist/lib/module-install.js +150 -21
  132. package/dist/lib/module-install.js.map +3 -3
  133. package/dist/lib/module-package.js +205 -15
  134. package/dist/lib/module-package.js.map +2 -2
  135. package/dist/lib/modules-config.js +69 -30
  136. package/dist/lib/modules-config.js.map +2 -2
  137. package/dist/lib/resolver.js +33 -34
  138. package/dist/lib/resolver.js.map +2 -2
  139. package/dist/mercato.js +17 -37
  140. package/dist/mercato.js.map +2 -2
  141. package/package.json +7 -7
  142. package/src/__tests__/mercato.test.ts +71 -4
  143. package/src/i18n/de.json +4 -0
  144. package/src/i18n/en.json +4 -0
  145. package/src/i18n/es.json +4 -0
  146. package/src/i18n/ko.json +4 -0
  147. package/src/i18n/pl.json +4 -0
  148. package/src/lib/__fixtures__/official-module-package/dist/modules/test_package/widgets/injection/test/widget.js +1 -0
  149. package/src/lib/__integration__/TC-INT-007.spec.ts +219 -19
  150. package/src/lib/__integration__/TC-INT-009.spec.ts +279 -0
  151. package/src/lib/__tests__/eject.test.ts +98 -4
  152. package/src/lib/__tests__/init-secrets.test.ts +85 -1
  153. package/src/lib/__tests__/init-summary.test.ts +50 -0
  154. package/src/lib/__tests__/module-github.test.ts +108 -0
  155. package/src/lib/__tests__/module-identity.test.ts +144 -0
  156. package/src/lib/__tests__/module-inspection.test.ts +195 -0
  157. package/src/lib/__tests__/module-install.test.ts +399 -10
  158. package/src/lib/__tests__/module-package.test.ts +154 -0
  159. package/src/lib/__tests__/modules-config.test.ts +55 -0
  160. package/src/lib/__tests__/resolver.packages.test.ts +29 -0
  161. package/src/lib/eject.ts +28 -10
  162. package/src/lib/generators/__tests__/module-id-collisions.test.ts +146 -0
  163. package/src/lib/generators/__tests__/module-package-sources.test.ts +19 -2
  164. package/src/lib/generators/__tests__/module-requirements.test.ts +56 -0
  165. package/src/lib/generators/entity-ids.ts +2 -0
  166. package/src/lib/generators/module-di.ts +2 -0
  167. package/src/lib/generators/module-entities.ts +2 -0
  168. package/src/lib/generators/module-package-sources.ts +7 -5
  169. package/src/lib/generators/module-registry.ts +33 -115
  170. package/src/lib/generators/openapi.ts +3 -0
  171. package/src/lib/generators/source-exports.ts +93 -0
  172. package/src/lib/init-secrets.ts +37 -0
  173. package/src/lib/init-summary.ts +54 -0
  174. package/src/lib/module-github.ts +109 -0
  175. package/src/lib/module-identity.ts +145 -0
  176. package/src/lib/module-inspection.ts +133 -0
  177. package/src/lib/module-install.ts +170 -22
  178. package/src/lib/module-package.ts +243 -16
  179. package/src/lib/modules-config.ts +80 -32
  180. package/src/lib/resolver.ts +35 -39
  181. package/src/mercato.ts +18 -44
  182. package/src/types/cross-spawn.d.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.10.1-develop.7990.1.82d0a183c5",
3
+ "version": "0.10.1-develop.8004.1.a8a79ee43f",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,10 +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/events": "0.10.1-develop.7990.1.82d0a183c5",
64
- "@open-mercato/queue": "0.10.1-develop.7990.1.82d0a183c5",
65
- "@open-mercato/shared": "0.10.1-develop.7990.1.82d0a183c5",
66
- "@open-mercato/telemetry": "0.10.1-develop.7990.1.82d0a183c5",
63
+ "@open-mercato/events": "0.10.1-develop.8004.1.a8a79ee43f",
64
+ "@open-mercato/queue": "0.10.1-develop.8004.1.a8a79ee43f",
65
+ "@open-mercato/shared": "0.10.1-develop.8004.1.a8a79ee43f",
66
+ "@open-mercato/telemetry": "0.10.1-develop.8004.1.a8a79ee43f",
67
67
  "cross-spawn": "^7.0.6",
68
68
  "minisearch": "7.2.0",
69
69
  "pg": "8.23.0",
@@ -75,10 +75,10 @@
75
75
  "typescript-js": "npm:typescript@6.0.3"
76
76
  },
77
77
  "peerDependencies": {
78
- "@open-mercato/shared": "0.10.1-develop.7990.1.82d0a183c5"
78
+ "@open-mercato/shared": "0.10.1-develop.8004.1.a8a79ee43f"
79
79
  },
80
80
  "devDependencies": {
81
- "@open-mercato/shared": "0.10.1-develop.7990.1.82d0a183c5",
81
+ "@open-mercato/shared": "0.10.1-develop.8004.1.a8a79ee43f",
82
82
  "@types/jest": "^30.0.0",
83
83
  "jest": "^30.4.2",
84
84
  "ts-jest": "^29.4.12"
@@ -1112,6 +1112,47 @@ describe('init command failure output', () => {
1112
1112
  )
1113
1113
  }
1114
1114
 
1115
+ it.each(['--reinstall', '--no-demo-users'])('rejects unsafe production init before side effects with %s', async (flag) => {
1116
+ const previousEnv = { ...process.env }
1117
+ jest.spyOn(console, 'error').mockImplementation()
1118
+ jest.spyOn(console, 'log').mockImplementation()
1119
+ const authSetup = jest.fn().mockResolvedValue(undefined)
1120
+ mockInitDependencies({ authSetup })
1121
+ process.env.NODE_ENV = 'production'
1122
+ process.env.OM_INIT_SUPERADMIN_PASSWORD = 'password'
1123
+ try {
1124
+ const mercato = await import('../mercato')
1125
+ expect(await mercato.run(['node', 'mercato', 'init', flag, '--skip-password-policy'])).toBe(1)
1126
+ const { Client } = await import('pg')
1127
+ const { generateEntityIds } = await import('../lib/generators')
1128
+ const { dbMigrate } = await import('../lib/db')
1129
+ expect(Client).not.toHaveBeenCalled()
1130
+ expect(generateEntityIds).not.toHaveBeenCalled()
1131
+ expect(dbMigrate).not.toHaveBeenCalled()
1132
+ expect(authSetup).not.toHaveBeenCalled()
1133
+ } finally {
1134
+ process.env = previousEnv
1135
+ }
1136
+ })
1137
+
1138
+ it('passes valid production credentials to auth setup without bypassing policy', async () => {
1139
+ const previousEnv = { ...process.env }
1140
+ jest.spyOn(console, 'error').mockImplementation()
1141
+ jest.spyOn(console, 'log').mockImplementation()
1142
+ const authSetup = jest.fn().mockResolvedValue(undefined)
1143
+ mockInitDependencies({ authSetup })
1144
+ process.env.NODE_ENV = 'production'
1145
+ process.env.OM_INIT_SUPERADMIN_PASSWORD = 'Unique-Owner-937!'
1146
+ try {
1147
+ const mercato = await import('../mercato')
1148
+ expect(await mercato.run(['node', 'mercato', 'init', '--no-demo-users', '--skip-password-policy'])).toBe(0)
1149
+ expect(authSetup).toHaveBeenCalledWith(expect.arrayContaining(['--password', 'Unique-Owner-937!']))
1150
+ expect(authSetup.mock.calls[0][0]).not.toContain('--skip-password-policy')
1151
+ } finally {
1152
+ process.env = previousEnv
1153
+ }
1154
+ })
1155
+
1115
1156
  it('passes --include-demo-users to auth setup by default', async () => {
1116
1157
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
1117
1158
  const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
@@ -1125,8 +1166,8 @@ describe('init command failure output', () => {
1125
1166
  expect(authSetup).toHaveBeenCalledWith(expect.arrayContaining(['--include-demo-users']))
1126
1167
  expect(consoleLogSpy).toHaveBeenCalledWith('👤 Demo accounts (admin@/employee@): enabled')
1127
1168
  const loggedLines = consoleLogSpy.mock.calls.map((args) => args.join(' '))
1128
- expect(loggedLines.some((line) => line.includes('🧰 Admin:'))).toBe(true)
1129
- expect(loggedLines.some((line) => line.includes('👷 Employee:'))).toBe(true)
1169
+ expect(loggedLines.some((line) => line.includes('🧰 Admin'))).toBe(true)
1170
+ expect(loggedLines.some((line) => line.includes('👷 Employee'))).toBe(true)
1130
1171
 
1131
1172
  consoleErrorSpy.mockRestore()
1132
1173
  consoleLogSpy.mockRestore()
@@ -1148,8 +1189,8 @@ describe('init command failure output', () => {
1148
1189
  '👤 Demo accounts (admin@/employee@): skipped (--no-demo-users)',
1149
1190
  )
1150
1191
  const loggedLines = consoleLogSpy.mock.calls.map((args) => args.join(' '))
1151
- expect(loggedLines.some((line) => line.includes('🧰 Admin:'))).toBe(false)
1152
- expect(loggedLines.some((line) => line.includes('👷 Employee:'))).toBe(false)
1192
+ expect(loggedLines.some((line) => line.includes('🧰 Admin'))).toBe(false)
1193
+ expect(loggedLines.some((line) => line.includes('👷 Employee'))).toBe(false)
1153
1194
 
1154
1195
  consoleErrorSpy.mockRestore()
1155
1196
  consoleLogSpy.mockRestore()
@@ -1336,6 +1377,22 @@ describe('init on an already initialized database', () => {
1336
1377
  consoleLogSpy.mockRestore()
1337
1378
  })
1338
1379
 
1380
+ it('skips an initialized production database without bootstrap credentials', async () => {
1381
+ const previousEnv = { ...process.env }
1382
+ jest.spyOn(console, 'error').mockImplementation()
1383
+ jest.spyOn(console, 'log').mockImplementation()
1384
+ const { generateEntityIds } = mockSeededDatabase('3')
1385
+ process.env.NODE_ENV = 'production'
1386
+ delete process.env.OM_INIT_SUPERADMIN_PASSWORD
1387
+ try {
1388
+ const mercato = await import('../mercato')
1389
+ expect(await mercato.run(['node', 'mercato', 'init', '--skip-if-initialized'])).toBe(0)
1390
+ expect(generateEntityIds).not.toHaveBeenCalled()
1391
+ } finally {
1392
+ process.env = previousEnv
1393
+ }
1394
+ })
1395
+
1339
1396
  it('keeps the data, names the target database and exits 0 with --skip-if-initialized', async () => {
1340
1397
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
1341
1398
  const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
@@ -1735,6 +1792,11 @@ describe('server dev managed process exits', () => {
1735
1792
  expect(exitCode).toBe(0)
1736
1793
  expect(consoleErrorSpy).not.toHaveBeenCalled()
1737
1794
  expect(consoleLogSpy).toHaveBeenCalledWith('[server] Skipping scheduler auto-start — module not enabled')
1795
+ const { spawn } = await import('child_process')
1796
+ const nextSpawn = (spawn as jest.Mock).mock.calls.find((call) =>
1797
+ pathIncludes(call[1]?.[0] ?? '', 'next/dist/bin/next') && call[1]?.includes('dev'),
1798
+ )
1799
+ expect(nextSpawn?.[2]?.env.NODE_ENV).toBe('development')
1738
1800
 
1739
1801
  consoleErrorSpy.mockRestore()
1740
1802
  consoleLogSpy.mockRestore()
@@ -2477,6 +2539,11 @@ describe('server start managed process exits', () => {
2477
2539
  expect(exitCode).toBe(0)
2478
2540
  expect(consoleErrorSpy).not.toHaveBeenCalled()
2479
2541
  expect(consoleLogSpy).toHaveBeenCalledWith('[server] Skipping scheduler auto-start — module not enabled')
2542
+ const { spawn } = await import('child_process')
2543
+ const nextSpawn = (spawn as jest.Mock).mock.calls.find((call) =>
2544
+ pathIncludes(call[1]?.[0] ?? '', 'next/dist/bin/next') && call[1]?.includes('start'),
2545
+ )
2546
+ expect(nextSpawn?.[2]?.env.NODE_ENV).toBe('production')
2480
2547
 
2481
2548
  consoleErrorSpy.mockRestore()
2482
2549
  consoleLogSpy.mockRestore()
@@ -0,0 +1,4 @@
1
+ {
2
+ "cli.init.errors.superadminPasswordPolicy": "Das Superadmin-Passwort für die Produktion erfüllt die konfigurierte Passwortrichtlinie nicht. Flags zum Umgehen der Passwortrichtlinie gelten nur außerhalb der Produktion.",
3
+ "cli.init.errors.superadminPasswordRequired": "Die Produktionsinitialisierung erfordert ein eindeutiges Superadmin-Passwort über OM_INIT_SUPERADMIN_PASSWORD oder --password; integrierte Platzhalter sind nicht zulässig."
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "cli.init.errors.superadminPasswordPolicy": "Production superadmin password does not meet the configured password policy. Password-policy bypass flags apply only outside production.",
3
+ "cli.init.errors.superadminPasswordRequired": "Production initialization requires a unique superadmin password via OM_INIT_SUPERADMIN_PASSWORD or --password; built-in placeholders are not allowed."
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "cli.init.errors.superadminPasswordPolicy": "La contraseña de superadministrador en producción no cumple la política de contraseñas configurada. Las opciones para omitir la política solo se aplican fuera de producción.",
3
+ "cli.init.errors.superadminPasswordRequired": "La inicialización en producción requiere una contraseña única de superadministrador mediante OM_INIT_SUPERADMIN_PASSWORD o --password; no se permiten los valores predeterminados de ejemplo."
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "cli.init.errors.superadminPasswordPolicy": "프로덕션 슈퍼관리자 비밀번호가 설정된 비밀번호 정책을 충족하지 않습니다. 비밀번호 정책 우회 플래그는 프로덕션 외 환경에서만 적용됩니다.",
3
+ "cli.init.errors.superadminPasswordRequired": "프로덕션 초기화에는 OM_INIT_SUPERADMIN_PASSWORD 또는 --password로 지정한 고유한 슈퍼관리자 비밀번호가 필요합니다. 기본 예시 비밀번호는 사용할 수 없습니다."
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "cli.init.errors.superadminPasswordPolicy": "Hasło superadministratora w środowisku produkcyjnym nie spełnia skonfigurowanej polityki haseł. Flagi pomijające politykę haseł działają tylko poza środowiskiem produkcyjnym.",
3
+ "cli.init.errors.superadminPasswordRequired": "Inicjalizacja produkcyjna wymaga unikalnego hasła superadministratora podanego przez OM_INIT_SUPERADMIN_PASSWORD lub --password; wbudowane hasła przykładowe są niedozwolone."
4
+ }
@@ -22,21 +22,33 @@ function yarnBinary(): string {
22
22
  return process.platform === 'win32' ? 'yarn.cmd' : 'yarn'
23
23
  }
24
24
 
25
- function runCommand(command: string, args: string[], cwd: string): string {
25
+ function runCommand(command: string, args: string[], cwd: string, environment: NodeJS.ProcessEnv = {}): string {
26
26
  const yarnCacheFolder = path.join(cwd, '.yarn', 'cache')
27
- return execFileSync(command, args, {
28
- cwd,
29
- encoding: 'utf8',
30
- env: {
31
- ...process.env,
32
- FORCE_COLOR: '0',
33
- NODE_NO_WARNINGS: '1',
34
- YARN_CACHE_FOLDER: yarnCacheFolder,
35
- YARN_ENABLE_GLOBAL_CACHE: '0',
36
- YARN_ENABLE_IMMUTABLE_INSTALLS: '0',
37
- YARN_NODE_LINKER: 'node-modules',
38
- },
39
- })
27
+ try {
28
+ return execFileSync(command, args, {
29
+ cwd,
30
+ encoding: 'utf8',
31
+ env: {
32
+ ...process.env,
33
+ FORCE_COLOR: '0',
34
+ NODE_NO_WARNINGS: '1',
35
+ YARN_CACHE_FOLDER: yarnCacheFolder,
36
+ YARN_ENABLE_GLOBAL_CACHE: '0',
37
+ YARN_ENABLE_IMMUTABLE_INSTALLS: '0',
38
+ YARN_NODE_LINKER: 'node-modules',
39
+ ...environment,
40
+ },
41
+ })
42
+ } catch (error) {
43
+ if (!error || typeof error !== 'object') throw error
44
+ const errorRecord = error as Record<string, unknown>
45
+ const outputs = ['stdout', 'stderr'].flatMap((streamName) => {
46
+ const output = errorRecord[streamName]
47
+ return typeof output === 'string' || Buffer.isBuffer(output) ? [output.toString().trim()] : []
48
+ }).filter(Boolean)
49
+ const message = error instanceof Error ? error.message : String(error)
50
+ throw new Error(outputs.length > 0 ? `${message}\n${outputs.join('\n')}` : message)
51
+ }
40
52
  }
41
53
 
42
54
  function runMercato(args: string[], cwd: string): string {
@@ -77,6 +89,7 @@ const commentedModulesSource = [
77
89
  ].join('\n')
78
90
 
79
91
  function createMonorepoFixture(rootDir: string): string {
92
+ writeFile(path.join(rootDir, 'yarn.lock'), '')
80
93
  writeFile(
81
94
  path.join(rootDir, 'package.json'),
82
95
  JSON.stringify(
@@ -113,6 +126,7 @@ function createMonorepoFixture(rootDir: string): string {
113
126
  }
114
127
 
115
128
  function createStandaloneFixture(rootDir: string): string {
129
+ writeFile(path.join(rootDir, 'yarn.lock'), '')
116
130
  writeFile(
117
131
  path.join(rootDir, '.yarnrc.yml'),
118
132
  ['nodeLinker: node-modules', 'enableGlobalCache: false', 'cacheFolder: ./.yarn/cache', ''].join('\n'),
@@ -196,14 +210,196 @@ test.describe('TC-INT-007: CLI official module install and eject flows', () => {
196
210
  const modulesSource = readFile(path.join(appDir, 'src', 'modules.ts'))
197
211
  const cssSource = readFile(path.join(appDir, '.mercato', 'generated', 'module-package-sources.css'))
198
212
 
199
- expect(modulesSource).toContain("{ id: 'test_package', from: '@app' }")
213
+ expect(modulesSource).toMatch(/id: 'test_package', from: '@app'/)
214
+ expect(modulesSource).not.toContain('locator:')
215
+ expect(modulesSource).toContain("packageName: '@open-mercato/test-package'")
200
216
  expect(fs.existsSync(path.join(appDir, 'src', 'modules', 'test_package', 'index.ts'))).toBe(true)
217
+ expect(readFile(path.join(appDir, 'src', 'modules', 'test_package', 'index.ts'))).toBe(readFile(path.join(fixtureModulePackage, 'src', 'modules', 'test_package', 'index.ts')))
201
218
  expect(cssSource).toBe('')
202
219
  } finally {
203
220
  fs.rmSync(appDir, { recursive: true, force: true })
204
221
  }
205
222
  })
206
223
 
224
+ test('module add reports dependencies that must be enabled before registration', () => {
225
+ const appDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mercato-cli-standalone-dependencies-'))
226
+
227
+ try {
228
+ createStandaloneFixture(appDir)
229
+ const packageDir = path.join(appDir, 'fixture-module-package')
230
+ fs.cpSync(fixtureModulePackage, packageDir, { recursive: true })
231
+ writeFile(
232
+ path.join(packageDir, 'src', 'modules', 'test_package', 'index.ts'),
233
+ "export const metadata = { id: 'test_package', requires: ['customers'], ejectable: true }\n",
234
+ )
235
+ runCommand(yarnBinary(), ['install'], appDir)
236
+
237
+ expect(() => runMercato(
238
+ ['module', 'add', '@open-mercato/test-package@file:./fixture-module-package'],
239
+ appDir,
240
+ )).toThrow(/requires module\(s\) that are not enabled: customers/)
241
+
242
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toBe('export const enabledModules = []\n')
243
+ } finally {
244
+ fs.rmSync(appDir, { recursive: true, force: true })
245
+ }
246
+ })
247
+
248
+ test('module add resolves canonical requirements from retained ejected origins', () => {
249
+ const appDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mercato-cli-canonical-requires-'))
250
+
251
+ try {
252
+ createStandaloneFixture(appDir)
253
+ const packageDir = path.join(appDir, 'fixture-module-package')
254
+ fs.cpSync(fixtureModulePackage, packageDir, { recursive: true })
255
+ const requirements = [
256
+ '@pkarw/marketing_consents@preview',
257
+ 'npm:@pkarw/marketing_consents@1.0.0',
258
+ 'github:pkarw/marketing-consents',
259
+ 'npm:consent-helper@next',
260
+ 'github:pkarw/consent-tools#main',
261
+ 'pkarw/consent-tools',
262
+ 'https://github.com/pkarw/consent-tools.git#release',
263
+ 'ssh://git@github.com/pkarw/consent-tools.git#main',
264
+ 'git@github.com:pkarw/consent-tools.git',
265
+ 'github:pkarw/installed-tools',
266
+ 'npm:@open-mercato/core@preview',
267
+ ]
268
+ writeFile(
269
+ path.join(packageDir, 'src', 'modules', 'test_package', 'index.ts'),
270
+ `export const metadata = { id: 'test_package', requires: ${JSON.stringify(requirements)}, ejectable: true }\n`,
271
+ )
272
+ const entries = [
273
+ { id: 'marketing_consents', from: '@app', locator: 'github:pkarw/marketing-consents', packageName: '@pkarw/marketing_consents' },
274
+ { id: 'npm_consents', from: '@app', locator: 'npm:@pkarw/marketing_consents', packageName: '@pkarw/marketing_consents' },
275
+ { id: 'npm_core', from: '@app', locator: 'npm:@open-mercato/core', packageName: '@open-mercato/core' },
276
+ { id: 'consent_helper', from: '@app', locator: 'npm:consent-helper' },
277
+ { id: 'consent_tools', from: '@app', locator: 'github:pkarw/consent-tools' },
278
+ ]
279
+ for (const entry of entries) {
280
+ writeFile(
281
+ path.join(appDir, 'src', 'modules', entry.id, 'index.ts'),
282
+ `export const metadata = { id: '${entry.id}' }\n`,
283
+ )
284
+ }
285
+ const corePackageDir = path.join(appDir, 'vendor', 'core')
286
+ writeFile(
287
+ path.join(corePackageDir, 'package.json'),
288
+ JSON.stringify({ name: '@open-mercato/core', version: '0.4.7', type: 'module', repository: { type: 'git', url: 'https://github.com/pkarw/installed-tools.git' } }),
289
+ )
290
+ writeFile(
291
+ path.join(corePackageDir, 'src', 'modules', 'repository_tools', 'index.ts'),
292
+ "export const metadata = { id: 'repository_tools' }\n",
293
+ )
294
+ writeFile(
295
+ path.join(corePackageDir, 'dist', 'modules', 'repository_tools', 'index.js'),
296
+ "export const metadata = { id: 'repository_tools' }\n",
297
+ )
298
+ const enabledEntries = [...entries, { id: 'repository_tools', from: '@open-mercato/core', locator: 'github:pkarw/installed-tools' }]
299
+ writeFile(path.join(appDir, 'src', 'modules.ts'), `export const enabledModules = ${JSON.stringify(enabledEntries)}\n`)
300
+ runCommand(yarnBinary(), ['install'], appDir)
301
+
302
+ runMercato(['module', 'add', 'npm:@open-mercato/test-package@file:./fixture-module-package'], appDir)
303
+
304
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toContain("id: 'test_package'")
305
+ } finally {
306
+ fs.rmSync(appDir, { recursive: true, force: true })
307
+ }
308
+ })
309
+
310
+ test('same-name modules from different origins do not satisfy canonical requirements', () => {
311
+ const appDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mercato-cli-wrong-origin-'))
312
+
313
+ try {
314
+ createStandaloneFixture(appDir)
315
+ const packageDir = path.join(appDir, 'fixture-module-package')
316
+ fs.cpSync(fixtureModulePackage, packageDir, { recursive: true })
317
+ writeFile(
318
+ path.join(packageDir, 'src', 'modules', 'test_package', 'index.ts'),
319
+ "export const metadata = { id: 'test_package', requires: ['@pkarw/marketing_consents', 'github:pkarw/consent-tools'], ejectable: true }\n",
320
+ )
321
+ const modulesSource = `export const enabledModules = [
322
+ { id: 'marketing_consents', from: '@app', locator: 'npm:@open-mercato/marketing_consents' },
323
+ { id: 'consent_tools', from: '@app', locator: 'github:another-owner/consent-tools' },
324
+ ]\n`
325
+ writeFile(path.join(appDir, 'src', 'modules.ts'), modulesSource)
326
+ runCommand(yarnBinary(), ['install'], appDir)
327
+
328
+ expect(() => runMercato(
329
+ ['module', 'add', '@open-mercato/test-package@file:./fixture-module-package', '--eject'],
330
+ appDir,
331
+ )).toThrow(/@pkarw\/marketing_consents[\s\S]*github:pkarw\/consent-tools[\s\S]*npx create-mercato-module install/)
332
+
333
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toBe(modulesSource)
334
+ expect(fs.existsSync(path.join(appDir, 'src', 'modules', 'test_package'))).toBe(false)
335
+ } finally {
336
+ fs.rmSync(appDir, { recursive: true, force: true })
337
+ }
338
+ })
339
+
340
+ test('module add rejects a same-ID source collision before ejecting', () => {
341
+ const appDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mercato-cli-module-collision-'))
342
+
343
+ try {
344
+ createStandaloneFixture(appDir)
345
+ const modulesSource = "export const enabledModules = [{ id: 'test_package', from: '@app', locator: 'github:another-owner/test-package' }]\n"
346
+ const localSource = "export const metadata = { id: 'test_package', locator: 'github:another-owner/test-package' }\n"
347
+ writeFile(path.join(appDir, 'src', 'modules.ts'), modulesSource)
348
+ writeFile(path.join(appDir, 'src', 'modules', 'test_package', 'index.ts'), localSource)
349
+ runCommand(yarnBinary(), ['install'], appDir)
350
+
351
+ expect(() => runMercato(
352
+ ['module', 'add', `@open-mercato/test-package@file:${fixtureModulePackage}`, '--eject'],
353
+ appDir,
354
+ )).toThrow(/(?=[\s\S]*test_package)(?=[\s\S]*(?:collision|already|conflict))(?=[\s\S]*another-owner)(?=[\s\S]*@open-mercato\/test-package)/i)
355
+
356
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toBe(modulesSource)
357
+ expect(readFile(path.join(appDir, 'src', 'modules', 'test_package', 'index.ts'))).toBe(localSource)
358
+ } finally {
359
+ fs.rmSync(appDir, { recursive: true, force: true })
360
+ }
361
+ })
362
+
363
+ test('GitHub locators detect a runtime ID collision before invoking Yarn', () => {
364
+ const appDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mercato-cli-github-collision-'))
365
+ try {
366
+ createStandaloneFixture(appDir)
367
+ const modulesSource = "export const enabledModules = [{ id: 'test_package', from: '@app', locator: 'github:other/test-package' }]\n"
368
+ writeFile(path.join(appDir, 'src', 'modules.ts'), modulesSource)
369
+ const originalManifest = readFile(path.join(appDir, 'package.json'))
370
+ const executableDir = path.join(appDir, 'bin')
371
+ const packageManifest = Buffer.from(JSON.stringify({ name: '@pkarw/test-package' })).toString('base64')
372
+ const ghPath = path.join(executableDir, 'gh')
373
+ writeFile(ghPath, `#!/usr/bin/env node
374
+ const endpoint = process.argv[3]
375
+ const sha = 'a'.repeat(40)
376
+ const value = endpoint === 'user' ? { login: 'pkarw' }
377
+ : endpoint.includes('/commits/') ? { sha }
378
+ : endpoint.includes('/contents/') ? { encoding: 'base64', content: ${JSON.stringify(packageManifest)} }
379
+ : endpoint.includes('/git/trees/') ? { tree: [{ path: 'src/modules/test_package/index.ts' }] }
380
+ : { default_branch: 'main' }
381
+ process.stdout.write(JSON.stringify(value))
382
+ `)
383
+ const yarnPath = path.join(executableDir, 'yarn')
384
+ const marker = path.join(appDir, 'yarn-invoked')
385
+ writeFile(yarnPath, "#!/usr/bin/env node\nrequire('node:fs').writeFileSync(process.env.COLLISION_YARN_MARKER, 'invoked')\nprocess.exit(1)\n")
386
+ fs.chmodSync(ghPath, 0o755)
387
+ fs.chmodSync(yarnPath, 0o755)
388
+ for (const locator of ['github:pkarw/test-package', 'https://github.com/pkarw/test-package.git', 'git@github.com:pkarw/test-package.git', 'github:test-package']) {
389
+ expect(() => runCommand(process.execPath, [cliBin, 'module', 'add', locator, '--allow-third-party'], appDir, {
390
+ PATH: executableDir + path.delimiter + process.env.PATH,
391
+ COLLISION_YARN_MARKER: marker,
392
+ })).toThrow(/Module name collision[\s\S]*test_package[\s\S]*github:other\/test-package[\s\S]*github:pkarw\/test-package/)
393
+ }
394
+ expect(fs.existsSync(marker)).toBe(false)
395
+ expect(readFile(path.join(appDir, 'package.json'))).toBe(originalManifest)
396
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toBe(modulesSource)
397
+ expect(fs.existsSync(path.join(appDir, 'src', 'modules', 'test_package'))).toBe(false)
398
+ } finally {
399
+ fs.rmSync(appDir, { recursive: true, force: true })
400
+ }
401
+ })
402
+
207
403
  test('module enable supports package-backed and ejected flows plus both eject entrypoints', () => {
208
404
  const appDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mercato-cli-standalone-eject-'))
209
405
 
@@ -218,25 +414,29 @@ test.describe('TC-INT-007: CLI official module install and eject flows', () => {
218
414
 
219
415
  runMercato(['module', 'enable', '@open-mercato/test-package'], appDir)
220
416
  expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toContain(
221
- "{ id: 'test_package', from: '@open-mercato/test-package' }",
417
+ "id: 'test_package', from: '@open-mercato/test-package'",
222
418
  )
223
419
 
224
420
  writeFile(path.join(appDir, 'src', 'modules.ts'), 'export const enabledModules = []\n')
225
421
  fs.rmSync(path.join(appDir, 'src', 'modules', 'test_package'), { recursive: true, force: true })
226
422
  runMercato(['module', 'enable', '@open-mercato/test-package', '--eject'], appDir)
227
- expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toContain("{ id: 'test_package', from: '@app' }")
423
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toContain("id: 'test_package', from: '@app'")
228
424
  expect(fs.existsSync(path.join(appDir, 'src', 'modules', 'test_package', 'index.ts'))).toBe(true)
229
425
  expect(readFile(path.join(appDir, '.mercato', 'generated', 'module-package-sources.css'))).toBe('')
230
426
 
231
427
  writeFile(path.join(appDir, 'src', 'modules.ts'), "export const enabledModules = [{ id: 'test_package', from: '@open-mercato/test-package' }]\n")
232
428
  fs.rmSync(path.join(appDir, 'src', 'modules', 'test_package'), { recursive: true, force: true })
233
429
  runMercato(['module', 'eject', 'test_package'], appDir)
234
- expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toContain("{ id: 'test_package', from: '@app' }")
430
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toContain("packageName: '@open-mercato/test-package'")
431
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).not.toContain("locator: 'npm:@open-mercato/test-package'")
432
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toContain("id: 'test_package', from: '@app'")
235
433
 
236
434
  writeFile(path.join(appDir, 'src', 'modules.ts'), "export const enabledModules = [{ id: 'test_package', from: '@open-mercato/test-package' }]\n")
237
435
  fs.rmSync(path.join(appDir, 'src', 'modules', 'test_package'), { recursive: true, force: true })
238
436
  runMercato(['eject', 'test_package'], appDir)
239
- expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toContain("{ id: 'test_package', from: '@app' }")
437
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toContain("packageName: '@open-mercato/test-package'")
438
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).not.toContain("locator: 'npm:@open-mercato/test-package'")
439
+ expect(readFile(path.join(appDir, 'src', 'modules.ts'))).toContain("id: 'test_package', from: '@app'")
240
440
  } finally {
241
441
  fs.rmSync(appDir, { recursive: true, force: true })
242
442
  }