diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index 103cc03d..d53e5426 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -248,6 +248,7 @@ export class Source extends SfCommand { const stages = new DeployStages({ title: 'Deleting Metadata', jsonEnabled: this.jsonEnabled(), + verbose: this.flags['verbose'], }); const isRest = (await resolveApi()) === API['REST']; diff --git a/src/commands/project/deploy/resume.ts b/src/commands/project/deploy/resume.ts index f24fcd26..143c7cda 100644 --- a/src/commands/project/deploy/resume.ts +++ b/src/commands/project/deploy/resume.ts @@ -134,6 +134,7 @@ export default class DeployMetadataResume extends SfCommand { new DeployStages({ title: 'Resuming Deploy', jsonEnabled: this.jsonEnabled(), + verbose: flags.verbose, }).start( { deploy, diff --git a/src/commands/project/deploy/start.ts b/src/commands/project/deploy/start.ts index e0133378..559bd94e 100644 --- a/src/commands/project/deploy/start.ts +++ b/src/commands/project/deploy/start.ts @@ -250,6 +250,7 @@ export default class DeployMetadata extends SfCommand { this.stages = new DeployStages({ title, jsonEnabled: this.jsonEnabled(), + verbose: flags['verbose'], }); this.deployUrl = buildDeployUrl(flags['target-org'], deploy.id); diff --git a/src/commands/project/deploy/validate.ts b/src/commands/project/deploy/validate.ts index 1f52f422..3bb15b57 100644 --- a/src/commands/project/deploy/validate.ts +++ b/src/commands/project/deploy/validate.ts @@ -211,6 +211,7 @@ export default class DeployMetadataValidate extends SfCommand new DeployStages({ title: 'Validating Deployment', jsonEnabled: this.jsonEnabled(), + verbose: flags.verbose, }).start( { deploy, diff --git a/src/formatters/testResultsFormatter.ts b/src/formatters/testResultsFormatter.ts index c675055d..67513d6f 100644 --- a/src/formatters/testResultsFormatter.ts +++ b/src/formatters/testResultsFormatter.ts @@ -18,9 +18,10 @@ import { Successes, } from '@salesforce/source-deploy-retrieve'; import { ensureArray } from '@salesforce/kit'; -import { isTruthy, TestLevel, Verbosity } from '../utils/types.js'; +import { TestLevel, Verbosity } from '../utils/types.js'; import { tableHeader, error, success, check } from '../utils/output.js'; import { coverageOutput } from '../utils/coverage.js'; +import { isCI } from '../utils/deployStages.js'; const ux = new Ux(); @@ -45,12 +46,14 @@ export class TestResultsFormatter { return; } - if (!isTruthy(process.env.CI)) { + if (!isCI()) { displayVerboseTestFailures(this.result.response); } if (this.verbosity === 'verbose') { - displayVerboseTestSuccesses(this.result.response.details.runTestResult?.successes); + if (!isCI()) { + displayVerboseTestSuccesses(this.result.response.details.runTestResult?.successes); + } displayVerboseTestCoverage(this.result.response.details.runTestResult?.codeCoverage); } @@ -124,7 +127,7 @@ const displayVerboseTestCoverage = (coverage?: CodeCoverage | CodeCoverage[]): v } }; -const testResultSort = (a: T, b: T): number => +export const testResultSort = (a: T, b: T): number => a.methodName === b.methodName ? a.name.localeCompare(b.name) : a.methodName.localeCompare(b.methodName); const coverageSort = (a: CodeCoverage, b: CodeCoverage): number => diff --git a/src/utils/deployStages.ts b/src/utils/deployStages.ts index 2d7c605c..73615e5f 100644 --- a/src/utils/deployStages.ts +++ b/src/utils/deployStages.ts @@ -7,18 +7,13 @@ import os from 'node:os'; import { MultiStageOutput } from '@oclif/multi-stage-output'; import { Lifecycle, Messages } from '@salesforce/core'; -import { - Failures, - MetadataApiDeploy, - MetadataApiDeployStatus, - RequestStatus, - Successes, -} from '@salesforce/source-deploy-retrieve'; +import { MetadataApiDeploy, MetadataApiDeployStatus, RequestStatus } from '@salesforce/source-deploy-retrieve'; import { SourceMemberPollingEvent } from '@salesforce/source-tracking'; import terminalLink from 'terminal-link'; import { ensureArray } from '@salesforce/kit'; import ansis from 'ansis'; -import { getZipFileSize } from './output.js'; +import { testResultSort } from '../formatters/testResultsFormatter.js'; +import { check, getZipFileSize } from './output.js'; import { isTruthy } from './types.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); @@ -27,6 +22,7 @@ const mdTransferMessages = Messages.loadMessages('@salesforce/plugin-deploy-retr type Options = { title: string; jsonEnabled: boolean; + verbose?: boolean; }; type Data = { @@ -58,7 +54,7 @@ function formatProgress(current: number, total: number): string { export class DeployStages { private mso: MultiStageOutput; - public constructor({ title, jsonEnabled }: Options) { + public constructor({ title, jsonEnabled, verbose }: Options) { this.mso = new MultiStageOutput({ title, stages: [ @@ -142,7 +138,8 @@ export class DeployStages { label: 'Successful', get: (data): string | undefined => data?.mdapiDeploy?.numberTestsTotal && data?.mdapiDeploy?.numberTestsCompleted - ? formatProgress(data?.mdapiDeploy?.numberTestsCompleted, data?.mdapiDeploy?.numberTestsTotal) + ? formatProgress(data?.mdapiDeploy?.numberTestsCompleted, data?.mdapiDeploy?.numberTestsTotal) + + (verbose && isCI() ? os.EOL + formatTestSuccesses(data) : '') : undefined, stage: 'Running Tests', type: 'dynamic-key-value', @@ -152,7 +149,7 @@ export class DeployStages { get: (data): string | undefined => data?.mdapiDeploy?.numberTestsTotal && data?.mdapiDeploy?.numberTestErrors ? formatProgress(data?.mdapiDeploy?.numberTestErrors, data?.mdapiDeploy?.numberTestsTotal) + - (isTruthy(process.env.CI) ? os.EOL + formatTestFailures(data) : '') + (isCI() ? os.EOL + formatTestFailures(data) : '') : undefined, stage: 'Running Tests', type: 'dynamic-key-value', @@ -253,8 +250,22 @@ export class DeployStages { } } +function formatTestSuccesses(data: Data): string { + const successes = ensureArray(data.mdapiDeploy.details.runTestResult?.successes).sort(testResultSort); + + let output = ''; + + if (successes.length > 0) { + for (const test of successes) { + const testName = ansis.underline(`${test.name}.${test.methodName}`); + output += ` ${check} ${testName}${os.EOL}`; + } + } + + return output; +} + function formatTestFailures(data: Data): string { - if (data.mdapiDeploy.details.runTestResult?.failures === undefined) return ''; const failures = ensureArray(data.mdapiDeploy.details.runTestResult?.failures).sort(testResultSort); let output = ''; @@ -273,5 +284,14 @@ function formatTestFailures(data: Data): string { return output.slice(0, -1); } -const testResultSort = (a: T, b: T): number => - a.methodName === b.methodName ? a.name.localeCompare(b.name) : a.methodName.localeCompare(b.methodName); +export function isCI(): boolean { + if ( + isTruthy(process.env.CI) && + ('CI' in process.env || + 'CONTINUOUS_INTEGRATION' in process.env || + Object.keys(process.env).some((key) => key.startsWith('CI_'))) + ) + return true; + + return false; +}