@open-mercato/cli 0.10.1-develop.7787.1.96c7cfc66f → 0.10.1-develop.7792.1.b0ce6289cf

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 (92) hide show
  1. package/dist/agentic/guides/module-facts.json +162 -162
  2. package/dist/agentic/guides/module-facts.v2.json +162 -162
  3. package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
  4. package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
  5. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  6. package/dist/agentic/guides/modules/api_keys/index.md +1 -1
  7. package/dist/agentic/guides/modules/attachments/index.md +1 -1
  8. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  9. package/dist/agentic/guides/modules/auth/index.md +1 -1
  10. package/dist/agentic/guides/modules/availability/index.md +1 -1
  11. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  12. package/dist/agentic/guides/modules/carrier_inpost/index.md +1 -1
  13. package/dist/agentic/guides/modules/cart/index.md +1 -1
  14. package/dist/agentic/guides/modules/catalog/index.md +1 -1
  15. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  16. package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
  17. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  18. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  19. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  20. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  21. package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
  22. package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
  23. package/dist/agentic/guides/modules/channel_smtp/index.md +1 -1
  24. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  25. package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
  26. package/dist/agentic/guides/modules/configs/index.md +1 -1
  27. package/dist/agentic/guides/modules/content/index.md +1 -1
  28. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  29. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  30. package/dist/agentic/guides/modules/customer_groups/index.md +1 -1
  31. package/dist/agentic/guides/modules/customers/index.md +1 -1
  32. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  33. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  34. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  35. package/dist/agentic/guides/modules/devices/index.md +1 -1
  36. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  37. package/dist/agentic/guides/modules/directory/index.md +1 -1
  38. package/dist/agentic/guides/modules/documents/index.md +1 -1
  39. package/dist/agentic/guides/modules/ecommerce/index.md +1 -1
  40. package/dist/agentic/guides/modules/entities/index.md +1 -1
  41. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  42. package/dist/agentic/guides/modules/events/index.md +1 -1
  43. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  44. package/dist/agentic/guides/modules/forms/index.md +1 -1
  45. package/dist/agentic/guides/modules/gateway_autopay/index.md +1 -1
  46. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  47. package/dist/agentic/guides/modules/gateway_tpay/index.md +1 -1
  48. package/dist/agentic/guides/modules/generators/index.md +1 -1
  49. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  50. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  51. package/dist/agentic/guides/modules/messages/index.md +1 -1
  52. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  53. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  54. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  55. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  56. package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
  57. package/dist/agentic/guides/modules/planner/index.md +1 -1
  58. package/dist/agentic/guides/modules/portal/index.md +1 -1
  59. package/dist/agentic/guides/modules/progress/index.md +1 -1
  60. package/dist/agentic/guides/modules/promotions/index.md +1 -1
  61. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  62. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  63. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  64. package/dist/agentic/guides/modules/resources/index.md +1 -1
  65. package/dist/agentic/guides/modules/sales/index.md +1 -1
  66. package/dist/agentic/guides/modules/scheduler/index.md +1 -1
  67. package/dist/agentic/guides/modules/search/index.md +1 -1
  68. package/dist/agentic/guides/modules/security/index.md +1 -1
  69. package/dist/agentic/guides/modules/seeds/index.md +1 -1
  70. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  71. package/dist/agentic/guides/modules/sso/index.md +1 -1
  72. package/dist/agentic/guides/modules/staff/index.md +1 -1
  73. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  74. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  75. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  76. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  77. package/dist/agentic/guides/modules/telemetry/index.md +1 -1
  78. package/dist/agentic/guides/modules/tillio/index.md +1 -1
  79. package/dist/agentic/guides/modules/translations/index.md +1 -1
  80. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  81. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  82. package/dist/agentic/guides/modules/wms/index.md +1 -1
  83. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  84. package/dist/agentic/guides/reference-module-facts.json +1 -1
  85. package/dist/agentic/guides/testing-debugging.md +2 -2
  86. package/dist/agentic/guides/upstream/manifest.json +1 -1
  87. package/dist/mercato.js +33 -7
  88. package/dist/mercato.js.map +3 -3
  89. package/package.json +7 -7
  90. package/src/__tests__/mercato.test.ts +87 -0
  91. package/src/lib/generators/__tests__/module-package-sources.test.ts +7 -4
  92. package/src/mercato.ts +35 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.10.1-develop.7787.1.96c7cfc66f",
