@open-mercato/cli 0.10.1-develop.7994.1.c9f31972aa → 0.10.1-develop.8022.1.b6753de892

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 (125) hide show
  1. package/build.mjs +1 -0
  2. package/dist/agentic/guides/module-facts.json +162 -162
  3. package/dist/agentic/guides/module-facts.v2.json +162 -162
  4. package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
  5. package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
  6. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  7. package/dist/agentic/guides/modules/api_keys/index.md +1 -1
  8. package/dist/agentic/guides/modules/attachments/index.md +1 -1
  9. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  10. package/dist/agentic/guides/modules/auth/index.md +1 -1
  11. package/dist/agentic/guides/modules/availability/index.md +1 -1
  12. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  13. package/dist/agentic/guides/modules/carrier_inpost/index.md +1 -1
  14. package/dist/agentic/guides/modules/cart/index.md +1 -1
  15. package/dist/agentic/guides/modules/catalog/index.md +1 -1
  16. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  17. package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
  18. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  19. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  20. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  21. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  22. package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
  23. package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
  24. package/dist/agentic/guides/modules/channel_smtp/index.md +1 -1
  25. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  26. package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
  27. package/dist/agentic/guides/modules/configs/index.md +1 -1
  28. package/dist/agentic/guides/modules/content/index.md +1 -1
  29. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  30. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  31. package/dist/agentic/guides/modules/customer_groups/index.md +1 -1
  32. package/dist/agentic/guides/modules/customers/index.md +1 -1
  33. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  34. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  35. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  36. package/dist/agentic/guides/modules/devices/index.md +1 -1
  37. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  38. package/dist/agentic/guides/modules/directory/index.md +1 -1
  39. package/dist/agentic/guides/modules/documents/index.md +1 -1
  40. package/dist/agentic/guides/modules/ecommerce/index.md +1 -1
  41. package/dist/agentic/guides/modules/entities/index.md +1 -1
  42. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  43. package/dist/agentic/guides/modules/events/index.md +1 -1
  44. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  45. package/dist/agentic/guides/modules/forms/index.md +1 -1
  46. package/dist/agentic/guides/modules/gateway_autopay/index.md +1 -1
  47. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  48. package/dist/agentic/guides/modules/gateway_tpay/index.md +1 -1
  49. package/dist/agentic/guides/modules/generators/index.md +1 -1
  50. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  51. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  52. package/dist/agentic/guides/modules/messages/index.md +1 -1
  53. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  54. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  55. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  56. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  57. package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
  58. package/dist/agentic/guides/modules/planner/index.md +1 -1
  59. package/dist/agentic/guides/modules/portal/index.md +1 -1
  60. package/dist/agentic/guides/modules/progress/index.md +1 -1
  61. package/dist/agentic/guides/modules/promotions/index.md +1 -1
  62. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  63. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  64. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  65. package/dist/agentic/guides/modules/resources/index.md +1 -1
  66. package/dist/agentic/guides/modules/sales/index.md +1 -1
  67. package/dist/agentic/guides/modules/scheduler/index.md +1 -1
  68. package/dist/agentic/guides/modules/search/index.md +1 -1
  69. package/dist/agentic/guides/modules/security/index.md +1 -1
  70. package/dist/agentic/guides/modules/seeds/index.md +1 -1
  71. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  72. package/dist/agentic/guides/modules/sso/index.md +1 -1
  73. package/dist/agentic/guides/modules/staff/index.md +1 -1
  74. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  75. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  76. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  77. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  78. package/dist/agentic/guides/modules/telemetry/index.md +1 -1
  79. package/dist/agentic/guides/modules/tillio/index.md +1 -1
  80. package/dist/agentic/guides/modules/translations/index.md +1 -1
  81. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  82. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  83. package/dist/agentic/guides/modules/wms/index.md +1 -1
  84. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  85. package/dist/agentic/guides/reference-module-facts.json +1 -1
  86. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +8 -0
  87. package/dist/agentic/guides/upstream/manifest.json +2 -2
  88. package/dist/agentic/shared/ai/harness/cases.json +2 -2
  89. package/dist/agentic/shared/ai/trackers/github.md +257 -2
  90. package/dist/agentic/shared/scripts/execution-sandbox.mjs +1 -0
  91. package/dist/i18n/de.json +4 -0
  92. package/dist/i18n/en.json +4 -0
  93. package/dist/i18n/es.json +4 -0
  94. package/dist/i18n/ko.json +4 -0
  95. package/dist/i18n/pl.json +4 -0
  96. package/dist/lib/__fixtures__/module-search/built-in.json +235 -0
  97. package/dist/lib/__fixtures__/module-search/catalog-parity.json +4076 -0
  98. package/dist/lib/__fixtures__/module-search/modules-index.json +1980 -0
  99. package/dist/lib/__fixtures__/official-module-package/package.json +33 -0
  100. package/dist/lib/generators/openapi.js +7 -2
  101. package/dist/lib/generators/openapi.js.map +2 -2
  102. package/dist/lib/init-secrets.js +33 -1
  103. package/dist/lib/init-secrets.js.map +2 -2
  104. package/dist/lib/telemetry-init.js +1 -1
  105. package/dist/lib/telemetry-init.js.map +1 -1
  106. package/dist/lib/testing/integration.js +22 -0
  107. package/dist/lib/testing/integration.js.map +2 -2
  108. package/dist/mercato.js +3 -14
  109. package/dist/mercato.js.map +2 -2
  110. package/package.json +7 -7
  111. package/src/__tests__/mercato.test.ts +57 -0
  112. package/src/i18n/de.json +4 -0
  113. package/src/i18n/en.json +4 -0
  114. package/src/i18n/es.json +4 -0
  115. package/src/i18n/ko.json +4 -0
  116. package/src/i18n/pl.json +4 -0
  117. package/src/lib/__tests__/init-secrets.test.ts +85 -1
  118. package/src/lib/__tests__/telemetry-init.test.ts +1 -1
  119. package/src/lib/generators/__tests__/openapi.test.ts +24 -0
  120. package/src/lib/generators/openapi.ts +7 -2
  121. package/src/lib/init-secrets.ts +37 -0
  122. package/src/lib/telemetry-init.ts +1 -1
  123. package/src/lib/testing/__tests__/integration.test.ts +24 -0
  124. package/src/lib/testing/integration.ts +23 -0
  125. package/src/mercato.ts +4 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.10.1-develop.7994.1.c9f31972aa",
