@open-mercato/cli 0.10.1-develop.7994.1.c9f31972aa → 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 (110) 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 +4 -0
  87. package/dist/agentic/guides/upstream/manifest.json +2 -2
  88. package/dist/i18n/de.json +4 -0
  89. package/dist/i18n/en.json +4 -0
  90. package/dist/i18n/es.json +4 -0
  91. package/dist/i18n/ko.json +4 -0
  92. package/dist/i18n/pl.json +4 -0
  93. package/dist/lib/__fixtures__/module-search/built-in.json +235 -0
  94. package/dist/lib/__fixtures__/module-search/catalog-parity.json +4076 -0
  95. package/dist/lib/__fixtures__/module-search/modules-index.json +1980 -0
  96. package/dist/lib/__fixtures__/official-module-package/package.json +33 -0
  97. package/dist/lib/init-secrets.js +33 -1
  98. package/dist/lib/init-secrets.js.map +2 -2
  99. package/dist/mercato.js +3 -14
  100. package/dist/mercato.js.map +2 -2
  101. package/package.json +7 -7
  102. package/src/__tests__/mercato.test.ts +57 -0
  103. package/src/i18n/de.json +4 -0
  104. package/src/i18n/en.json +4 -0
  105. package/src/i18n/es.json +4 -0
  106. package/src/i18n/ko.json +4 -0
  107. package/src/i18n/pl.json +4 -0
  108. package/src/lib/__tests__/init-secrets.test.ts +85 -1
  109. package/src/lib/init-secrets.ts +37 -0
  110. 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.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.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.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.7994.1.c9f31972aa"
78
+ "@open-mercato/shared": "0.10.1-develop.8004.1.a8a79ee43f"
79
79
  },
80
80
  "devDependencies": {
81
- "@open-mercato/shared": "0.10.1-develop.7994.1.c9f31972aa",
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()
@@ -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
+ })
@@ -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
+ }
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 = [