3
+ "version": "0.10.1-develop.7792.1.b0ce6289cf",
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.7787.1.96c7cfc66f",
64
- "@open-mercato/queue": "0.10.1-develop.7787.1.96c7cfc66f",
65
- "@open-mercato/shared": "0.10.1-develop.7787.1.96c7cfc66f",
66
- "@open-mercato/telemetry": "0.10.1-develop.7787.1.96c7cfc66f",
63
+ "@open-mercato/events": "0.10.1-develop.7792.1.b0ce6289cf",
64
+ "@open-mercato/queue": "0.10.1-develop.7792.1.b0ce6289cf",
65
+ "@open-mercato/shared": "0.10.1-develop.7792.1.b0ce6289cf",
66
+ "@open-mercato/telemetry": "0.10.1-develop.7792.1.b0ce6289cf",
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.7787.1.96c7cfc66f"
78
+ "@open-mercato/shared": "0.10.1-develop.7792.1.b0ce6289cf"
79
79
  },
80
80
  "devDependencies": {
81
- "@open-mercato/shared": "0.10.1-develop.7787.1.96c7cfc66f",
81
+ "@open-mercato/shared": "0.10.1-develop.7792.1.b0ce6289cf",
82
82
  "@types/jest": "^30.0.0",
83
83
  "jest": "^30.4.2",
84
84
  "ts-jest": "^29.4.12"
@@ -189,6 +189,93 @@ describe('padByCodePointWidth', () => {
189
189
  })
190
190
  })
191
191
 
