diff --git a/package.json b/package.json index 9e769b7..3c4cddf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@absmartly/cli", - "version": "1.6.0", + "version": "1.7.0", "description": "ABSmartly CLI - A/B Testing and Feature Flags command-line tool for AI agents and humans", "type": "module", "main": "./dist/index.js", diff --git a/src/commands/actiondialogfields/actiondialogfields.test.ts b/src/commands/actiondialogfields/actiondialogfields.test.ts index d41cf48..ee0ac28 100644 --- a/src/commands/actiondialogfields/actiondialogfields.test.ts +++ b/src/commands/actiondialogfields/actiondialogfields.test.ts @@ -72,7 +72,7 @@ describe('actiondialogfields command', () => { ]); expect(mockClient.createExperimentActionDialogField).toHaveBeenCalledWith({ name: 'Reason' }); - expect(printFormatted).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Action dialog field created')); }); it('should update an action dialog field', async () => { @@ -88,6 +88,8 @@ describe('actiondialogfields command', () => { expect(mockClient.updateExperimentActionDialogField).toHaveBeenCalledWith(1, { required: true, }); - expect(printFormatted).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining('Action dialog field 1 updated') + ); }); }); diff --git a/src/commands/actiondialogfields/index.ts b/src/commands/actiondialogfields/index.ts index 53bec73..a4dafc9 100644 --- a/src/commands/actiondialogfields/index.ts +++ b/src/commands/actiondialogfields/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { validateJSON } from '../../lib/utils/validators.js'; @@ -61,8 +61,12 @@ const createCommand = new Command('create') const result = await coreCreateActionDialogField(client, { config: config as Record, }); - console.log(chalk.green(`✓ Action dialog field created`)); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: `✓ Action dialog field created`, + id: data?.id, + raw: result.data, + }); }) ); @@ -82,8 +86,11 @@ const updateCommand = new Command('update') id, config: config as Record, }); - console.log(chalk.green(`✓ Action dialog field ${id} updated`)); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: `✓ Action dialog field ${id} updated`, + id, + raw: result.data, + }); }) ); diff --git a/src/commands/apikeys/index.ts b/src/commands/apikeys/index.ts index 29d8e4f..af8437e 100644 --- a/src/commands/apikeys/index.ts +++ b/src/commands/apikeys/index.ts @@ -4,6 +4,7 @@ import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseApiKeyId } from '../../lib/utils/validators.js'; @@ -55,10 +56,19 @@ const createCommand = new Command('create') description: options.description, permissions: options.permissions, }); - console.log(chalk.green(`✓ API key created with ID: ${result.data.id}`)); - if (result.data.key) { - console.log(` Key: ${result.data.key}`); - console.log(chalk.yellow(' Save this key now — it cannot be retrieved later.')); + const format = globalOptions.output ?? 'table'; + if (format === 'table' || format === 'rendered') { + console.log(chalk.green(`✓ API key created with ID: ${result.data.id}`)); + if (result.data.key) { + console.log(` Key: ${result.data.key}`); + console.log(chalk.yellow(' Save this key now — it cannot be retrieved later.')); + } + } else { + printResult(globalOptions, { + message: `✓ API key created with ID: ${result.data.id}`, + id: result.data.id, + raw: result.data, + }); } }) ); @@ -77,7 +87,7 @@ const updateCommand = new Command('update') name: options.name, description: options.description, }); - console.log(chalk.green(`✓ API key ${id} updated`)); + printResult(globalOptions, { message: `✓ API key ${id} updated`, id }); }) ); @@ -89,7 +99,7 @@ const deleteCommand = new Command('delete') const globalOptions = getGlobalOptions(deleteCommand); const client = await getAPIClientFromOptions(globalOptions); await deleteApiKey(client, { id }); - console.log(chalk.green(`✓ API key ${id} deleted`)); + printResult(globalOptions, { message: `✓ API key ${id} deleted`, id }); }) ); diff --git a/src/commands/apps/index.ts b/src/commands/apps/index.ts index f87e861..d277de0 100644 --- a/src/commands/apps/index.ts +++ b/src/commands/apps/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseApplicationId } from '../../lib/utils/validators.js'; @@ -67,9 +67,12 @@ const createCommand = new Command('create') const globalOptions = getGlobalOptions(createCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await createApp(client, { name: options.name }); - console.log( - chalk.green(`✓ Application created with ID: ${(result.data as Record).id}`) - ); + const newId = (result.data as Record).id; + printResult(globalOptions, { + message: `✓ Application created with ID: ${newId}`, + id: newId, + raw: result.data, + }); }) ); @@ -82,7 +85,7 @@ const updateCommand = new Command('update') const globalOptions = getGlobalOptions(updateCommand); const client = await getAPIClientFromOptions(globalOptions); await updateApp(client, { id, name: options.name }); - console.log(chalk.green(`✓ Application ${id} updated`)); + printResult(globalOptions, { message: `✓ Application ${id} updated`, id }); }) ); @@ -96,7 +99,7 @@ const archiveCommand = new Command('archive') const client = await getAPIClientFromOptions(globalOptions); await archiveApp(client, { id, unarchive: options.unarchive }); const action = options.unarchive ? 'unarchived' : 'archived'; - console.log(chalk.green(`✓ Application ${id} ${action}`)); + printResult(globalOptions, { message: `✓ Application ${id} ${action}`, id }); }) ); diff --git a/src/commands/assetroles/index.ts b/src/commands/assetroles/index.ts index 38e5263..d19948d 100644 --- a/src/commands/assetroles/index.ts +++ b/src/commands/assetroles/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseAssetRoleId } from '../../lib/utils/validators.js'; @@ -49,9 +49,12 @@ const createCommand = new Command('create') const globalOptions = getGlobalOptions(createCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await createAssetRole(client, { name: options.name }); - console.log( - chalk.green(`✓ Asset role created with ID: ${(result.data as Record).id}`) - ); + const newId = (result.data as Record).id; + printResult(globalOptions, { + message: `✓ Asset role created with ID: ${newId}`, + id: newId, + raw: result.data, + }); }) ); @@ -64,7 +67,7 @@ const updateCommand = new Command('update') const globalOptions = getGlobalOptions(updateCommand); const client = await getAPIClientFromOptions(globalOptions); await updateAssetRole(client, { id, name: options.name }); - console.log(chalk.green(`✓ Asset role ${id} updated`)); + printResult(globalOptions, { message: `✓ Asset role ${id} updated`, id }); }) ); @@ -76,7 +79,7 @@ const deleteCommand = new Command('delete') const globalOptions = getGlobalOptions(deleteCommand); const client = await getAPIClientFromOptions(globalOptions); await deleteAssetRole(client, { id }); - console.log(chalk.green(`✓ Asset role ${id} deleted`)); + printResult(globalOptions, { message: `✓ Asset role ${id} deleted`, id }); }) ); diff --git a/src/commands/cors/index.ts b/src/commands/cors/index.ts index bc9128e..9cfb07a 100644 --- a/src/commands/cors/index.ts +++ b/src/commands/cors/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseCorsOriginId } from '../../lib/utils/validators.js'; @@ -47,8 +47,12 @@ const createCommand = new Command('create') const globalOptions = getGlobalOptions(createCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await createCorsOrigin(client, { origin: options.origin }); - console.log(chalk.green(`✓ CORS origin created`)); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: `✓ CORS origin created`, + id: data?.id, + raw: result.data, + }); }) ); @@ -61,8 +65,11 @@ const updateCommand = new Command('update') const globalOptions = getGlobalOptions(updateCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await updateCorsOrigin(client, { id, origin: options.origin }); - console.log(chalk.green(`✓ CORS origin ${id} updated`)); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: `✓ CORS origin ${id} updated`, + id, + raw: result.data, + }); }) ); @@ -74,7 +81,7 @@ const deleteCommand = new Command('delete') const globalOptions = getGlobalOptions(deleteCommand); const client = await getAPIClientFromOptions(globalOptions); await deleteCorsOrigin(client, { id }); - console.log(chalk.green(`✓ CORS origin ${id} deleted`)); + printResult(globalOptions, { message: `✓ CORS origin ${id} deleted`, id }); }) ); diff --git a/src/commands/customfields/index.ts b/src/commands/customfields/index.ts index 1806dc4..d990c84 100644 --- a/src/commands/customfields/index.ts +++ b/src/commands/customfields/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseCustomSectionFieldId } from '../../lib/utils/validators.js'; @@ -67,12 +67,12 @@ const createCommand = new Command('create') type: options.type, defaultValue: options.defaultValue, }); - console.log( - chalk.green( - `✓ Custom section field created with ID: ${(result.data as Record).id}` - ) - ); - printFormatted(result.data, globalOptions); + const newId = (result.data as Record).id; + printResult(globalOptions, { + message: `✓ Custom section field created with ID: ${newId}`, + id: newId, + raw: result.data, + }); }) ); @@ -99,8 +99,11 @@ const updateCommand = new Command('update') type: options.type, defaultValue: options.defaultValue, }); - console.log(chalk.green(`✓ Custom section field ${id} updated`)); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: `✓ Custom section field ${id} updated`, + id, + raw: result.data, + }); }) ); @@ -113,9 +116,10 @@ const archiveCommand = new Command('archive') const globalOptions = getGlobalOptions(archiveCommand); const client = await getAPIClientFromOptions(globalOptions); await archiveCustomField(client, { id, unarchive: !!options.unarchive }); - console.log( - chalk.green(`✓ Custom section field ${id} ${options.unarchive ? 'unarchived' : 'archived'}`) - ); + printResult(globalOptions, { + message: `✓ Custom section field ${id} ${options.unarchive ? 'unarchived' : 'archived'}`, + id, + }); }) ); diff --git a/src/commands/customsections/index.ts b/src/commands/customsections/index.ts index cc63ef2..2055db4 100644 --- a/src/commands/customsections/index.ts +++ b/src/commands/customsections/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseCustomSectionId } from '../../lib/utils/validators.js'; @@ -38,8 +38,12 @@ const createCommand = new Command('create') const globalOptions = getGlobalOptions(createCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await createCustomSection(client, { name: options.name, type: options.type }); - console.log(chalk.green(`✓ Custom section created`)); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: `✓ Custom section created`, + id: data?.id, + raw: result.data, + }); }) ); @@ -52,8 +56,11 @@ const updateCommand = new Command('update') const globalOptions = getGlobalOptions(updateCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await updateCustomSection(client, { id, name: options.name }); - console.log(chalk.green(`✓ Custom section ${id} updated`)); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: `✓ Custom section ${id} updated`, + id, + raw: result.data, + }); }) ); @@ -67,7 +74,7 @@ const archiveCommand = new Command('archive') const client = await getAPIClientFromOptions(globalOptions); await archiveCustomSection(client, { id, unarchive: !!options.unarchive }); const action = options.unarchive ? 'unarchived' : 'archived'; - console.log(chalk.green(`✓ Custom section ${id} ${action}`)); + printResult(globalOptions, { message: `✓ Custom section ${id} ${action}`, id }); }) ); @@ -90,7 +97,7 @@ const reorderCommand = new Command('reorder') return { id, order_index }; }); await reorderCustomSections(client, { sections }); - console.log(chalk.green(`✓ Custom sections reordered`)); + printResult(globalOptions, { message: `✓ Custom sections reordered` }); }) ); diff --git a/src/commands/datasources/index.ts b/src/commands/datasources/index.ts index 0c2704d..ce46cd4 100644 --- a/src/commands/datasources/index.ts +++ b/src/commands/datasources/index.ts @@ -4,6 +4,7 @@ import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseDatasourceId, validateJSON } from '../../lib/utils/validators.js'; @@ -60,8 +61,12 @@ const createCommand = new Command('create') const client = await getAPIClientFromOptions(globalOptions); const config = validateJSON(options.jsonConfig, '--json-config') as Record; const result = await coreCreateDatasource(client, { config }); - console.error(chalk.green(`✓ Datasource created`)); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: `✓ Datasource created`, + id: data?.id, + raw: result.data, + }); }) ); @@ -75,8 +80,11 @@ const updateCommand = new Command('update') const client = await getAPIClientFromOptions(globalOptions); const config = validateJSON(options.jsonConfig, '--json-config') as Record; const result = await coreUpdateDatasource(client, { id, config }); - console.error(chalk.green(`✓ Datasource ${id} updated`)); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: `✓ Datasource ${id} updated`, + id, + raw: result.data, + }); }) ); @@ -90,7 +98,7 @@ const archiveCommand = new Command('archive') const client = await getAPIClientFromOptions(globalOptions); await coreArchiveDatasource(client, { id, unarchive: options.unarchive }); const action = options.unarchive ? 'unarchived' : 'archived'; - console.log(chalk.green(`✓ Datasource ${id} ${action}`)); + printResult(globalOptions, { message: `✓ Datasource ${id} ${action}`, id }); }) ); @@ -103,7 +111,7 @@ const testCommand = new Command('test') const client = await getAPIClientFromOptions(globalOptions); const config = validateJSON(options.jsonConfig, '--json-config') as Record; await coreTestDatasource(client, { config }); - console.log(chalk.green(`✓ Datasource connection test passed`)); + printResult(globalOptions, { message: `✓ Datasource connection test passed` }); }) ); @@ -129,7 +137,7 @@ const validateQueryCommand = new Command('validate-query') const client = await getAPIClientFromOptions(globalOptions); const config = validateJSON(options.jsonConfig, '--json-config') as Record; await coreValidateDatasourceQuery(client, { config }); - console.log(chalk.green(`✓ Datasource query is valid`)); + printResult(globalOptions, { message: `✓ Datasource query is valid` }); }) ); @@ -154,7 +162,7 @@ const setDefaultCommand = new Command('set-default') const globalOptions = getGlobalOptions(setDefaultCommand); const client = await getAPIClientFromOptions(globalOptions); await coreSetDefaultDatasource(client, { id }); - console.log(chalk.green(`✓ Datasource ${id} set as default`)); + printResult(globalOptions, { message: `✓ Datasource ${id} set as default`, id }); }) ); @@ -178,7 +186,7 @@ const deleteCommand = new Command('delete') const globalOptions = getGlobalOptions(deleteCommand); const client = await getAPIClientFromOptions(globalOptions); await coreDeleteDatasource(client, { id }); - console.log(chalk.green(`✓ Datasource ${id} deleted`)); + printResult(globalOptions, { message: `✓ Datasource ${id} deleted`, id }); }) ); @@ -194,7 +202,10 @@ const jsonLayoutsCreateCommand = new Command('create') const globalOptions = getGlobalOptions(jsonLayoutsCreateCommand); const client = await getAPIClientFromOptions(globalOptions); await coreCreateDatasourceJsonLayouts(client, { id }); - console.log(chalk.green(`✓ json_layouts table created on datasource ${id}`)); + printResult(globalOptions, { + message: `✓ json_layouts table created on datasource ${id}`, + id, + }); }) ); @@ -217,7 +228,10 @@ const jsonLayoutsRecreateCommand = new Command('recreate') const globalOptions = getGlobalOptions(jsonLayoutsRecreateCommand); const client = await getAPIClientFromOptions(globalOptions); await coreRecreateDatasourceJsonLayouts(client, { id }); - console.log(chalk.green(`✓ json_layouts table recreated on datasource ${id}`)); + printResult(globalOptions, { + message: `✓ json_layouts table recreated on datasource ${id}`, + id, + }); }) ); diff --git a/src/commands/envs/index.ts b/src/commands/envs/index.ts index 84e7ffc..7114e9e 100644 --- a/src/commands/envs/index.ts +++ b/src/commands/envs/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseEnvironmentId } from '../../lib/utils/validators.js'; @@ -51,9 +51,12 @@ const createCommand = new Command('create') const globalOptions = getGlobalOptions(createCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await createEnv(client, { name: options.name }); - console.log( - chalk.green(`✓ Environment created with ID: ${(result.data as Record).id}`) - ); + const newId = (result.data as Record).id; + printResult(globalOptions, { + message: `✓ Environment created with ID: ${newId}`, + id: newId, + raw: result.data, + }); }) ); @@ -66,7 +69,7 @@ const updateCommand = new Command('update') const globalOptions = getGlobalOptions(updateCommand); const client = await getAPIClientFromOptions(globalOptions); await updateEnv(client, { id, name: options.name }); - console.log(chalk.green(`✓ Environment ${id} updated`)); + printResult(globalOptions, { message: `✓ Environment ${id} updated`, id }); }) ); @@ -80,7 +83,7 @@ const archiveCommand = new Command('archive') const client = await getAPIClientFromOptions(globalOptions); await archiveEnv(client, { id, unarchive: options.unarchive }); const action = options.unarchive ? 'unarchived' : 'archived'; - console.log(chalk.green(`✓ Environment ${id} ${action}`)); + printResult(globalOptions, { message: `✓ Environment ${id} ${action}`, id }); }) ); diff --git a/src/commands/events/index.ts b/src/commands/events/index.ts index e939972..4ee1648 100644 --- a/src/commands/events/index.ts +++ b/src/commands/events/index.ts @@ -4,6 +4,7 @@ import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseDateFlagOrUndefined } from '../../lib/utils/date-parser.js'; @@ -156,9 +157,17 @@ const deleteUnitDataCommand = new Command('delete-unit-data') const client = await getAPIClientFromOptions(globalOptions); const parsed = parseUnits(units); const result = await coreDeleteEventUnitData(client, { units }); - console.log(chalk.green(`✓ Unit data deleted for ${parsed.length} unit(s)`)); - if (result.data) { - printFormatted(result.data, globalOptions); + const format = globalOptions.output ?? 'table'; + if (format === 'table' || format === 'rendered') { + console.log(chalk.green(`✓ Unit data deleted for ${parsed.length} unit(s)`)); + if (result.data) { + printFormatted(result.data, globalOptions); + } + } else { + printResult(globalOptions, { + message: `✓ Unit data deleted for ${parsed.length} unit(s)`, + raw: result.data, + }); } }) ); diff --git a/src/commands/experiments/access.ts b/src/commands/experiments/access.ts index 742738e..313869c 100644 --- a/src/commands/experiments/access.ts +++ b/src/commands/experiments/access.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { @@ -50,7 +50,10 @@ const grantUserCommand = new Command('grant-user') userId: options.user, roleId: options.role, }); - console.log(chalk.green(`✓ User ${options.user} granted access to experiment ${id}`)); + printResult(globalOptions, { + message: `✓ User ${options.user} granted access to experiment ${id}`, + id, + }); }) ); @@ -68,7 +71,10 @@ const revokeUserCommand = new Command('revoke-user') userId: options.user, roleId: options.role, }); - console.log(chalk.green(`✓ User ${options.user} access revoked from experiment ${id}`)); + printResult(globalOptions, { + message: `✓ User ${options.user} access revoked from experiment ${id}`, + id, + }); }) ); @@ -98,7 +104,10 @@ const grantTeamCommand = new Command('grant-team') teamId: options.team, roleId: options.role, }); - console.log(chalk.green(`✓ Team ${options.team} granted access to experiment ${id}`)); + printResult(globalOptions, { + message: `✓ Team ${options.team} granted access to experiment ${id}`, + id, + }); }) ); @@ -116,7 +125,10 @@ const revokeTeamCommand = new Command('revoke-team') teamId: options.team, roleId: options.role, }); - console.log(chalk.green(`✓ Team ${options.team} access revoked from experiment ${id}`)); + printResult(globalOptions, { + message: `✓ Team ${options.team} access revoked from experiment ${id}`, + id, + }); }) ); diff --git a/src/commands/experiments/activity.ts b/src/commands/experiments/activity.ts index 038682c..8a7db54 100644 --- a/src/commands/experiments/activity.ts +++ b/src/commands/experiments/activity.ts @@ -4,6 +4,7 @@ import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, resolveEndpoint, resolveAPIKey, withErrorHandling, @@ -143,7 +144,11 @@ const createActivityCommand = new Command('create') const id = await client.resolveExperimentId(nameOrId); const result = await createExperimentNote(client, { experimentId: id, note: options.note }); - console.log(chalk.green(`✓ Note created (id: ${result.data.id})`)); + printResult(globalOptions, { + message: `✓ Note created (id: ${result.data.id})`, + id: result.data.id, + raw: result.data, + }); }) ); @@ -159,7 +164,7 @@ const editActivityCommand = new Command('edit') const id = await client.resolveExperimentId(expNameOrId); await editExperimentNote(client, { experimentId: id, noteId, note: options.note }); - console.log(chalk.green(`✓ Note ${noteId} updated`)); + printResult(globalOptions, { message: `✓ Note ${noteId} updated`, id: noteId }); }) ); @@ -179,7 +184,11 @@ const replyActivityCommand = new Command('reply') noteId, note: options.note, }); - console.log(chalk.green(`✓ Reply created (id: ${result.data.id})`)); + printResult(globalOptions, { + message: `✓ Reply created (id: ${result.data.id})`, + id: result.data.id, + raw: result.data, + }); }) ); diff --git a/src/commands/experiments/alerts.ts b/src/commands/experiments/alerts.ts index 66a34c1..7a656c7 100644 --- a/src/commands/experiments/alerts.ts +++ b/src/commands/experiments/alerts.ts @@ -4,6 +4,7 @@ import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExperimentId, parseAlertId } from '../../lib/utils/validators.js'; @@ -40,7 +41,7 @@ const dismissAlertCommand = new Command('dismiss') const client = await getAPIClientFromOptions(globalOptions); await dismissAlert(client, { alertId }); - console.log(chalk.green(`✓ Alert ${alertId} dismissed`)); + printResult(globalOptions, { message: `✓ Alert ${alertId} dismissed`, id: alertId }); }) ); diff --git a/src/commands/experiments/annotations.ts b/src/commands/experiments/annotations.ts index 34f330d..7b3d0c1 100644 --- a/src/commands/experiments/annotations.ts +++ b/src/commands/experiments/annotations.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { @@ -44,8 +44,12 @@ const createCommand = new Command('create') const globalOptions = getGlobalOptions(createCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await createAnnotation(client, { experimentId, type: options.type }); - console.log(chalk.green(`✓ Annotation created`)); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: `✓ Annotation created`, + id: data?.id, + raw: result.data, + }); }) ); @@ -61,8 +65,11 @@ const updateCommand = new Command('update') if (options.type !== undefined) data.type = options.type; requireAtLeastOneField(data, 'update field'); const result = await updateAnnotation(client, { annotationId, type: options.type }); - console.log(chalk.green(`✓ Annotation ${annotationId} updated`)); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: `✓ Annotation ${annotationId} updated`, + id: annotationId, + raw: result.data, + }); }) ); @@ -78,7 +85,11 @@ const archiveCmd = new Command('archive') annotationId, unarchive: !!options.unarchive, }); - console.log(chalk.green(`✓ Annotation ${annotationId} ${result.data.action}`)); + printResult(globalOptions, { + message: `✓ Annotation ${annotationId} ${result.data.action}`, + id: annotationId, + raw: result.data, + }); }) ); diff --git a/src/commands/experiments/archive.ts b/src/commands/experiments/archive.ts index 6ec07b0..dd434ba 100644 --- a/src/commands/experiments/archive.ts +++ b/src/commands/experiments/archive.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExperimentIdOrName } from './resolve-id.js'; @@ -29,7 +30,6 @@ export const archiveCommand = new Command('archive') ? await readLinesFromStdin() : []; if (ids.length === 0) throw new Error('Provide an experiment ID or pipe IDs from stdin'); - const outputPiped = isStdoutPiped(); const actionLabel = options.unarchive ? 'unarchived' : 'archived'; const note = await resolveNote( @@ -44,15 +44,10 @@ export const archiveCommand = new Command('archive') try { const id = await client.resolveExperimentId(idStr); await archiveExperiment(client, { experimentId: id, unarchive: options.unarchive, note }); - if (outputPiped) { - console.log(id); - console.error(chalk.green(`✓ Experiment ${id} ${actionLabel}`)); - } else { - console.log(chalk.green(`✓ Experiment ${id} ${actionLabel}`)); - } + printResult(globalOptions, { message: `✓ Experiment ${id} ${actionLabel}`, id }); } catch (e) { hasFailures = true; - if (outputPiped && options.passThrough) console.log(idStr); + if (isStdoutPiped() && options.passThrough) console.log(idStr); console.error(chalk.red(`✗ Experiment ${idStr}: ${e instanceof Error ? e.message : e}`)); } } diff --git a/src/commands/experiments/create.ts b/src/commands/experiments/create.ts index 52fdaa1..2becada 100644 --- a/src/commands/experiments/create.ts +++ b/src/commands/experiments/create.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, resolveAPIKey, resolveEndpoint, withErrorHandling, @@ -196,8 +197,17 @@ createCommand.action( const result = await createExperiment(client, data); - console.log(chalk.green(`✓ Experiment created with ID: ${result.data.id}`)); - console.log(` Name: ${result.data.name}`); - console.log(` Type: ${result.data.type}`); + const format = globalOptions.output ?? 'table'; + if (format === 'table' || format === 'rendered') { + console.log(chalk.green(`✓ Experiment created with ID: ${result.data.id}`)); + console.log(` Name: ${result.data.name}`); + console.log(` Type: ${result.data.type}`); + } else { + printResult(globalOptions, { + message: `✓ Experiment created with ID: ${result.data.id}`, + id: result.data.id, + raw: result.data, + }); + } }) ); diff --git a/src/commands/experiments/custom-fields.ts b/src/commands/experiments/custom-fields.ts index a50f0a5..5239175 100644 --- a/src/commands/experiments/custom-fields.ts +++ b/src/commands/experiments/custom-fields.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseCustomSectionFieldId, requireAtLeastOneField } from '../../lib/utils/validators.js'; @@ -82,12 +82,12 @@ const createCommand = new Command('create') defaultValue: options.defaultValue, }); - console.log( - chalk.green( - `✓ Custom section field created with ID: ${(result.data as Record).id}` - ) - ); - printFormatted(result.data, globalOptions); + const newId = (result.data as Record).id; + printResult(globalOptions, { + message: `✓ Custom section field created with ID: ${newId}`, + id: newId, + raw: result.data, + }); }) ); @@ -115,8 +115,11 @@ const updateCommand = new Command('update') defaultValue: options.defaultValue, }); - console.log(chalk.green(`✓ Custom section field ${id} updated`)); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: `✓ Custom section field ${id} updated`, + id, + raw: result.data, + }); }) ); @@ -130,9 +133,10 @@ const archiveCmd = new Command('archive') const client = await getAPIClientFromOptions(globalOptions); await archiveCustomField(client, { id, unarchive: !!options.unarchive }); - console.log( - chalk.green(`✓ Custom section field ${id} ${options.unarchive ? 'unarchived' : 'archived'}`) - ); + printResult(globalOptions, { + message: `✓ Custom section field ${id} ${options.unarchive ? 'unarchived' : 'archived'}`, + id, + }); }) ); diff --git a/src/commands/experiments/development.ts b/src/commands/experiments/development.ts index e021ea8..3c01ae9 100644 --- a/src/commands/experiments/development.ts +++ b/src/commands/experiments/development.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExperimentIdOrName } from './resolve-id.js'; @@ -29,7 +30,6 @@ export const developmentCommand = new Command('development') ? await readLinesFromStdin() : []; if (ids.length === 0) throw new Error('Provide an experiment ID or pipe IDs from stdin'); - const outputPiped = isStdoutPiped(); const note = await resolveNote( options, @@ -43,15 +43,13 @@ export const developmentCommand = new Command('development') try { const id = await client.resolveExperimentId(idStr); await developmentExperiment(client, { experimentId: id, note }); - if (outputPiped) { - console.log(id); - console.error(chalk.green(`✓ Experiment ${id} set to development mode`)); - } else { - console.log(chalk.green(`✓ Experiment ${id} set to development mode`)); - } + printResult(globalOptions, { + message: `✓ Experiment ${id} set to development mode`, + id, + }); } catch (e) { hasFailures = true; - if (outputPiped && options.passThrough) console.log(idStr); + if (isStdoutPiped() && options.passThrough) console.log(idStr); console.error(chalk.red(`✗ Experiment ${idStr}: ${e instanceof Error ? e.message : e}`)); } } diff --git a/src/commands/experiments/follow.ts b/src/commands/experiments/follow.ts index 684f376..14d5c6f 100644 --- a/src/commands/experiments/follow.ts +++ b/src/commands/experiments/follow.ts @@ -1,8 +1,8 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExperimentId } from '../../lib/utils/validators.js'; @@ -17,7 +17,7 @@ export const followCommand = new Command('follow') const globalOptions = getGlobalOptions(followCommand); const client = await getAPIClientFromOptions(globalOptions); await followExperiment(client, { experimentId: id }); - console.log(chalk.green(`✓ Now following experiment ${id}`)); + printResult(globalOptions, { message: `✓ Now following experiment ${id}`, id }); }) ); @@ -29,6 +29,6 @@ export const unfollowCommand = new Command('unfollow') const globalOptions = getGlobalOptions(unfollowCommand); const client = await getAPIClientFromOptions(globalOptions); await unfollowExperiment(client, { experimentId: id }); - console.log(chalk.green(`✓ No longer following experiment ${id}`)); + printResult(globalOptions, { message: `✓ No longer following experiment ${id}`, id }); }) ); diff --git a/src/commands/experiments/full-on.ts b/src/commands/experiments/full-on.ts index f2543f1..60ce751 100644 --- a/src/commands/experiments/full-on.ts +++ b/src/commands/experiments/full-on.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExperimentIdOrName } from './resolve-id.js'; @@ -35,7 +36,6 @@ export const fullOnCommand = new Command('full-on') ? await readLinesFromStdin() : []; if (ids.length === 0) throw new Error('Provide an experiment ID or pipe IDs from stdin'); - const outputPiped = isStdoutPiped(); const note = await resolveNote(options, 'full_on', getDefaultType(), globalOptions.profile); @@ -44,19 +44,13 @@ export const fullOnCommand = new Command('full-on') try { const id = await client.resolveExperimentId(idStr); await fullOnExperiment(client, { experimentId: id, variant: options.variant, note }); - if (outputPiped) { - console.log(id); - console.error( - chalk.green(`✓ Experiment ${id} set to full-on (variant ${options.variant})`) - ); - } else { - console.log( - chalk.green(`✓ Experiment ${id} set to full-on (variant ${options.variant})`) - ); - } + printResult(globalOptions, { + message: `✓ Experiment ${id} set to full-on (variant ${options.variant})`, + id, + }); } catch (e) { hasFailures = true; - if (outputPiped && options.passThrough) console.log(idStr); + if (isStdoutPiped() && options.passThrough) console.log(idStr); console.error(chalk.red(`✗ Experiment ${idStr}: ${e instanceof Error ? e.message : e}`)); } } diff --git a/src/commands/experiments/metrics.ts b/src/commands/experiments/metrics.ts index 15f18f3..a73a2a2 100644 --- a/src/commands/experiments/metrics.ts +++ b/src/commands/experiments/metrics.ts @@ -4,6 +4,7 @@ import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseMetricId } from '../../lib/utils/validators.js'; @@ -53,7 +54,7 @@ const addCommand = new Command('add') const id = await client.resolveExperimentId(nameOrId); const metricIds = options.metrics.split(',').map((s: string) => parseMetricId(s.trim())); await addExperimentMetrics(client, { experimentId: id, metricIds }); - console.log(chalk.green(`✓ Metrics added to experiment ${id}`)); + printResult(globalOptions, { message: `✓ Metrics added to experiment ${id}`, id }); }) ); @@ -67,9 +68,10 @@ const confirmImpactCommand = new Command('confirm-impact') const client = await getAPIClientFromOptions(globalOptions); const experimentId = await client.resolveExperimentId(experimentNameOrId); await confirmMetricImpact(client, { experimentId, metricId }); - console.log( - chalk.green(`✓ Metric impact confirmed for experiment ${experimentId}, metric ${metricId}`) - ); + printResult(globalOptions, { + message: `✓ Metric impact confirmed for experiment ${experimentId}, metric ${metricId}`, + id: experimentId, + }); }) ); @@ -83,7 +85,10 @@ const excludeCommand = new Command('exclude') const client = await getAPIClientFromOptions(globalOptions); const experimentId = await client.resolveExperimentId(experimentNameOrId); await excludeExperimentMetric(client, { experimentId, metricId }); - console.log(chalk.green(`✓ Metric ${metricId} excluded from experiment ${experimentId}`)); + printResult(globalOptions, { + message: `✓ Metric ${metricId} excluded from experiment ${experimentId}`, + id: experimentId, + }); }) ); @@ -97,7 +102,10 @@ const includeCommand = new Command('include') const client = await getAPIClientFromOptions(globalOptions); const experimentId = await client.resolveExperimentId(experimentNameOrId); await includeExperimentMetric(client, { experimentId, metricId }); - console.log(chalk.green(`✓ Metric ${metricId} included in experiment ${experimentId}`)); + printResult(globalOptions, { + message: `✓ Metric ${metricId} included in experiment ${experimentId}`, + id: experimentId, + }); }) ); @@ -111,9 +119,10 @@ const removeImpactCommand = new Command('remove-impact') const client = await getAPIClientFromOptions(globalOptions); const experimentId = await client.resolveExperimentId(experimentNameOrId); await removeMetricImpact(client, { experimentId, metricId }); - console.log( - chalk.green(`✓ Metric impact removed for experiment ${experimentId}, metric ${metricId}`) - ); + printResult(globalOptions, { + message: `✓ Metric impact removed for experiment ${experimentId}, metric ${metricId}`, + id: experimentId, + }); }) ); diff --git a/src/commands/experiments/recommendations.ts b/src/commands/experiments/recommendations.ts index 5fed73f..665926f 100644 --- a/src/commands/experiments/recommendations.ts +++ b/src/commands/experiments/recommendations.ts @@ -4,6 +4,7 @@ import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExperimentId, parseRecommendedActionId } from '../../lib/utils/validators.js'; @@ -45,7 +46,10 @@ const dismissRecommendationCommand = new Command('dismiss') const client = await getAPIClientFromOptions(globalOptions); await dismissRecommendedAction(client, { actionId }); - console.log(chalk.green(`✓ Recommended action ${actionId} dismissed`)); + printResult(globalOptions, { + message: `✓ Recommended action ${actionId} dismissed`, + id: actionId, + }); }) ); diff --git a/src/commands/experiments/request-update.ts b/src/commands/experiments/request-update.ts index 263d1c7..c7070e9 100644 --- a/src/commands/experiments/request-update.ts +++ b/src/commands/experiments/request-update.ts @@ -1,8 +1,8 @@ import { Command, Option } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExperimentId } from '../../lib/utils/validators.js'; @@ -64,6 +64,9 @@ export const requestUpdateCommand = new Command('request-update') tasks: options.tasks, replaceGsa: options.replaceGsa, }); - console.log(chalk.green(`✓ Analysis update requested for experiment ${id}`)); + printResult(globalOptions, { + message: `✓ Analysis update requested for experiment ${id}`, + id, + }); }) ); diff --git a/src/commands/experiments/restart.ts b/src/commands/experiments/restart.ts index ac8670d..f7bb07b 100644 --- a/src/commands/experiments/restart.ts +++ b/src/commands/experiments/restart.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { experimentToMarkdown } from '../../api-client/template/serializer.js'; @@ -107,8 +108,10 @@ restartCommand.action( const result = await restartExperiment(client, params, changes); const typeNote = options.asType ? ` as ${options.asType}` : ''; - console.log( - chalk.green(`Experiment ${id} restarted${typeNote} → new iteration ID: ${result.data.newId}`) - ); + printResult(globalOptions, { + message: `Experiment ${id} restarted${typeNote} → new iteration ID: ${result.data.newId}`, + id: result.data.newId, + raw: result.data, + }); }) ); diff --git a/src/commands/experiments/schedule.ts b/src/commands/experiments/schedule.ts index 30605ea..7d59e07 100644 --- a/src/commands/experiments/schedule.ts +++ b/src/commands/experiments/schedule.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExperimentId, parseScheduledActionId } from '../../lib/utils/validators.js'; @@ -37,11 +38,20 @@ const createScheduleCommand = new Command('create') reason: options.reason, }); - console.log(chalk.green(`✓ Scheduled "${options.action}" for experiment ${id}`)); - if (result.data.actionId) { - console.log(` Scheduled action ID: ${result.data.actionId}`); + const format = globalOptions.output ?? 'table'; + if (format === 'table' || format === 'rendered') { + console.log(chalk.green(`✓ Scheduled "${options.action}" for experiment ${id}`)); + if (result.data.actionId) { + console.log(` Scheduled action ID: ${result.data.actionId}`); + } + console.log(` Scheduled at: ${result.data.scheduledAt}`); + } else { + printResult(globalOptions, { + message: `✓ Scheduled "${options.action}" for experiment ${id}`, + id: result.data.actionId ?? id, + raw: result.data, + }); } - console.log(` Scheduled at: ${result.data.scheduledAt}`); }) ); @@ -55,9 +65,10 @@ const deleteScheduleCommand = new Command('delete') const client = await getAPIClientFromOptions(globalOptions); await deleteScheduledAction(client, { experimentId, actionId }); - console.log( - chalk.green(`✓ Scheduled action ${actionId} deleted from experiment ${experimentId}`) - ); + printResult(globalOptions, { + message: `✓ Scheduled action ${actionId} deleted from experiment ${experimentId}`, + id: actionId, + }); }) ); diff --git a/src/commands/experiments/start.ts b/src/commands/experiments/start.ts index f7306d5..5f43178 100644 --- a/src/commands/experiments/start.ts +++ b/src/commands/experiments/start.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExperimentIdOrName } from './resolve-id.js'; @@ -28,7 +29,6 @@ export const startCommand = new Command('start') ? await readLinesFromStdin() : []; if (ids.length === 0) throw new Error('Provide an experiment ID or pipe IDs from stdin'); - const outputPiped = isStdoutPiped(); const note = await resolveNote(options, 'start', getDefaultType(), globalOptions.profile); @@ -39,18 +39,13 @@ export const startCommand = new Command('start') const result = await startExperiment(client, { experimentId: id, note }); if (result.data.skipped) { console.error(chalk.yellow(`⚠ Experiment ${id} is in draft state, skipping`)); - if (outputPiped && options.passThrough) console.log(id); + if (isStdoutPiped() && options.passThrough) console.log(id); continue; } - if (outputPiped) { - console.log(id); - console.error(chalk.green(`✓ Experiment ${id} started`)); - } else { - console.log(chalk.green(`✓ Experiment ${id} started`)); - } + printResult(globalOptions, { message: `✓ Experiment ${id} started`, id }); } catch (e) { hasFailures = true; - if (outputPiped && options.passThrough) console.log(idStr); + if (isStdoutPiped() && options.passThrough) console.log(idStr); console.error(chalk.red(`✗ Experiment ${idStr}: ${e instanceof Error ? e.message : e}`)); } } diff --git a/src/commands/experiments/stop.ts b/src/commands/experiments/stop.ts index ca6ace4..822c407 100644 --- a/src/commands/experiments/stop.ts +++ b/src/commands/experiments/stop.ts @@ -4,6 +4,7 @@ import { select } from '@inquirer/prompts'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExperimentIdOrName } from './resolve-id.js'; @@ -30,7 +31,6 @@ export const stopCommand = new Command('stop') ? await readLinesFromStdin() : []; if (ids.length === 0) throw new Error('Provide an experiment ID or pipe IDs from stdin'); - const outputPiped = isStdoutPiped(); const reason = options.reason || @@ -48,15 +48,10 @@ export const stopCommand = new Command('stop') try { const id = await client.resolveExperimentId(idStr); await stopExperiment(client, { experimentId: id, reason, note }); - if (outputPiped) { - console.log(id); - console.error(chalk.green(`✓ Experiment ${id} stopped`)); - } else { - console.log(chalk.green(`✓ Experiment ${id} stopped`)); - } + printResult(globalOptions, { message: `✓ Experiment ${id} stopped`, id }); } catch (e) { hasFailures = true; - if (outputPiped && options.passThrough) console.log(idStr); + if (isStdoutPiped() && options.passThrough) console.log(idStr); console.error(chalk.red(`✗ Experiment ${idStr}: ${e instanceof Error ? e.message : e}`)); } } diff --git a/src/commands/experiments/update.ts b/src/commands/experiments/update.ts index f7066e8..80dbe07 100644 --- a/src/commands/experiments/update.ts +++ b/src/commands/experiments/update.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { experimentToMarkdown } from '../../api-client/template/serializer.js'; @@ -132,6 +133,6 @@ updateCommand.action( } await updateExperiment(client, { experimentId: id, changes, note }); - console.log(chalk.green(`Experiment ${id} updated`)); + printResult(globalOptions, { message: `Experiment ${id} updated`, id }); }) ); diff --git a/src/commands/exportconfigs/index.ts b/src/commands/exportconfigs/index.ts index fb5accb..0e0683d 100644 --- a/src/commands/exportconfigs/index.ts +++ b/src/commands/exportconfigs/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExportConfigId, validateJSON } from '../../lib/utils/validators.js'; @@ -53,8 +53,12 @@ const createCommand = new Command('create') const client = await getAPIClientFromOptions(globalOptions); const config = validateJSON(options.jsonConfig, '--json-config') as Record; const result = await coreCreateExportConfig(client, { config }); - console.log(chalk.green(`✓ Export configuration created`)); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: `✓ Export configuration created`, + id: data?.id, + raw: result.data, + }); }) ); @@ -68,8 +72,11 @@ const updateCommand = new Command('update') const client = await getAPIClientFromOptions(globalOptions); const config = validateJSON(options.jsonConfig, '--json-config') as Record; const result = await coreUpdateExportConfig(client, { id, config }); - console.log(chalk.green(`✓ Export configuration ${id} updated`)); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: `✓ Export configuration ${id} updated`, + id, + raw: result.data, + }); }) ); @@ -83,7 +90,7 @@ const archiveCommand = new Command('archive') const client = await getAPIClientFromOptions(globalOptions); await coreArchiveExportConfig(client, { id, unarchive: options.unarchive }); const action = options.unarchive ? 'unarchived' : 'archived'; - console.log(chalk.green(`✓ Export configuration ${id} ${action}`)); + printResult(globalOptions, { message: `✓ Export configuration ${id} ${action}`, id }); }) ); @@ -95,7 +102,7 @@ const pauseCommand = new Command('pause') const globalOptions = getGlobalOptions(pauseCommand); const client = await getAPIClientFromOptions(globalOptions); await corePauseExportConfig(client, { id }); - console.log(chalk.green(`✓ Export configuration ${id} paused`)); + printResult(globalOptions, { message: `✓ Export configuration ${id} paused`, id }); }) ); @@ -121,7 +128,10 @@ const cancelHistoryCommand = new Command('cancel-history') const globalOptions = getGlobalOptions(cancelHistoryCommand); const client = await getAPIClientFromOptions(globalOptions); await coreCancelExportHistory(client, { exportConfigId, historyId, reason: options.reason }); - console.log(chalk.green(`✓ Export history ${historyId} cancelled`)); + printResult(globalOptions, { + message: `✓ Export history ${historyId} cancelled`, + id: historyId, + }); }) ); diff --git a/src/commands/favorites/index.ts b/src/commands/favorites/index.ts index 2140f16..0f47b7f 100644 --- a/src/commands/favorites/index.ts +++ b/src/commands/favorites/index.ts @@ -1,8 +1,8 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseExperimentId, parseMetricId } from '../../lib/utils/validators.js'; @@ -36,7 +36,7 @@ const addCommand = new Command('add') } await coreAddFavorite(client, { type, id }); - console.log(chalk.green(`✓ Added ${type} ${id} to favorites`)); + printResult(globalOptions, { message: `✓ Added ${type} ${id} to favorites`, id }); }) ); @@ -59,7 +59,7 @@ const removeCommand = new Command('remove') } await coreRemoveFavorite(client, { type, id }); - console.log(chalk.green(`✓ Removed ${type} ${id} from favorites`)); + printResult(globalOptions, { message: `✓ Removed ${type} ${id} from favorites`, id }); }) ); diff --git a/src/commands/goals/access.ts b/src/commands/goals/access.ts index 4c6b59d..e33dbb8 100644 --- a/src/commands/goals/access.ts +++ b/src/commands/goals/access.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { @@ -46,7 +46,10 @@ const grantUserCommand = new Command('grant-user') const globalOptions = getGlobalOptions(grantUserCommand); const client = await getAPIClientFromOptions(globalOptions); await grantGoalAccessUser(client, { id, userId: options.user, roleId: options.role }); - console.log(chalk.green(`✓ User ${options.user} granted access to goal ${id}`)); + printResult(globalOptions, { + message: `✓ User ${options.user} granted access to goal ${id}`, + id, + }); }) ); @@ -60,7 +63,10 @@ const revokeUserCommand = new Command('revoke-user') const globalOptions = getGlobalOptions(revokeUserCommand); const client = await getAPIClientFromOptions(globalOptions); await revokeGoalAccessUser(client, { id, userId: options.user, roleId: options.role }); - console.log(chalk.green(`✓ User ${options.user} access revoked from goal ${id}`)); + printResult(globalOptions, { + message: `✓ User ${options.user} access revoked from goal ${id}`, + id, + }); }) ); @@ -86,7 +92,10 @@ const grantTeamCommand = new Command('grant-team') const globalOptions = getGlobalOptions(grantTeamCommand); const client = await getAPIClientFromOptions(globalOptions); await grantGoalAccessTeam(client, { id, teamId: options.team, roleId: options.role }); - console.log(chalk.green(`✓ Team ${options.team} granted access to goal ${id}`)); + printResult(globalOptions, { + message: `✓ Team ${options.team} granted access to goal ${id}`, + id, + }); }) ); @@ -100,7 +109,10 @@ const revokeTeamCommand = new Command('revoke-team') const globalOptions = getGlobalOptions(revokeTeamCommand); const client = await getAPIClientFromOptions(globalOptions); await revokeGoalAccessTeam(client, { id, teamId: options.team, roleId: options.role }); - console.log(chalk.green(`✓ Team ${options.team} access revoked from goal ${id}`)); + printResult(globalOptions, { + message: `✓ Team ${options.team} access revoked from goal ${id}`, + id, + }); }) ); diff --git a/src/commands/goals/follow.ts b/src/commands/goals/follow.ts index 95b9035..9d53b8d 100644 --- a/src/commands/goals/follow.ts +++ b/src/commands/goals/follow.ts @@ -1,8 +1,8 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseGoalId } from '../../lib/utils/validators.js'; @@ -17,7 +17,7 @@ export const followCommand = new Command('follow') const globalOptions = getGlobalOptions(followCommand); const client = await getAPIClientFromOptions(globalOptions); await followGoal(client, { id }); - console.log(chalk.green(`✓ Now following goal ${id}`)); + printResult(globalOptions, { message: `✓ Now following goal ${id}`, id }); }) ); @@ -29,6 +29,6 @@ export const unfollowCommand = new Command('unfollow') const globalOptions = getGlobalOptions(unfollowCommand); const client = await getAPIClientFromOptions(globalOptions); await unfollowGoal(client, { id }); - console.log(chalk.green(`✓ No longer following goal ${id}`)); + printResult(globalOptions, { message: `✓ No longer following goal ${id}`, id }); }) ); diff --git a/src/commands/goals/index.ts b/src/commands/goals/index.ts index 4022d13..346c272 100644 --- a/src/commands/goals/index.ts +++ b/src/commands/goals/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseGoalId } from '../../lib/utils/validators.js'; @@ -76,7 +76,11 @@ const createCommand = new Command('create') description: options.description, }); - console.log(chalk.green(`✓ Goal created with ID: ${result.data.id}`)); + printResult(globalOptions, { + message: `✓ Goal created with ID: ${result.data.id}`, + id: result.data.id, + raw: result.data, + }); }) ); @@ -93,7 +97,7 @@ const updateCommand = new Command('update') id, description: options.description, }); - console.log(chalk.green(`✓ Goal ${id} updated`)); + printResult(globalOptions, { message: `✓ Goal ${id} updated`, id }); }) ); diff --git a/src/commands/goaltags/index.ts b/src/commands/goaltags/index.ts index 0e25c49..81f4f3e 100644 --- a/src/commands/goaltags/index.ts +++ b/src/commands/goaltags/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseTagId } from '../../lib/utils/validators.js'; @@ -56,8 +56,12 @@ const createCommand = new Command('create') const globalOptions = getGlobalOptions(createCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await createGoalTag(client, { tag: options.tag }); - console.log(chalk.green('Goal tag created successfully')); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: 'Goal tag created successfully', + id: data?.id, + raw: result.data, + }); }) ); @@ -70,8 +74,12 @@ const updateCommand = new Command('update') const globalOptions = getGlobalOptions(updateCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await updateGoalTag(client, { id, tag: options.tag }); - console.log(chalk.green('Goal tag updated successfully')); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: 'Goal tag updated successfully', + id: data?.id ?? id, + raw: result.data, + }); }) ); @@ -83,7 +91,7 @@ const deleteCommand = new Command('delete') const globalOptions = getGlobalOptions(deleteCommand); const client = await getAPIClientFromOptions(globalOptions); await deleteGoalTag(client, { id }); - console.log(chalk.green('Goal tag deleted successfully')); + printResult(globalOptions, { message: 'Goal tag deleted successfully', id }); }) ); diff --git a/src/commands/metriccategories/index.ts b/src/commands/metriccategories/index.ts index 29bb118..0c68324 100644 --- a/src/commands/metriccategories/index.ts +++ b/src/commands/metriccategories/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseTagId } from '../../lib/utils/validators.js'; @@ -63,8 +63,12 @@ const createCommand = new Command('create') description: options.description, color: options.color, }); - console.log(chalk.green('Metric category created successfully')); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: 'Metric category created successfully', + id: data?.id, + raw: result.data, + }); }) ); @@ -84,8 +88,12 @@ const updateCommand = new Command('update') description: options.description, color: options.color, }); - console.log(chalk.green('Metric category updated successfully')); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: 'Metric category updated successfully', + id: data?.id ?? id, + raw: result.data, + }); }) ); @@ -98,9 +106,10 @@ const archiveCommand = new Command('archive') const globalOptions = getGlobalOptions(archiveCommand); const client = await getAPIClientFromOptions(globalOptions); await archiveMetricCategory(client, { id, unarchive: options.unarchive }); - console.log( - chalk.green(`Metric category ${options.unarchive ? 'unarchived' : 'archived'} successfully`) - ); + printResult(globalOptions, { + message: `Metric category ${options.unarchive ? 'unarchived' : 'archived'} successfully`, + id, + }); }) ); diff --git a/src/commands/metrics/access.ts b/src/commands/metrics/access.ts index dd51b4e..b6e1604 100644 --- a/src/commands/metrics/access.ts +++ b/src/commands/metrics/access.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { @@ -46,7 +46,10 @@ const grantUserCommand = new Command('grant-user') const globalOptions = getGlobalOptions(grantUserCommand); const client = await getAPIClientFromOptions(globalOptions); await grantMetricAccessUser(client, { id, userId: options.user, roleId: options.role }); - console.log(chalk.green(`✓ User ${options.user} granted access to metric ${id}`)); + printResult(globalOptions, { + message: `✓ User ${options.user} granted access to metric ${id}`, + id, + }); }) ); @@ -60,7 +63,10 @@ const revokeUserCommand = new Command('revoke-user') const globalOptions = getGlobalOptions(revokeUserCommand); const client = await getAPIClientFromOptions(globalOptions); await revokeMetricAccessUser(client, { id, userId: options.user, roleId: options.role }); - console.log(chalk.green(`✓ User ${options.user} access revoked from metric ${id}`)); + printResult(globalOptions, { + message: `✓ User ${options.user} access revoked from metric ${id}`, + id, + }); }) ); @@ -86,7 +92,10 @@ const grantTeamCommand = new Command('grant-team') const globalOptions = getGlobalOptions(grantTeamCommand); const client = await getAPIClientFromOptions(globalOptions); await grantMetricAccessTeam(client, { id, teamId: options.team, roleId: options.role }); - console.log(chalk.green(`✓ Team ${options.team} granted access to metric ${id}`)); + printResult(globalOptions, { + message: `✓ Team ${options.team} granted access to metric ${id}`, + id, + }); }) ); @@ -100,7 +109,10 @@ const revokeTeamCommand = new Command('revoke-team') const globalOptions = getGlobalOptions(revokeTeamCommand); const client = await getAPIClientFromOptions(globalOptions); await revokeMetricAccessTeam(client, { id, teamId: options.team, roleId: options.role }); - console.log(chalk.green(`✓ Team ${options.team} access revoked from metric ${id}`)); + printResult(globalOptions, { + message: `✓ Team ${options.team} access revoked from metric ${id}`, + id, + }); }) ); diff --git a/src/commands/metrics/follow.ts b/src/commands/metrics/follow.ts index 2b633aa..774c77e 100644 --- a/src/commands/metrics/follow.ts +++ b/src/commands/metrics/follow.ts @@ -1,8 +1,8 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseMetricId } from '../../lib/utils/validators.js'; @@ -17,7 +17,7 @@ export const followCommand = new Command('follow') const globalOptions = getGlobalOptions(followCommand); const client = await getAPIClientFromOptions(globalOptions); await followMetric(client, { id }); - console.log(chalk.green(`✓ Now following metric ${id}`)); + printResult(globalOptions, { message: `✓ Now following metric ${id}`, id }); }) ); @@ -29,6 +29,6 @@ export const unfollowCommand = new Command('unfollow') const globalOptions = getGlobalOptions(unfollowCommand); const client = await getAPIClientFromOptions(globalOptions); await unfollowMetric(client, { id }); - console.log(chalk.green(`✓ No longer following metric ${id}`)); + printResult(globalOptions, { message: `✓ No longer following metric ${id}`, id }); }) ); diff --git a/src/commands/metrics/index.ts b/src/commands/metrics/index.ts index baba1e0..1d03f8a 100644 --- a/src/commands/metrics/index.ts +++ b/src/commands/metrics/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseMetricId } from '../../lib/utils/validators.js'; @@ -284,9 +284,17 @@ const createCommand = addMetricFieldOptions( const newId = versionResult.data.id; if (options.activate) { await client.activateMetric(newId as MetricId, options.reason); - console.log(chalk.green(`✓ New metric version created and activated (id: ${newId})`)); + printResult(globalOptions, { + message: `✓ New metric version created and activated (id: ${newId})`, + id: newId, + raw: versionResult.data, + }); } else { - console.log(chalk.green(`✓ New metric version created as draft (id: ${newId})`)); + printResult(globalOptions, { + message: `✓ New metric version created as draft (id: ${newId})`, + id: newId, + raw: versionResult.data, + }); } return; } @@ -318,9 +326,17 @@ const createCommand = addMetricFieldOptions( if (options.activate) { await client.activateMetric(result.data.id as MetricId, 'Initial activation'); - console.log(chalk.green(`✓ Metric created and activated with ID: ${result.data.id}`)); + printResult(globalOptions, { + message: `✓ Metric created and activated with ID: ${result.data.id}`, + id: result.data.id, + raw: result.data, + }); } else { - console.log(chalk.green(`✓ Metric created with ID: ${result.data.id}`)); + printResult(globalOptions, { + message: `✓ Metric created with ID: ${result.data.id}`, + id: result.data.id, + raw: result.data, + }); } }) ); @@ -344,7 +360,7 @@ const updateCommand = new Command('update') description: options.description, owner: options.owner, }); - console.log(chalk.green(`✓ Metric ${id} updated`)); + printResult(globalOptions, { message: `✓ Metric ${id} updated`, id }); }) ); @@ -359,7 +375,7 @@ const archiveCommand = new Command('archive') await archiveMetric(client, { id, unarchive: options.unarchive }); const action = options.unarchive ? 'unarchived' : 'archived'; - console.log(chalk.green(`✓ Metric ${id} ${action}`)); + printResult(globalOptions, { message: `✓ Metric ${id} ${action}`, id }); }) ); @@ -373,7 +389,7 @@ const activateCommand = new Command('activate') const client = await getAPIClientFromOptions(globalOptions); await activateMetric(client, { id, reason: options.reason }); - console.log(chalk.green(`✓ Metric ${id} activated`)); + printResult(globalOptions, { message: `✓ Metric ${id} activated`, id }); }) ); @@ -406,11 +422,17 @@ const versionCommand = addMetricFieldOptions( const newId = result.data.id; if (options.activate) { await client.activateMetric(newId as MetricId, options.reason); - console.log( - chalk.green(`✓ New version of metric ${id} created and activated (new id: ${newId})`) - ); + printResult(globalOptions, { + message: `✓ New version of metric ${id} created and activated (new id: ${newId})`, + id: newId, + raw: result.data, + }); } else { - console.log(chalk.green(`✓ New draft version of metric ${id} created (new id: ${newId})`)); + printResult(globalOptions, { + message: `✓ New draft version of metric ${id} created (new id: ${newId})`, + id: newId, + raw: result.data, + }); } }) ); diff --git a/src/commands/metrics/review.ts b/src/commands/metrics/review.ts index 40dacd8..324cc35 100644 --- a/src/commands/metrics/review.ts +++ b/src/commands/metrics/review.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseMetricId } from '../../lib/utils/validators.js'; @@ -39,7 +39,7 @@ const requestCommand = new Command('request') const globalOptions = getGlobalOptions(requestCommand); const client = await getAPIClientFromOptions(globalOptions); await requestMetricReview(client, { id }); - console.log(chalk.green(`✓ Review requested for metric ${id}`)); + printResult(globalOptions, { message: `✓ Review requested for metric ${id}`, id }); }) ); @@ -51,7 +51,7 @@ const approveCommand = new Command('approve') const globalOptions = getGlobalOptions(approveCommand); const client = await getAPIClientFromOptions(globalOptions); await approveMetricReview(client, { id }); - console.log(chalk.green(`✓ Metric ${id} review approved`)); + printResult(globalOptions, { message: `✓ Metric ${id} review approved`, id }); }) ); @@ -76,7 +76,7 @@ const commentCommand = new Command('comment') const globalOptions = getGlobalOptions(commentCommand); const client = await getAPIClientFromOptions(globalOptions); await addMetricReviewComment(client, { id, message: options.message }); - console.log(chalk.green(`✓ Comment added to metric ${id} review`)); + printResult(globalOptions, { message: `✓ Comment added to metric ${id} review`, id }); }) ); @@ -90,7 +90,10 @@ const replyCommand = new Command('reply') const globalOptions = getGlobalOptions(replyCommand); const client = await getAPIClientFromOptions(globalOptions); await replyToMetricReviewComment(client, { id, commentId, message: options.message }); - console.log(chalk.green(`✓ Reply added to comment ${commentId} on metric ${id} review`)); + printResult(globalOptions, { + message: `✓ Reply added to comment ${commentId} on metric ${id} review`, + id, + }); }) ); diff --git a/src/commands/metrictags/index.ts b/src/commands/metrictags/index.ts index d80354e..0e20273 100644 --- a/src/commands/metrictags/index.ts +++ b/src/commands/metrictags/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseTagId } from '../../lib/utils/validators.js'; @@ -56,8 +56,12 @@ const createCommand = new Command('create') const globalOptions = getGlobalOptions(createCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await createMetricTag(client, { tag: options.tag }); - console.log(chalk.green('Metric tag created successfully')); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: 'Metric tag created successfully', + id: data?.id, + raw: result.data, + }); }) ); @@ -70,8 +74,12 @@ const updateCommand = new Command('update') const globalOptions = getGlobalOptions(updateCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await updateMetricTag(client, { id, tag: options.tag }); - console.log(chalk.green('Metric tag updated successfully')); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: 'Metric tag updated successfully', + id: data?.id ?? id, + raw: result.data, + }); }) ); @@ -83,7 +91,7 @@ const deleteCommand = new Command('delete') const globalOptions = getGlobalOptions(deleteCommand); const client = await getAPIClientFromOptions(globalOptions); await deleteMetricTag(client, { id }); - console.log(chalk.green('Metric tag deleted successfully')); + printResult(globalOptions, { message: 'Metric tag deleted successfully', id }); }) ); diff --git a/src/commands/notifications/index.ts b/src/commands/notifications/index.ts index 58841cc..d0af5cc 100644 --- a/src/commands/notifications/index.ts +++ b/src/commands/notifications/index.ts @@ -4,6 +4,7 @@ import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { @@ -37,7 +38,7 @@ const markSeenCommand = new Command('mark-seen') const globalOptions = getGlobalOptions(markSeenCommand); const client = await getAPIClientFromOptions(globalOptions); await coreMarkNotificationsSeen(client); - console.log(chalk.green('✓ All notifications marked as seen')); + printResult(globalOptions, { message: '✓ All notifications marked as seen' }); }) ); @@ -52,7 +53,7 @@ const markReadCommand = new Command('mark-read') ? options.ids.split(',').map((s: string) => parseInt(s.trim(), 10)) : undefined; await coreMarkNotificationsRead(client, { ids }); - console.log(chalk.green('✓ Notifications marked as read')); + printResult(globalOptions, { message: '✓ Notifications marked as read' }); }) ); diff --git a/src/commands/platformconfig/index.ts b/src/commands/platformconfig/index.ts index a2e01ec..7ca369f 100644 --- a/src/commands/platformconfig/index.ts +++ b/src/commands/platformconfig/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { validateJSON } from '../../lib/utils/validators.js'; @@ -48,8 +48,11 @@ const updateCommand = new Command('update') const client = await getAPIClientFromOptions(globalOptions); const value = validateJSON(options.value, '--value') as Record; const result = await updatePlatformConfig(client, { id, value }); - console.log(chalk.green(`✓ Platform config ${id} updated`)); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: `✓ Platform config ${id} updated`, + id, + raw: result.data, + }); }) ); diff --git a/src/commands/roles/index.ts b/src/commands/roles/index.ts index e07bc4f..9677a50 100644 --- a/src/commands/roles/index.ts +++ b/src/commands/roles/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseRoleId } from '../../lib/utils/validators.js'; @@ -51,9 +51,12 @@ const createCommand = new Command('create') name: options.name, description: options.description, }); - console.log( - chalk.green(`✓ Role created with ID: ${(result.data as Record).id}`) - ); + const newId = (result.data as Record).id; + printResult(globalOptions, { + message: `✓ Role created with ID: ${newId}`, + id: newId, + raw: result.data, + }); }) ); @@ -71,7 +74,7 @@ const updateCommand = new Command('update') name: options.name, description: options.description, }); - console.log(chalk.green(`✓ Role ${id} updated`)); + printResult(globalOptions, { message: `✓ Role ${id} updated`, id }); }) ); @@ -83,7 +86,7 @@ const deleteCommand = new Command('delete') const globalOptions = getGlobalOptions(deleteCommand); const client = await getAPIClientFromOptions(globalOptions); await deleteRole(client, { id }); - console.log(chalk.green(`✓ Role ${id} deleted`)); + printResult(globalOptions, { message: `✓ Role ${id} deleted`, id }); }) ); diff --git a/src/commands/segments/index.ts b/src/commands/segments/index.ts index 34884b7..dc23287 100644 --- a/src/commands/segments/index.ts +++ b/src/commands/segments/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseSegmentId } from '../../lib/utils/validators.js'; @@ -67,9 +67,12 @@ const createCommand = new Command('create') attribute: options.attribute, description: options.description, }); - console.log( - chalk.green(`✓ Segment created with ID: ${(result.data as Record).id}`) - ); + const newId = (result.data as Record).id; + printResult(globalOptions, { + message: `✓ Segment created with ID: ${newId}`, + id: newId, + raw: result.data, + }); }) ); @@ -87,7 +90,7 @@ const updateCommand = new Command('update') displayName: options.displayName, description: options.description, }); - console.log(chalk.green(`✓ Segment ${id} updated`)); + printResult(globalOptions, { message: `✓ Segment ${id} updated`, id }); }) ); @@ -99,7 +102,7 @@ const deleteCommand = new Command('delete') const globalOptions = getGlobalOptions(deleteCommand); const client = await getAPIClientFromOptions(globalOptions); await deleteSegment(client, { id }); - console.log(chalk.green(`✓ Segment ${id} deleted`)); + printResult(globalOptions, { message: `✓ Segment ${id} deleted`, id }); }) ); diff --git a/src/commands/storageconfigs/index.ts b/src/commands/storageconfigs/index.ts index ae4f656..1f2c309 100644 --- a/src/commands/storageconfigs/index.ts +++ b/src/commands/storageconfigs/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { validateJSON } from '../../lib/utils/validators.js'; @@ -49,8 +49,12 @@ const createCommand = new Command('create') const client = await getAPIClientFromOptions(globalOptions); const config = validateJSON(options.jsonConfig, '--json-config') as Record; const result = await createStorageConfig(client, { config }); - console.log(chalk.green(`✓ Storage config created`)); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: `✓ Storage config created`, + id: data?.id, + raw: result.data, + }); }) ); @@ -64,8 +68,11 @@ const updateCommand = new Command('update') const client = await getAPIClientFromOptions(globalOptions); const config = validateJSON(options.jsonConfig, '--json-config') as Record; const result = await updateStorageConfig(client, { id, config }); - console.log(chalk.green(`✓ Storage config ${id} updated`)); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: `✓ Storage config ${id} updated`, + id, + raw: result.data, + }); }) ); @@ -78,7 +85,7 @@ const testCommand = new Command('test') const client = await getAPIClientFromOptions(globalOptions); const config = validateJSON(options.jsonConfig, '--json-config') as Record; await testStorageConfig(client, { config }); - console.log(chalk.green(`✓ Storage config connection test passed`)); + printResult(globalOptions, { message: `✓ Storage config connection test passed` }); }) ); diff --git a/src/commands/storageconfigs/storageconfigs.test.ts b/src/commands/storageconfigs/storageconfigs.test.ts index bfa5e47..ab0af90 100644 --- a/src/commands/storageconfigs/storageconfigs.test.ts +++ b/src/commands/storageconfigs/storageconfigs.test.ts @@ -73,7 +73,7 @@ describe('storage-configs command', () => { ]); expect(mockClient.createStorageConfig).toHaveBeenCalledWith({ type: 's3' }); - expect(printFormatted).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Storage config created')); }); it('should update a storage config', async () => { @@ -87,7 +87,7 @@ describe('storage-configs command', () => { ]); expect(mockClient.updateStorageConfig).toHaveBeenCalledWith(1, { bucket: 'new' }); - expect(printFormatted).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Storage config 1 updated')); }); it('should test a storage config', async () => { diff --git a/src/commands/tags/index.ts b/src/commands/tags/index.ts index 8e80802..9abc3b3 100644 --- a/src/commands/tags/index.ts +++ b/src/commands/tags/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseTagId } from '../../lib/utils/validators.js'; @@ -50,8 +50,12 @@ const createCommand = new Command('create') const globalOptions = getGlobalOptions(createCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await createTag(client, { tag: options.tag }); - console.log(chalk.green('Experiment tag created successfully')); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: 'Experiment tag created successfully', + id: data?.id, + raw: result.data, + }); }) ); @@ -64,8 +68,11 @@ const updateCommand = new Command('update') const globalOptions = getGlobalOptions(updateCommand); const client = await getAPIClientFromOptions(globalOptions); const result = await updateTag(client, { id, tag: options.tag }); - console.log(chalk.green('Experiment tag updated successfully')); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: 'Experiment tag updated successfully', + id, + raw: result.data, + }); }) ); @@ -77,7 +84,7 @@ const deleteCommand = new Command('delete') const globalOptions = getGlobalOptions(deleteCommand); const client = await getAPIClientFromOptions(globalOptions); await deleteTag(client, { id }); - console.log(chalk.green('Experiment tag deleted successfully')); + printResult(globalOptions, { message: 'Experiment tag deleted successfully', id }); }) ); diff --git a/src/commands/teams/index.ts b/src/commands/teams/index.ts index a63a66c..70a6aff 100644 --- a/src/commands/teams/index.ts +++ b/src/commands/teams/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseTeamId } from '../../lib/utils/validators.js'; @@ -62,9 +62,12 @@ const createCommand = new Command('create') name: options.name, description: options.description, }); - console.log( - chalk.green(`✓ Team created with ID: ${(result.data as Record).id}`) - ); + const newId = (result.data as Record).id; + printResult(globalOptions, { + message: `✓ Team created with ID: ${newId}`, + id: newId, + raw: result.data, + }); }) ); @@ -77,7 +80,7 @@ const updateCommand = new Command('update') const globalOptions = getGlobalOptions(updateCommand); const client = await getAPIClientFromOptions(globalOptions); await updateTeam(client, { id, description: options.description }); - console.log(chalk.green(`✓ Team ${id} updated`)); + printResult(globalOptions, { message: `✓ Team ${id} updated`, id }); }) ); @@ -91,7 +94,7 @@ const archiveCommand = new Command('archive') const client = await getAPIClientFromOptions(globalOptions); await archiveTeam(client, { id, unarchive: options.unarchive }); const action = options.unarchive ? 'unarchived' : 'archived'; - console.log(chalk.green(`✓ Team ${id} ${action}`)); + printResult(globalOptions, { message: `✓ Team ${id} ${action}`, id }); }) ); diff --git a/src/commands/teams/members.ts b/src/commands/teams/members.ts index 31a3b8e..7cf76d0 100644 --- a/src/commands/teams/members.ts +++ b/src/commands/teams/members.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseTeamId } from '../../lib/utils/validators.js'; @@ -69,7 +69,10 @@ const addMembersCommand = new Command('add') const userIds = await resolveUserValues(client, options.users); const roleIds = options.roles ? await resolveRoleValues(client, options.roles) : undefined; await addTeamMembers(client, { id, userIds, roleIds }); - console.log(chalk.green(`✓ Added ${userIds.length} member(s) to team ${id}`)); + printResult(globalOptions, { + message: `✓ Added ${userIds.length} member(s) to team ${id}`, + id, + }); }) ); @@ -85,7 +88,10 @@ const editRolesCommand = new Command('edit-roles') const userIds = await resolveUserValues(client, options.users); const roleIds = await resolveRoleValues(client, options.roles); await editTeamMemberRoles(client, { id, userIds, roleIds }); - console.log(chalk.green(`✓ Updated roles for ${userIds.length} member(s) in team ${id}`)); + printResult(globalOptions, { + message: `✓ Updated roles for ${userIds.length} member(s) in team ${id}`, + id, + }); }) ); @@ -99,7 +105,10 @@ const removeMembersCommand = new Command('remove') const client = await getAPIClientFromOptions(globalOptions); const userIds = await resolveUserValues(client, options.users); await removeTeamMembers(client, { id, userIds }); - console.log(chalk.green(`✓ Removed ${userIds.length} member(s) from team ${id}`)); + printResult(globalOptions, { + message: `✓ Removed ${userIds.length} member(s) from team ${id}`, + id, + }); }) ); diff --git a/src/commands/units/index.ts b/src/commands/units/index.ts index 7ddbcb8..fcade15 100644 --- a/src/commands/units/index.ts +++ b/src/commands/units/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseUnitTypeId } from '../../lib/utils/validators.js'; @@ -52,9 +52,12 @@ const createCommand = new Command('create') name: options.name, description: options.description, }); - console.log( - chalk.green(`✓ Unit type created with ID: ${(result.data as Record).id}`) - ); + const newId = (result.data as Record).id; + printResult(globalOptions, { + message: `✓ Unit type created with ID: ${newId}`, + id: newId, + raw: result.data, + }); }) ); @@ -68,7 +71,7 @@ const updateCommand = new Command('update') const globalOptions = getGlobalOptions(updateCommand); const client = await getAPIClientFromOptions(globalOptions); await updateUnit(client, { id, name: options.name, description: options.description }); - console.log(chalk.green(`✓ Unit type ${id} updated`)); + printResult(globalOptions, { message: `✓ Unit type ${id} updated`, id }); }) ); @@ -82,7 +85,7 @@ const archiveCommand = new Command('archive') const client = await getAPIClientFromOptions(globalOptions); await archiveUnit(client, { id, unarchive: options.unarchive }); const action = options.unarchive ? 'unarchived' : 'archived'; - console.log(chalk.green(`✓ Unit type ${id} ${action}`)); + printResult(globalOptions, { message: `✓ Unit type ${id} ${action}`, id }); }) ); diff --git a/src/commands/updateschedules/index.ts b/src/commands/updateschedules/index.ts index d24f820..753ae6e 100644 --- a/src/commands/updateschedules/index.ts +++ b/src/commands/updateschedules/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseUpdateScheduleId, validateJSON } from '../../lib/utils/validators.js'; @@ -50,8 +50,12 @@ const createCommand = new Command('create') const client = await getAPIClientFromOptions(globalOptions); const config = validateJSON(options.jsonConfig, '--json-config') as Record; const result = await createUpdateSchedule(client, { config }); - console.log(chalk.green(`✓ Update schedule created`)); - printFormatted(result.data, globalOptions); + const data = result.data as { id?: unknown } | undefined; + printResult(globalOptions, { + message: `✓ Update schedule created`, + id: data?.id, + raw: result.data, + }); }) ); @@ -65,8 +69,11 @@ const updateCommand = new Command('update') const client = await getAPIClientFromOptions(globalOptions); const config = validateJSON(options.jsonConfig, '--json-config') as Record; const result = await updateUpdateSchedule(client, { id, config }); - console.log(chalk.green(`✓ Update schedule ${id} updated`)); - printFormatted(result.data, globalOptions); + printResult(globalOptions, { + message: `✓ Update schedule ${id} updated`, + id, + raw: result.data, + }); }) ); @@ -78,7 +85,7 @@ const deleteCommand = new Command('delete') const globalOptions = getGlobalOptions(deleteCommand); const client = await getAPIClientFromOptions(globalOptions); await deleteUpdateSchedule(client, { id }); - console.log(chalk.green(`✓ Update schedule ${id} deleted`)); + printResult(globalOptions, { message: `✓ Update schedule ${id} deleted`, id }); }) ); diff --git a/src/commands/users/api-keys.ts b/src/commands/users/api-keys.ts index 6d6c58d..552aa42 100644 --- a/src/commands/users/api-keys.ts +++ b/src/commands/users/api-keys.ts @@ -4,6 +4,7 @@ import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { addPaginationOptions, printPaginationFooter } from '../../lib/utils/pagination.js'; @@ -72,9 +73,19 @@ const createCommand = new Command('create') name: options.name, description: options.description, }); - console.log(chalk.green(`✓ API key created: ${result.data.name}`)); - console.log(` Key: ${result.data.key}`); - console.log(chalk.yellow(' Save this key now — it cannot be retrieved later.')); + const format = globalOptions.output ?? 'table'; + if (format === 'table' || format === 'rendered') { + console.log(chalk.green(`✓ API key created: ${result.data.name}`)); + console.log(` Key: ${result.data.key}`); + console.log(chalk.yellow(' Save this key now — it cannot be retrieved later.')); + } else { + const data = result.data as Record; + printResult(globalOptions, { + message: `✓ API key created: ${result.data.name}`, + id: data.id, + raw: result.data, + }); + } }) ); @@ -94,7 +105,7 @@ const updateCommand = new Command('update') name: options.name, description: options.description, }); - console.log(chalk.green(`✓ API key ${id} updated`)); + printResult(globalOptions, { message: `✓ API key ${id} updated`, id }); }) ); @@ -110,7 +121,7 @@ const deleteCommand = new Command('delete') userRef: options.user as string, keyId: id, }); - console.log(chalk.green(`✓ API key ${id} deleted`)); + printResult(globalOptions, { message: `✓ API key ${id} deleted`, id }); }) ); diff --git a/src/commands/users/index.ts b/src/commands/users/index.ts index 780bbf2..ecfe678 100644 --- a/src/commands/users/index.ts +++ b/src/commands/users/index.ts @@ -5,6 +5,7 @@ import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, resolveAPIKey, resolveEndpoint, withErrorHandling, @@ -245,7 +246,11 @@ const createCommand = new Command('create') role: options.role, }); - console.log(chalk.green(`✓ User created with ID: ${result.data.id}`)); + printResult(globalOptions, { + message: `✓ User created with ID: ${result.data.id}`, + id: result.data.id, + raw: result.data, + }); }) ); @@ -264,7 +269,7 @@ const updateCommand = new Command('update') name: options.name, role: options.role, }); - console.log(chalk.green(`✓ User ${id} updated`)); + printResult(globalOptions, { message: `✓ User ${id} updated`, id }); }) ); @@ -279,7 +284,7 @@ const archiveCommand = new Command('archive') await coreArchiveUser(client, { id, unarchive: options.unarchive }); const action = options.unarchive ? 'unarchived' : 'archived'; - console.log(chalk.green(`✓ User ${id} ${action}`)); + printResult(globalOptions, { message: `✓ User ${id} ${action}`, id }); }) ); diff --git a/src/commands/users/reset-password.ts b/src/commands/users/reset-password.ts index 822d984..4d5d236 100644 --- a/src/commands/users/reset-password.ts +++ b/src/commands/users/reset-password.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseUserId } from '../../lib/utils/validators.js'; @@ -18,8 +19,17 @@ export const resetPasswordCommand = new Command('reset-password') const client = await getAPIClientFromOptions(globalOptions); const result = await resetUserPassword(client, { id }); - console.log(chalk.green(`✓ Password reset for user ${id}`)); - console.log(` New password: ${result.data.password}`); - console.log(chalk.yellow(' Save this password — it cannot be retrieved later.')); + const format = globalOptions.output ?? 'table'; + if (format === 'table' || format === 'rendered') { + console.log(chalk.green(`✓ Password reset for user ${id}`)); + console.log(` New password: ${result.data.password}`); + console.log(chalk.yellow(' Save this password — it cannot be retrieved later.')); + } else { + printResult(globalOptions, { + message: `✓ Password reset for user ${id}`, + id, + raw: result.data, + }); + } }) ); diff --git a/src/commands/webhooks/index.ts b/src/commands/webhooks/index.ts index 7bca1ee..4d78a9b 100644 --- a/src/commands/webhooks/index.ts +++ b/src/commands/webhooks/index.ts @@ -1,9 +1,9 @@ import { Command } from 'commander'; -import chalk from 'chalk'; import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, withErrorHandling, } from '../../lib/utils/api-helper.js'; import { parseWebhookId } from '../../lib/utils/validators.js'; @@ -66,9 +66,12 @@ const createCommand = new Command('create') ordered: options.ordered, maxRetries: options.maxRetries, }); - console.log( - chalk.green(`✓ Webhook created with ID: ${(result.data as Record).id}`) - ); + const newId = (result.data as Record).id; + printResult(globalOptions, { + message: `✓ Webhook created with ID: ${newId}`, + id: newId, + raw: result.data, + }); }) ); @@ -94,7 +97,7 @@ const updateCommand = new Command('update') ordered: options.ordered, maxRetries: options.maxRetries, }); - console.log(chalk.green(`✓ Webhook ${id} updated`)); + printResult(globalOptions, { message: `✓ Webhook ${id} updated`, id }); }) ); @@ -106,7 +109,7 @@ const deleteCommand = new Command('delete') const globalOptions = getGlobalOptions(deleteCommand); const client = await getAPIClientFromOptions(globalOptions); await deleteWebhook(client, { id }); - console.log(chalk.green(`✓ Webhook ${id} deleted`)); + printResult(globalOptions, { message: `✓ Webhook ${id} deleted`, id }); }) ); diff --git a/src/lib/utils/api-helper.test.ts b/src/lib/utils/api-helper.test.ts index d38aeeb..240c1e0 100644 --- a/src/lib/utils/api-helper.test.ts +++ b/src/lib/utils/api-helper.test.ts @@ -23,6 +23,7 @@ import { getAPIClientFromOptions, getGlobalOptions, printFormatted, + printResult, shouldOutputIdsOnly, withErrorHandling, resolveAuth, @@ -391,6 +392,151 @@ describe('API Helper', () => { }); }); + describe('printResult', () => { + let consoleSpy: ReturnType; + let consoleErrorSpy: ReturnType; + + beforeEach(() => { + consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + setTTYOverride({ stdin: true, stdout: true }); + }); + + afterEach(() => { + consoleSpy.mockRestore(); + consoleErrorSpy.mockRestore(); + setTTYOverride({ stdin: true, stdout: true }); + }); + + it('prints the chalk success message for table output (default)', () => { + printResult( + { output: 'table', outputExplicit: false, noColor: true }, + { message: '✓ Metric 42 updated', id: 42 } + ); + + expect(formatOutput).not.toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledOnce(); + const printed = consoleSpy.mock.calls[0]?.[0] as string; + expect(printed).toContain('✓ Metric 42 updated'); + }); + + it('routes through printFormatted with {success,message,id} for json', () => { + printResult( + { output: 'json', outputExplicit: true, noColor: true }, + { message: '✓ Metric created with ID: 42', id: 42 } + ); + + expect(formatOutput).toHaveBeenCalledOnce(); + expect(formatOutput).toHaveBeenCalledWith( + { success: true, message: '✓ Metric created with ID: 42', id: 42 }, + 'json', + expect.any(Object) + ); + }); + + it('omits id from the JSON payload when no id is provided', () => { + printResult( + { output: 'json', outputExplicit: true, noColor: true }, + { message: '✓ Datasource connection test passed' } + ); + + expect(formatOutput).toHaveBeenCalledWith( + { success: true, message: '✓ Datasource connection test passed' }, + 'json', + expect.any(Object) + ); + }); + + it('emits the raw API response when --raw is set and raw is supplied', () => { + const raw = { id: 42, name: 'foo', state: 'created' }; + printResult( + { output: 'json', outputExplicit: true, noColor: true, raw: true }, + { message: '✓ Metric created', id: 42, raw } + ); + + expect(formatOutput).toHaveBeenCalledWith(raw, 'json', expect.any(Object)); + }); + + it('falls back to the minimal payload when --raw is set but no raw is supplied', () => { + printResult( + { output: 'json', outputExplicit: true, noColor: true, raw: true }, + { message: '✓ Metric 42 updated', id: 42 } + ); + + expect(formatOutput).toHaveBeenCalledWith( + { success: true, message: '✓ Metric 42 updated', id: 42 }, + 'json', + expect.any(Object) + ); + }); + + it('routes structured outputs (yaml, markdown, plain, vertical, template) through printFormatted', () => { + for (const format of ['yaml', 'markdown', 'plain', 'vertical', 'template'] as const) { + vi.mocked(formatOutput).mockClear(); + printResult( + { output: format, outputExplicit: true, noColor: true }, + { message: '✓ X done', id: 1 } + ); + expect(formatOutput).toHaveBeenCalledOnce(); + } + }); + + it('prints only the id when output is "ids" (and stays quiet on stderr)', () => { + printResult( + { output: 'ids', outputExplicit: true }, + { message: '✓ Metric 42 updated', id: 42 } + ); + + expect(formatOutput).not.toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalledWith(42); + expect(consoleErrorSpy).not.toHaveBeenCalled(); + }); + + it('prints id to stdout and the green confirmation to stderr when piped without explicit -o', () => { + setTTYOverride({ stdout: false }); + printResult( + { output: 'table', outputExplicit: false, noColor: true }, + { message: '✓ Metric created with ID: 42', id: 42 } + ); + + expect(consoleSpy).toHaveBeenCalledWith(42); + const stderrMessage = consoleErrorSpy.mock.calls[0]?.[0] as string; + expect(stderrMessage).toContain('✓ Metric created with ID: 42'); + expect(formatOutput).not.toHaveBeenCalled(); + }); + + it('writes nothing on stdout but still confirms on stderr when piped without an id', () => { + // No id available (e.g. connection test): stdout stays empty so a + // downstream `xargs` doesn't choke, but the user still gets the + // confirmation on stderr. + setTTYOverride({ stdout: false }); + printResult( + { output: 'table', outputExplicit: false, noColor: true }, + { message: '✓ Datasource connection test passed' } + ); + + expect(consoleSpy).not.toHaveBeenCalled(); + const stderrMessage = consoleErrorSpy.mock.calls[0]?.[0] as string; + expect(stderrMessage).toContain('✓ Datasource connection test passed'); + expect(formatOutput).not.toHaveBeenCalled(); + }); + + it('still emits JSON when piped if -o json was set explicitly', () => { + setTTYOverride({ stdout: false }); + printResult( + { output: 'json', outputExplicit: true, noColor: true }, + { message: '✓ Metric 42 updated', id: 42 } + ); + + expect(formatOutput).toHaveBeenCalledWith( + { success: true, message: '✓ Metric 42 updated', id: 42 }, + 'json', + expect.any(Object) + ); + expect(consoleErrorSpy).not.toHaveBeenCalled(); + }); + }); + describe('withErrorHandling', () => { let consoleErrorSpy: ReturnType; let processExitSpy: ReturnType; diff --git a/src/lib/utils/api-helper.ts b/src/lib/utils/api-helper.ts index e5514be..86cd82a 100644 --- a/src/lib/utils/api-helper.ts +++ b/src/lib/utils/api-helper.ts @@ -3,6 +3,7 @@ import { getProfile, loadConfig } from '../config/config.js'; import { getAPIKey, getOAuthToken } from '../config/keyring.js'; import type { AuthConfig } from '../api/axios-adapter.js'; import { Command } from 'commander'; +import chalk from 'chalk'; import { formatOutput, type OutputFormat } from '../output/formatter.js'; import { isStdoutPiped } from './stdin.js'; @@ -196,6 +197,66 @@ export function printFormatted(data: unknown, globalOptions: GlobalOptions): voi console.log(output); } +// Formats that should render structured machine output instead of the +// human-readable green checkmark. `table` and `rendered` keep the +// interactive success message; everything machine-oriented (json, yaml, +// markdown, plain, vertical, template) goes through printFormatted. +const STRUCTURED_FORMATS: ReadonlySet = new Set([ + 'json', + 'yaml', + 'markdown', + 'plain', + 'vertical', + 'template', +]); + +// Print the outcome of a mutation (create/update/delete/archive/etc). +// Honors --output and the same pipe-falls-back-to-ids rule the list +// commands use, so a single helper covers every command type. +// +// Behavior by mode: +// - structured --output (json/yaml/markdown/plain/vertical/template): +// emits the structured payload to stdout. With --raw + a provided +// raw response, that response is used verbatim; otherwise the +// minimal { success, message, id? } is rendered. +// - explicit --output ids: bare id to stdout (or silent if no id). +// - stdout piped without explicit --output: bare id to stdout for +// chaining, the green confirmation message to stderr so the user +// still sees what happened. +// - interactive default: chalk.green(message) to stdout. +export function printResult( + globalOptions: GlobalOptions, + args: { message: string; id?: unknown; raw?: unknown } +): void { + const format = (globalOptions.output ?? 'table') as OutputFormat; + + if (globalOptions.outputExplicit && STRUCTURED_FORMATS.has(format)) { + const payload = + globalOptions.raw && args.raw !== undefined + ? args.raw + : { + success: true, + message: args.message, + ...(args.id !== undefined ? { id: args.id } : {}), + }; + printFormatted(payload, globalOptions); + return; + } + + if (shouldOutputIdsOnly(globalOptions)) { + if (args.id !== undefined) console.log(args.id); + // Piped (no explicit format) → also surface the confirmation on + // stderr so users running `cmd | xargs ...` still see success. + // Explicit `-o ids` stays quiet to keep the stream machine-clean. + if (isStdoutPiped() && !globalOptions.outputExplicit) { + console.error(chalk.green(args.message)); + } + return; + } + + console.log(chalk.green(args.message)); +} + function handleCommandError(error: unknown): never { console.error('Error:', error instanceof Error ? error.message : error); const apiError = error as { statusCode?: number; response?: unknown };