@open-mercato/cli 0.10.1-develop.7787.1.96c7cfc66f → 0.10.1-develop.7793.1.9e0c43fdde
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.
- package/dist/agentic/guides/module-facts.json +162 -162
- package/dist/agentic/guides/module-facts.v2.json +162 -162
- package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
- package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
- package/dist/agentic/guides/modules/api_docs/index.md +1 -1
- package/dist/agentic/guides/modules/api_keys/index.md +1 -1
- package/dist/agentic/guides/modules/attachments/index.md +1 -1
- package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
- package/dist/agentic/guides/modules/auth/index.md +1 -1
- package/dist/agentic/guides/modules/availability/index.md +1 -1
- package/dist/agentic/guides/modules/business_rules/index.md +1 -1
- package/dist/agentic/guides/modules/carrier_inpost/index.md +1 -1
- package/dist/agentic/guides/modules/cart/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/index.md +1 -1
- package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
- package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
- package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
- package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
- package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
- package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
- package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
- package/dist/agentic/guides/modules/channel_smtp/index.md +1 -1
- package/dist/agentic/guides/modules/checkout/index.md +1 -1
- package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
- package/dist/agentic/guides/modules/configs/index.md +1 -1
- package/dist/agentic/guides/modules/content/index.md +1 -1
- package/dist/agentic/guides/modules/currencies/index.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
- package/dist/agentic/guides/modules/customer_groups/index.md +1 -1
- package/dist/agentic/guides/modules/customers/index.md +1 -1
- package/dist/agentic/guides/modules/dashboards/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/index.md +1 -1
- package/dist/agentic/guides/modules/design_system/index.md +1 -1
- package/dist/agentic/guides/modules/devices/index.md +1 -1
- package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
- package/dist/agentic/guides/modules/directory/index.md +1 -1
- package/dist/agentic/guides/modules/documents/index.md +1 -1
- package/dist/agentic/guides/modules/ecommerce/index.md +1 -1
- package/dist/agentic/guides/modules/entities/index.md +1 -1
- package/dist/agentic/guides/modules/eudr/index.md +1 -1
- package/dist/agentic/guides/modules/events/index.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
- package/dist/agentic/guides/modules/forms/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_autopay/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_tpay/index.md +1 -1
- package/dist/agentic/guides/modules/generators/index.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
- package/dist/agentic/guides/modules/integrations/index.md +1 -1
- package/dist/agentic/guides/modules/messages/index.md +1 -1
- package/dist/agentic/guides/modules/notifications/index.md +1 -1
- package/dist/agentic/guides/modules/onboarding/index.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
- package/dist/agentic/guides/modules/perspectives/index.md +1 -1
- package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
- package/dist/agentic/guides/modules/planner/index.md +1 -1
- package/dist/agentic/guides/modules/portal/index.md +1 -1
- package/dist/agentic/guides/modules/progress/index.md +1 -1
- package/dist/agentic/guides/modules/promotions/index.md +1 -1
- package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
- package/dist/agentic/guides/modules/query_index/index.md +1 -1
- package/dist/agentic/guides/modules/record_locks/index.md +1 -1
- package/dist/agentic/guides/modules/resources/index.md +1 -1
- package/dist/agentic/guides/modules/sales/index.md +1 -1
- package/dist/agentic/guides/modules/scheduler/index.md +1 -1
- package/dist/agentic/guides/modules/search/index.md +1 -1
- package/dist/agentic/guides/modules/security/index.md +1 -1
- package/dist/agentic/guides/modules/seeds/index.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
- package/dist/agentic/guides/modules/sso/index.md +1 -1
- package/dist/agentic/guides/modules/staff/index.md +1 -1
- package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
- package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
- package/dist/agentic/guides/modules/telemetry/index.md +1 -1
- package/dist/agentic/guides/modules/tillio/index.md +1 -1
- package/dist/agentic/guides/modules/translations/index.md +1 -1
- package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
- package/dist/agentic/guides/modules/webhooks/index.md +1 -1
- package/dist/agentic/guides/modules/wms/index.md +1 -1
- package/dist/agentic/guides/modules/workflows/index.md +1 -1
- package/dist/agentic/guides/reference-module-facts.json +1 -1
- package/dist/agentic/guides/testing-debugging.md +2 -2
- package/dist/agentic/guides/upstream/manifest.json +1 -1
- package/dist/mercato.js +33 -7
- package/dist/mercato.js.map +3 -3
- package/package.json +7 -7
- package/src/__tests__/mercato.test.ts +87 -0
- package/src/lib/generators/__tests__/module-package-sources.test.ts +7 -4
- 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.
|
|
3
|
+
"version": "0.10.1-develop.7793.1.9e0c43fdde",
|
|
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.
|
|
64
|
-
"@open-mercato/queue": "0.10.1-develop.
|
|
65
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
66
|
-
"@open-mercato/telemetry": "0.10.1-develop.
|
|
63
|
+
"@open-mercato/events": "0.10.1-develop.7793.1.9e0c43fdde",
|
|
64
|
+
"@open-mercato/queue": "0.10.1-develop.7793.1.9e0c43fdde",
|
|
65
|
+
"@open-mercato/shared": "0.10.1-develop.7793.1.9e0c43fdde",
|
|
66
|
+
"@open-mercato/telemetry": "0.10.1-develop.7793.1.9e0c43fdde",
|
|
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.
|
|
78
|
+
"@open-mercato/shared": "0.10.1-develop.7793.1.9e0c43fdde"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
81
|
+
"@open-mercato/shared": "0.10.1-develop.7793.1.9e0c43fdde",
|
|
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(
|
|
100
|
-
expect(fs.statSync(checksumFile).mtimeMs).toBe(
|
|
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(
|
|
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(
|
|
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
|
|
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 () => {
|