192
+ describe('module command argument logging', () => {
193
+ beforeEach(() => {
194
+ jest.restoreAllMocks()
195
+ })
196
+
197
+ it('redacts legacy encryption keys before logging module command arguments', async () => {
198
+ const rotateEncryptionKey = jest.fn().mockResolvedValue(undefined)
199
+ registerCliModules([
200
+ {
201
+ id: 'entities',
202
+ cli: [{ command: 'rotate-encryption-key', run: rotateEncryptionKey }],
203
+ } as Module,
204
+ ])
205
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
206
+
207
+ expect(await run([
208
+ 'node',
209
+ 'mercato',
210
+ 'entities',
211
+ 'rotate-encryption-key',
212
+ '--old-key',
213
+ 'argument-secret',
214
+ ])).toBe(0)
215
+ expect(await run([
216
+ 'node',
217
+ 'mercato',
218
+ 'entities',
219
+ 'rotate-encryption-key',
220
+ '--oldKey=inline-secret',
221
+ ])).toBe(0)
222
+
223
+ const output = consoleLogSpy.mock.calls.flat().map(String).join('\n')
224
+ expect(output).not.toContain('argument-secret')
225
+ expect(output).not.toContain('inline-secret')
226
+ expect(output).toContain('--old-key ****')
227
+ expect(output).toContain('--oldKey=****')
228
+ expect(rotateEncryptionKey).toHaveBeenCalledTimes(2)
229
+ consoleLogSpy.mockRestore()
230
+ })
231
+
232
+ // The echoed command line lands in terminal scrollback and CI logs, so every option that
233
+ // carries a credential has to be covered — not just the one the encryption work introduced.
234
+ it('redacts passwords and API keys passed to any module command', async () => {
235
+ const setup = jest.fn().mockResolvedValue(undefined)
236
+ const chat = jest.fn().mockResolvedValue(undefined)
237
+ registerCliModules([
238
+ { id: 'auth', cli: [{ command: 'setup', run: setup }] } as Module,
239
+ { id: 'ai_assistant', cli: [{ command: 'chat', run: chat }] } as Module,
240
+ ])
241
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
242
+
243
+ expect(await run(['node', 'mercato', 'auth', 'setup', '--password', 'hunter2-secret'])).toBe(0)
244
+ expect(await run(['node', 'mercato', 'ai_assistant', 'chat', '--api-key', 'sk-live-secret'])).toBe(0)
245
+ expect(await run(['node', 'mercato', 'ai_assistant', 'chat', '--apiKey=sk-inline-secret'])).toBe(0)
246
+
247
+ const output = consoleLogSpy.mock.calls.flat().map(String).join('\n')
248
+ expect(output).not.toContain('hunter2-secret')
249
+ expect(output).not.toContain('sk-live-secret')
250
+ expect(output).not.toContain('sk-inline-secret')
251
+ expect(output).toContain('--password ****')
252
+ expect(output).toContain('--api-key ****')
253
+ expect(output).toContain('--apiKey=****')
254
+ consoleLogSpy.mockRestore()
255
+ })
256
+
257
+ // `--key` is key material in `seeds encrypt|decrypt|load` and a custom-field key in
258
+ // `entities add-field`, so the redaction has to be per-command rather than global.
259
+ it('redacts --key only for the commands where it carries key material', async () => {
260
+ const encrypt = jest.fn().mockResolvedValue(undefined)
261
+ const addField = jest.fn().mockResolvedValue(undefined)
262
+ registerCliModules([
263
+ { id: 'seeds', cli: [{ command: 'encrypt', run: encrypt }] } as Module,
264
+ { id: 'entities', cli: [{ command: 'add-field', run: addField }] } as Module,
265
+ ])
266
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation()
267
+
268
+ expect(await run(['node', 'mercato', 'seeds', 'encrypt', '--key', 'base64-seed-key'])).toBe(0)
269
+ expect(await run(['node', 'mercato', 'entities', 'add-field', '--key', 'priority_level'])).toBe(0)
270
+
271
+ const output = consoleLogSpy.mock.calls.flat().map(String).join('\n')
272
+ expect(output).not.toContain('base64-seed-key')
273
+ expect(output).toContain('--key ****')
274
+ expect(output).toContain('--key priority_level')
275
+ consoleLogSpy.mockRestore()
276
+ })
277
+ })
278
+
192
279
  describe('--help never triggers side effects (issue #5581)', () => {
193
280
  beforeEach(() => {
194
281
  jest.restoreAllMocks()
@@ -89,6 +89,8 @@ describe('generateModulePackageSources', () => {
89
89
  const pinnedTime = new Date('2020-01-01T00:00:00.000Z')
90
90
  fs.utimesSync(outFile, pinnedTime, pinnedTime)
91
91
  fs.utimesSync(checksumFile, pinnedTime, pinnedTime)
92
+ const outputMtimeBefore = fs.statSync(outFile).mtimeMs
93
+ const checksumMtimeBefore = fs.statSync(checksumFile).mtimeMs
92
94
 
93
95
  fs.writeFileSync(path.join(packageRoot, 'src', 'unrelated-runtime.ts'), 'export const unrelated = true\n')
94
96
  const result = await generateModulePackageSources({ resolver, quiet: true })
@@ -96,8 +98,8 @@ describe('generateModulePackageSources', () => {
96
98
  expect(result.filesWritten).toEqual([])
97
99
  expect(result.filesUnchanged).toEqual([outFile])
98
100
  expect(fs.readFileSync(outFile, 'utf8')).toBe(outputBefore)
99
- expect(fs.statSync(outFile).mtimeMs).toBe(pinnedTime.getTime())
100
- expect(fs.statSync(checksumFile).mtimeMs).toBe(pinnedTime.getTime())
101
+ expect(fs.statSync(outFile).mtimeMs).toBe(outputMtimeBefore)
102
+ expect(fs.statSync(checksumFile).mtimeMs).toBe(checksumMtimeBefore)
101
103
  expect(readChecksumRecord(checksumFile)).toEqual(checksumBefore)
102
104
  })
103
105
 
@@ -123,12 +125,13 @@ describe('generateModulePackageSources', () => {
123
125
  const initialStructure = readChecksumRecord(checksumFile)?.structure
124
126
  const pinnedTime = new Date('2020-01-01T00:00:00.000Z')
125
127
  fs.utimesSync(outFile, pinnedTime, pinnedTime)
128
+ const outputMtimeBefore = fs.statSync(outFile).mtimeMs
126
129
 
127
130
  await generateModulePackageSources({ resolver: alternateResolver, quiet: true })
128
131
  const alternateStructure = readChecksumRecord(checksumFile)?.structure
129
132
  expect(alternateStructure).not.toBe(initialStructure)
130
133
  expect(fs.readFileSync(outFile, 'utf8')).toBe(outputBefore)
131
- expect(fs.statSync(outFile).mtimeMs).toBe(pinnedTime.getTime())
134
+ expect(fs.statSync(outFile).mtimeMs).toBe(outputMtimeBefore)
132
135
 
133
136
  const packageJsonPath = path.join(packageRoot, 'package.json')
134
137
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as Record<string, unknown>
@@ -140,7 +143,7 @@ describe('generateModulePackageSources', () => {
140
143
  expect(result.filesWritten).toEqual([])
141
144
  expect(result.filesUnchanged).toEqual([outFile])
142
145
  expect(fs.readFileSync(outFile, 'utf8')).toBe(outputBefore)
143
- expect(fs.statSync(outFile).mtimeMs).toBe(pinnedTime.getTime())
146
+ expect(fs.statSync(outFile).mtimeMs).toBe(outputMtimeBefore)
144
147
  })
145
148
 
146
149
  it('keeps official package validation as the source inclusion gate', async () => {
package/src/mercato.ts CHANGED
@@ -232,6 +232,39 @@ function formatCliFailureMessage(modName: string, cmdName: string, error: unknow
232
232
  return fallbackMessage
233
233
  }
234
234
 
235
+ // Options that carry key material or credentials in every command that accepts them, so the
236
+ // echoed command line must never reproduce their values: argv is world-readable through
237
+ // `ps`/`/proc`, and this line additionally lands in terminal scrollback and CI logs.
238
+ const SENSITIVE_MODULE_CLI_OPTIONS = new Set([
239
+ '--old-key',
240
+ '--oldKey',
241
+ '--password',
242
+ '--api-key',
243
+ '--apiKey',
244
+ ])
245
+
246
+ // `--key` is ambiguous: it is a base64 encryption key in `seeds encrypt|decrypt|load` but a
247
+ // custom-field key in `entities add-field`, so it is redacted only where it means a secret.
248
+ const SENSITIVE_MODULE_CLI_OPTIONS_BY_COMMAND = new Map<string, string[]>([
249
+ ['seeds:encrypt', ['--key']],
250
+ ['seeds:decrypt', ['--key']],
251
+ ['seeds:load', ['--key']],
252
+ ])
253
+
254
+ function redactSensitiveModuleCliArgs(args: string[], commandKey: string): string[] {
255
+ const sensitive = new Set(SENSITIVE_MODULE_CLI_OPTIONS)
256
+ for (const option of SENSITIVE_MODULE_CLI_OPTIONS_BY_COMMAND.get(commandKey) ?? []) sensitive.add(option)
257
+ return args.map((argument, index) => {
258
+ const separatorIndex = argument.indexOf('=')
259
+ if (separatorIndex > 0) {
260
+ const option = argument.slice(0, separatorIndex)
261
+ if (sensitive.has(option)) return `${option}=****`
262
+ }
263
+ if (index > 0 && sensitive.has(args[index - 1]!)) return '****'
264
+ return argument
265
+ })
266
+ }
267
+
235
268
  function formatInitFailureMessage(error: unknown): string {
236
269
  const fallbackMessage = getFallbackErrorMessage(error)
237
270
  const databaseIssue = detectDatabaseConnectionIssue(error)
@@ -2810,9 +2843,10 @@ export async function run(argv = process.argv) {
2810
2843
 
2811
2844
  console.log('')
2812
2845
  const started = Date.now()
2813
- const loggedArgs = modName === 'deploy' && cmdName === 'railway'
2846
+ const commandArgs = modName === 'deploy' && cmdName === 'railway'
2814
2847
  ? (await import('./lib/deploy/railway/options')).redactRailwayCliArgs(rest)
2815
2848
  : rest
2849
+ const loggedArgs = redactSensitiveModuleCliArgs(commandArgs, `${modName}:${cmdName}`)
2816
2850
  console.log(`🚀 Running ${modName}:${cmdName} ${loggedArgs.join(' ')}`)
2817
2851
  try {
2818
2852
  const commandExitCode = await runWithCapturedExitCode(async () => {