3
+ "version": "0.10.1-develop.8022.1.b6753de892",
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.7994.1.c9f31972aa",
64
- "@open-mercato/queue": "0.10.1-develop.7994.1.c9f31972aa",
65
- "@open-mercato/shared": "0.10.1-develop.7994.1.c9f31972aa",
66
- "@open-mercato/telemetry": "0.10.1-develop.7994.1.c9f31972aa",
63
+ "@open-mercato/events": "0.10.1-develop.8022.1.b6753de892",
64
+ "@open-mercato/queue": "0.10.1-develop.8022.1.b6753de892",
65
+ "@open-mercato/shared": "0.10.1-develop.8022.1.b6753de892",
66
+ "@open-mercato/telemetry": "0.10.1-develop.8022.1.b6753de892",
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.7994.1.c9f31972aa"
78
+ "@open-mercato/shared": "0.10.1-develop.8022.1.b6753de892"
79
79
  },
80
80
  "devDependencies": {
81
- "@open-mercato/shared": "0.10.1-develop.7994.1.c9f31972aa",
81
+ "@open-mercato/shared": "0.10.1-develop.8022.1.b6753de892",
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()
@@ -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()
@@ -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
+ }
@@ -1,4 +1,6 @@
1
- import { resolveInitDerivedSecrets } from '../init-secrets'
1
+ import { readFileSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { resolveInitDerivedSecrets, resolveInitSuperadminCredentials } from '../init-secrets'
2
4
 
3
5
  describe('resolveInitDerivedSecrets', () => {
4
6
  it("seeds the well-known 'secret' password in non-production when no env overrides are set", () => {
@@ -77,3 +79,85 @@ describe('resolveInitDerivedSecrets', () => {
77
79
  })
78
80
  })
79
81
 
82
+
83
+ describe('production bootstrap superadmin credentials', () => {
84
+ const production = { NODE_ENV: 'production' }
85
+
86
+ it.each([undefined, '', ' ', 'secret', 'password', ' PASSWORD ', 'changeme', 'change-me', 'replace_me'])(
87
+ 'rejects an absent or placeholder production password: %s', (password) => {
88
+ expect(() => resolveInitSuperadminCredentials([], {
89
+ ...production, OM_INIT_SUPERADMIN_PASSWORD: password,
90
+ })).toThrow('requires a unique superadmin password')
91
+ },
92
+ )
93
+
94
+ it.each(['--skip-password-policy', '--skip-password-policy=true', '--allow-weak-password', '--allow-weak-password=true'])(
95
+ 'enforces production policy despite %s', (flag) => {
96
+ expect(() => resolveInitSuperadminCredentials([flag, '--password=weak'], production))
97
+ .toThrow('does not meet the configured password policy')
98
+ expect(resolveInitSuperadminCredentials([flag, '--password=Unique-Owner-937!'], production).skipPasswordPolicy).toBe(false)
99
+ },
100
+ )
101
+
102
+ it('uses the configured policy and never includes the password in errors', () => {
103
+ const password = 'Unique-Owner-937!'
104
+ expect(() => resolveInitSuperadminCredentials([`--password=${password}`], {
105
+ ...production, OM_PASSWORD_MIN_LENGTH: '40',
106
+ })).toThrow('does not meet the configured password policy')
107
+ try {
108
+ resolveInitSuperadminCredentials([`--password=${password}`], { ...production, OM_PASSWORD_MIN_LENGTH: '40' })
109
+ } catch (error) {
110
+ expect(String(error)).not.toContain(password)
111
+ }
112
+ })
113
+
114
+ it('rejects placeholders even with a relaxed policy', () => {
115
+ expect(() => resolveInitSuperadminCredentials([], {
116
+ ...production, OM_INIT_SUPERADMIN_PASSWORD: 'password', OM_PASSWORD_REQUIRE_DIGIT: 'false',
117
+ OM_PASSWORD_REQUIRE_UPPERCASE: 'false', OM_PASSWORD_REQUIRE_SPECIAL: 'false',
118
+ })).toThrow('requires a unique superadmin password')
119
+ })
120
+
121
+ it('prefers command arguments over environment overrides', () => {
122
+ expect(resolveInitSuperadminCredentials(['--email=owner@example.test', '--password=Unique-Owner-937!'], {
123
+ NODE_ENV: ' Production ', OM_INIT_SUPERADMIN_PASSWORD: 'password', OM_INIT_SUPERADMIN_EMAIL: 'other@example.test',
124
+ })).toEqual({ email: 'owner@example.test', password: 'Unique-Owner-937!', skipPasswordPolicy: false })
125
+ })
126
+
127
+ it('uses the CLI locale for operator-facing production diagnostics', () => {
128
+ expect(() => resolveInitSuperadminCredentials([], { ...production, LC_ALL: 'pl_PL.UTF-8' }))
129
+ .toThrow('Inicjalizacja produkcyjna wymaga')
130
+ expect(() => resolveInitSuperadminCredentials([], { ...production, LC_ALL: 'unknown' }))
131
+ .toThrow('Production initialization requires')
132
+ })
133
+
134
+ it('accepts a policy-compliant environment password', () => {
135
+ expect(resolveInitSuperadminCredentials([], {
136
+ ...production, OM_INIT_SUPERADMIN_PASSWORD: 'Unique-Owner-937!',
137
+ }).password).toBe('Unique-Owner-937!')
138
+ })
139
+
140
+ it.each([undefined, 'development', 'test', 'staging'])('preserves non-production defaults for %s', (nodeEnv) => {
141
+ expect(resolveInitSuperadminCredentials([], { NODE_ENV: nodeEnv })).toEqual({
142
+ email: 'superadmin@acme.com', password: 'secret', skipPasswordPolicy: true,
143
+ })
144
+ expect(resolveInitSuperadminCredentials([], { NODE_ENV: nodeEnv, OM_INIT_SUPERADMIN_PASSWORD: 'password' }).password).toBe('password')
145
+ expect(resolveInitSuperadminCredentials(['--skip-password-policy=false'], { NODE_ENV: nodeEnv }).skipPasswordPolicy).toBe(false)
146
+ })
147
+ })
148
+
149
+ describe('full-app Compose bootstrap defaults', () => {
150
+ it.each(['starters/docker/compose.fullapp.yml', 'packages/create-app/template/docker-compose.fullapp.yml'])(
151
+ '%s cannot bootstrap predictable production credentials', (composePath) => {
152
+ const compose = readFileSync(path.resolve(__dirname, '../../../../..', composePath), 'utf8')
153
+ const fallback = compose.match(/OM_INIT_SUPERADMIN_PASSWORD: \$\{OM_INIT_SUPERADMIN_PASSWORD:-(.*?)\}/)?.[1]
154
+ expect(fallback).toBeDefined()
155
+ expect(() => resolveInitSuperadminCredentials([], {
156
+ NODE_ENV: 'production', OM_INIT_SUPERADMIN_PASSWORD: fallback,
157
+ })).toThrow('requires a unique superadmin password')
158
+ expect(resolveInitSuperadminCredentials([], {
159
+ NODE_ENV: 'development', OM_INIT_SUPERADMIN_PASSWORD: fallback,
160
+ }).password).toBe(fallback)
161
+ },
162
+ )
163
+ })
@@ -164,7 +164,7 @@ describe('mercato telemetry init', () => {
164
164
 
165
165
  const pkg = JSON.parse(read('package.json'))
166
166
  expect(pkg.dependencies['@open-mercato/telemetry']).toBe('1.2.3')
167
- expect(pkg.optionalDependencies['bullmq-otel']).toBe('^1.3.1')
167
+ expect(pkg.optionalDependencies['bullmq-otel']).toBe('^2.0.1')
168
168
 
169
169
  expect(read('.env.example')).toContain('TELEMETRY_BACKEND')
170
170
  expect(read('.env.example')).toContain('OTEL_EXPORTER_OTLP_ENDPOINT')
@@ -160,6 +160,30 @@ export function buildOpenApiDocument(modules: any[], options: any) {
160
160
  expect(openApiDoc.paths['/api/demo/details']?.get?.summary).toBe('package details')
161
161
  })
162
162
 
163
+ it('bundles into a per-run file and leaves no bundle behind', async () => {
164
+ touchFile(
165
+ path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'demo', 'api', 'health', 'route.ts'),
166
+ [
167
+ 'export async function GET() { return new Response("pkg-health") }',
168
+ 'export const openApi = {',
169
+ " GET: { summary: 'package health' }",
170
+ '}',
171
+ '',
172
+ ].join('\n'),
173
+ )
174
+ const cacheDir = path.join(tmpDir, 'node_modules', '.cache')
175
+ touchFile(path.join(cacheDir, '_openapi-bundle.mjs'), 'export {}\n')
176
+
177
+ const result = await generateOpenApi({ resolver: createMockResolver([{ id: 'demo', from: '@open-mercato/core' }]), quiet: true })
178
+
179
+ expect(result.errors).toEqual([])
180
+ const openApiDoc = JSON.parse(
181
+ fs.readFileSync(path.join(tmpDir, 'output', 'generated', 'openapi.generated.json'), 'utf8'),
182
+ ) as { paths: Record<string, { get?: { summary?: string } }> }
183
+ expect(openApiDoc.paths['/api/demo/health']?.get?.summary).toBe('package health')
184
+ expect(fs.readdirSync(cacheDir).filter((name) => name.startsWith('_openapi-bundle'))).toEqual([])
185
+ })
186
+
163
187
  it('skips route discovery and bundling when all tracked inputs are unchanged', async () => {
164
188
  touchFile(
165
189
  path.join(tmpDir, 'packages', 'core', 'src', 'modules', 'demo', 'api', 'health', 'route.ts'),
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import * as fs from 'node:fs'
10
+ import { randomBytes } from 'node:crypto'
10
11
  import * as path from 'node:path'
11
12
  import type { ModuleEntry, PackageResolver } from '../resolver'
12
13
  import { getEnabledModuleIdentities } from '../module-identity'
@@ -340,7 +341,10 @@ async function generateOpenApiViaBundle(
340
341
  const cacheDir = path.join(rootDir, 'node_modules', '.cache')
341
342
  if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true })
342
343
 
343
- const bundlePath = path.join(cacheDir, '_openapi-bundle.mjs')
344
+ // Per-run file: `yarn dev`'s watcher and a CLI `generate` (e.g. after
345
+ // create-mercato-module init) can bundle concurrently, and a shared path let
346
+ // one process execute the other's half-written bundle ("Unexpected end of input").
347
+ const bundlePath = path.join(cacheDir, `_openapi-bundle-${process.pid}-${randomBytes(6).toString('hex')}.mjs`)
344
348
  let inputPaths: string[] = []
345
349
 
346
350
  // Build the entry script that imports all routes and calls buildOpenApiDocument
@@ -607,7 +611,8 @@ process.stdout.write(JSON.stringify(deepClone(doc), (_, v) =>
607
611
  // process exit when a plugin request is still in flight.
608
612
  try { await esbuild.stop() } catch {}
609
613
  // Clean up old files from previous tsx-based approach
610
- for (const file of ['_openapi-register.mjs', '_openapi-loader.mjs', '_next-stub.cjs']) {
614
+ try { fs.unlinkSync(bundlePath) } catch {}
615
+ for (const file of ['_openapi-bundle.mjs', '_openapi-register.mjs', '_openapi-loader.mjs', '_next-stub.cjs']) {
611
616
  try { fs.unlinkSync(path.join(cacheDir, file)) } catch {}
612
617
  }
613
618
  }
@@ -1,5 +1,17 @@
1
1
  import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
2
+ import { getPasswordPolicy, validatePassword } from '@open-mercato/shared/lib/auth/passwordPolicy'
2
3
  import { randomBytes } from 'node:crypto'
4
+ import { createTranslator } from '@open-mercato/shared/lib/i18n/translate'
5
+ import { createRequire } from 'node:module'
6
+
7
+ const requireLocale = createRequire(import.meta.url)
8
+
9
+ function initTranslator(env: NodeJS.ProcessEnv) {
10
+ const locale = (env.LC_ALL || env.LC_MESSAGES || env.LANG || 'en').split(/[_.-]/)[0].toLowerCase()
11
+ const supportedLocale = ['en', 'pl', 'es', 'de', 'ko'].includes(locale) ? locale : 'en'
12
+ const dictionary: Record<string, string> = requireLocale(`../i18n/${supportedLocale}.json`)
13
+ return createTranslator(dictionary)
14
+ }
3
15
 
4
16
  type EnvReader = (key: string) => string | undefined
5
17
 
@@ -71,3 +83,28 @@ export function resolveInitDerivedSecrets(options: {
71
83
  employeePassword: resolvePassword('OM_INIT_EMPLOYEE_PASSWORD', employeeEmail),
72
84
  }
73
85
  }
86
+
87
+ export function resolveInitSuperadminCredentials(args: string[], env: NodeJS.ProcessEnv = process.env) {
88
+ const argValue = (name: string) => args.find((arg) => arg.startsWith(`${name}=`))?.slice(name.length + 1) || undefined
89
+ const email = argValue('--email') ?? readEnvValue(env, 'OM_INIT_SUPERADMIN_EMAIL') ?? 'superadmin@acme.com'
90
+ const suppliedPassword = argValue('--password') ?? readEnvValue(env, 'OM_INIT_SUPERADMIN_PASSWORD')
91
+ const isProduction = (env.NODE_ENV ?? '').trim().toLowerCase() === 'production'
92
+ const skipPolicyArg = args.find((arg) =>
93
+ arg === '--skip-password-policy' || arg.startsWith('--skip-password-policy=') ||
94
+ arg === '--allow-weak-password' || arg.startsWith('--allow-weak-password='),
95
+ )
96
+ const skipPasswordPolicy = !isProduction && (skipPolicyArg
97
+ ? parseBooleanToken(skipPolicyArg.split('=')[1] ?? 'true') ?? true
98
+ : true)
99
+ if (isProduction) {
100
+ const t = initTranslator(env)
101
+ const placeholder = suppliedPassword?.trim().toLowerCase()
102
+ if (!suppliedPassword || !placeholder || ['secret', 'password', 'changeme', 'change-me', 'change_me', 'replace-me', 'replace_me'].includes(placeholder)) {
103
+ throw new Error(t('cli.init.errors.superadminPasswordRequired'))
104
+ }
105
+ if (!validatePassword(suppliedPassword, getPasswordPolicy(env)).ok) {
106
+ throw new Error(t('cli.init.errors.superadminPasswordPolicy'))
107
+ }
108
+ }
109
+ return { email, password: suppliedPassword ?? 'secret', skipPasswordPolicy }
110
+ }
@@ -23,7 +23,7 @@ import { dirname, join, resolve } from 'node:path'
23
23
  * `@open-mercato/cli`'s telemetry dependency once the app updates its packages.
24
24
  */
25
25
 
26
- const BULLMQ_OTEL_VERSION = '^1.3.1'
26
+ const BULLMQ_OTEL_VERSION = '^2.0.1'
27
27
 
28
28
  type StepStatus = 'created' | 'patched' | 'skipped' | 'manual'
29
29
 
@@ -17,6 +17,7 @@ import {
17
17
  resolveAppReadyTimeoutMs,
18
18
  resolveEphemeralPostgresImage,
19
19
  resolveEphemeralJwtSecret,
20
+ resolveEphemeralSuperadminPassword,
20
21
  ephemeralPostgresInitSql,
21
22
  shouldReuseBuildArtifacts,
22
23
  acquireEphemeralRuntimeLock,
@@ -30,6 +31,7 @@ import {
30
31
  } from '../integration'
31
32
  import type { CapturedOutputProcess, ShutdownProcessRef } from '../integration'
32
33
  import { resolveSpawnCommand } from '../../spawn'
34
+ import { resolveInitSuperadminCredentials } from '../../init-secrets'
33
35
  import { EventEmitter } from 'node:events'
34
36
  import { spawn } from 'node:child_process'
35
37
  import type { ChildProcess } from 'node:child_process'
@@ -510,6 +512,28 @@ describe('integration cache and options', () => {
510
512
  )
511
513
  })
512
514
 
515
+ it('supplies a production-valid superadmin password when the inherited one is missing or a placeholder', () => {
516
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined)
517
+ try {
518
+ const fallback = resolveEphemeralSuperadminPassword({})
519
+ expect(fallback).toBe('Om-Ephemeral-Superadmin-2026!')
520
+ expect(() =>
521
+ resolveInitSuperadminCredentials([], { NODE_ENV: 'production', OM_INIT_SUPERADMIN_PASSWORD: fallback }),
522
+ ).not.toThrow()
523
+ expect(warnSpy).not.toHaveBeenCalled()
524
+ expect(resolveEphemeralSuperadminPassword({ OM_INIT_SUPERADMIN_PASSWORD: 'secret' })).toBe(fallback)
525
+ expect(warnSpy).toHaveBeenCalledTimes(1)
526
+ } finally {
527
+ warnSpy.mockRestore()
528
+ }
529
+ })
530
+
531
+ it('keeps a production-valid operator-provided superadmin password', () => {
532
+ expect(resolveEphemeralSuperadminPassword({ OM_INIT_SUPERADMIN_PASSWORD: 'Operator-Chosen-Pass-42!' })).toBe(
533
+ 'Operator-Chosen-Pass-42!',
534
+ )
535
+ })
536
+
513
537
  it('creates the vector and pgcrypto extensions in the ephemeral init SQL', () => {
514
538
  const sql = ephemeralPostgresInitSql()
515
539
  expect(sql).toContain('CREATE EXTENSION IF NOT EXISTS vector')
@@ -11,6 +11,7 @@ import spawn from 'cross-spawn'
11
11
  import { fetchWithTimeout, type FetchWithTimeoutInit } from '@open-mercato/shared/lib/http/fetchWithTimeout'
12
12
  import { isUnsafeJwtSecret } from '@open-mercato/shared/lib/auth/jwt'
13
13
  import { resolveEnvironment } from '../resolver'
14
+ import { resolveInitSuperadminCredentials } from '../init-secrets'
14
15
  import { resolveSpawnCommand } from '../spawn'
15
16
  import { discoverIntegrationSpecFiles as discoverIntegrationSpecFilesShared } from './integration-discovery'
16
17
  import { resolveDockerHostFromContext, runCommandAndCapture } from './runtime-utils'
@@ -202,6 +203,23 @@ export function resolveEphemeralJwtSecret(env: NodeJS.ProcessEnv = process.env):
202
203
  }
203
204
  return DEFAULT_EPHEMERAL_JWT_SECRET
204
205
  }
206
+ const DEFAULT_EPHEMERAL_SUPERADMIN_PASSWORD = 'Om-Ephemeral-Superadmin-2026!'
207
+
208
+ export function resolveEphemeralSuperadminPassword(env: NodeJS.ProcessEnv = process.env): string {
209
+ const inherited = env.OM_INIT_SUPERADMIN_PASSWORD
210
+ if (inherited !== undefined && inherited.trim().length > 0) {
211
+ try {
212
+ resolveInitSuperadminCredentials([], { ...env, NODE_ENV: 'production', OM_INIT_SUPERADMIN_PASSWORD: inherited })
213
+ return inherited
214
+ } catch {
215
+ console.warn(
216
+ '[integration] Ignoring the inherited OM_INIT_SUPERADMIN_PASSWORD: it is a placeholder or fails the password policy and the ephemeral app initializes with NODE_ENV=production. Using the ephemeral default instead.',
217
+ )
218
+ }
219
+ }
220
+ return DEFAULT_EPHEMERAL_SUPERADMIN_PASSWORD
221
+ }
222
+
205
223
  const PLAYWRIGHT_ENV_UNAVAILABLE_PATTERNS: RegExp[] = [
206
224
  /net::ERR_CONNECTION_REFUSED/i,
207
225
  /Failed to connect to .* (localhost|127\.0\.0\.1)/i,
@@ -2197,6 +2215,7 @@ function buildReusableEnvironment(
2197
2215
  // OM_INIT_*_PASSWORD to fix the seeded credential.
2198
2216
  OM_INIT_ADMIN_PASSWORD: process.env.OM_INIT_ADMIN_PASSWORD ?? 'secret',
2199
2217
  OM_INIT_EMPLOYEE_PASSWORD: process.env.OM_INIT_EMPLOYEE_PASSWORD ?? 'secret',
2218
+ OM_INIT_SUPERADMIN_PASSWORD: resolveEphemeralSuperadminPassword(),
2200
2219
  OM_INTEGRATION_TEST: 'true',
2201
2220
  OM_ENABLE_ENTERPRISE_MODULES: enterpriseModulesFlag,
2202
2221
  OM_ENABLE_ENTERPRISE_MODULES_SSO: process.env.OM_ENABLE_ENTERPRISE_MODULES_SSO ?? enterpriseModulesFlag,
@@ -3585,6 +3604,10 @@ export async function startEphemeralEnvironment(options: EphemeralRuntimeOptions
3585
3604
  // NODE_ENV=production.
3586
3605
  OM_INIT_ADMIN_PASSWORD: process.env.OM_INIT_ADMIN_PASSWORD ?? 'secret',
3587
3606
  OM_INIT_EMPLOYEE_PASSWORD: process.env.OM_INIT_EMPLOYEE_PASSWORD ?? 'secret',
3607
+ // Production init refuses a missing or placeholder superadmin password
3608
+ // (#8896). Integration auth helpers read OM_INIT_SUPERADMIN_PASSWORD, so
3609
+ // the Playwright runner logs in with the same value.
3610
+ OM_INIT_SUPERADMIN_PASSWORD: resolveEphemeralSuperadminPassword(),
3588
3611
  // Pool sizing for the ephemeral integration runtime. Defaults were once
3589
3612
  // very aggressive (max=5, idle=1000) which exposed flaky 'timeout exceeded
3590
3613
  // when trying to connect' errors on `progressService.createJob`-backed
package/src/mercato.ts CHANGED
@@ -10,7 +10,7 @@ import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
10
10
  import { getSslConfig } from '@open-mercato/shared/lib/db/ssl'
11
11
  import { getRedisUrl, getRedisUrlOrThrow, parseRedisUrl } from '@open-mercato/shared/lib/redis/connection'
12
12
  import { formatInitSummary } from './lib/init-summary'
13
- import { resolveInitDerivedSecrets } from './lib/init-secrets'
13
+ import { resolveInitDerivedSecrets, resolveInitSuperadminCredentials } from './lib/init-secrets'
14
14
  import { decideExistingDatabase, formatSkipIfInitializedMessage } from './lib/init-existing-database'
15
15
  import {
16
16
  resolveAutoSpawnWorkersMode,
@@ -1032,6 +1032,7 @@ export async function run(argv = process.argv) {
1032
1032
  delete process.env.OM_INIT_REINSTALL
1033
1033
  }
1034
1034
  const skipIfInitialized = initArgs.includes('--skip-if-initialized')
1035
+ const credentials = skipIfInitialized && !reinstall ? undefined : resolveInitSuperadminCredentials(initArgs)
1035
1036
  const skipExamples = initArgs.includes('--no-examples')
1036
1037
  const skipDemoUsers = initArgs.includes('--no-demo-users')
1037
1038
  const stressTestEnabled =
@@ -1209,6 +1210,8 @@ export async function run(argv = process.argv) {
1209
1210
  }
1210
1211
  }
1211
1212
 
1213
+ const { email, password, skipPasswordPolicy } = credentials ?? resolveInitSuperadminCredentials(initArgs)
1214
+
1212
1215
  // Step 1: Run generators directly (no process spawn)
1213
1216
  console.log('🔧 Preparing modules (registry, entities, DI)...')
1214
1217
  const { createResolver } = await import('./lib/resolver')
@@ -1257,16 +1260,7 @@ export async function run(argv = process.argv) {
1257
1260
  }
1258
1261
  return fallback
1259
1262
  }
1260
- const readEnvDefault = (key: string) => {
1261
- const value = process.env[key]
1262
- if (typeof value === 'string' && value.trim().length > 0) return value.trim()
1263
- return undefined
1264
- }
1265
- const defaultEmail = readEnvDefault('OM_INIT_SUPERADMIN_EMAIL') ?? 'superadmin@acme.com'
1266
- const defaultPassword = readEnvDefault('OM_INIT_SUPERADMIN_PASSWORD') ?? 'secret'
1267
1263
  const orgName = findArgValue(['--org=', '--orgName='], 'Acme Corp')
1268
- const email = findArgValue(['--email='], defaultEmail)
1269
- const password = findArgValue(['--password='], defaultPassword)
1270
1264
  const derivedSecrets = resolveInitDerivedSecrets({ email, env: process.env })
1271
1265
  const adminEmailDerived = derivedSecrets.adminEmail
1272
1266
  const employeeEmailDerived = derivedSecrets.employeeEmail
@@ -1300,16 +1294,6 @@ export async function run(argv = process.argv) {
1300
1294
  process.env.OM_INIT_EMPLOYEE_EMAIL = employeeEmailDerived
1301
1295
  }
1302
1296
  const roles = findArgValue(['--roles='], 'superadmin,admin,employee')
1303
- const skipPasswordPolicyRaw = initArgs.find((arg) =>
1304
- arg === '--skip-password-policy' ||
1305
- arg.startsWith('--skip-password-policy=') ||
1306
- arg === '--allow-weak-password' ||
1307
- arg.startsWith('--allow-weak-password=')
1308
- )
1309
- const skipPasswordPolicy = skipPasswordPolicyRaw
1310
- ? parseBooleanToken(skipPasswordPolicyRaw.split('=')[1] ?? 'true') ?? true
1311
- : true
1312
-
1313
1297
  console.log('🔐 Setting up RBAC and users...')
1314
1298
  // Run auth setup command via CLI
1315
1299
  const setupArgs = [