diff --git a/messages/flags.md b/messages/flags.md new file mode 100644 index 00000000..868d02e7 --- /dev/null +++ b/messages/flags.md @@ -0,0 +1,7 @@ +# flags.result-format.summary + +Format of the test results. + +# flags.code-coverage.summary + +Retrieve code coverage results. diff --git a/messages/gettest.md b/messages/gettest.md index 8d38eddc..69009000 100644 --- a/messages/gettest.md +++ b/messages/gettest.md @@ -28,14 +28,6 @@ Provide a test run ID to display test results for an enqueued or completed async ID of the test run. -# flags.result-format.summary - -Format of the results. - -# flags.code-coverage.summary - -Retrieve code coverage results. - # flags.output-dir.summary Directory in which to store test result files. diff --git a/messages/list.md b/messages/list.md index 41d6f967..0c771b7f 100644 --- a/messages/list.md +++ b/messages/list.md @@ -21,43 +21,3 @@ To fetch a specific log from your org, obtain the ID from this command's output, # noDebugLogsFound No debug logs found in org - -# appColHeader - -APPLICATION - -# durationColHeader - -DURATION (MS) - -# idColHeader - -ID - -# locationColHeader - -LOCATION - -# sizeColHeader - -SIZE (B) - -# userColHeader - -LOG USER - -# operationColHeader - -OPERATION - -# requestColHeader - -REQUEST - -# timeColHeader - -START TIME - -# statusColHeader - -STATUS diff --git a/messages/runtest.md b/messages/runtest.md index 7128bc90..7b9cde8a 100644 --- a/messages/runtest.md +++ b/messages/runtest.md @@ -34,10 +34,6 @@ NOTE: The testRunCoverage value (JSON and JUnit result formats) is a percentage <%= config.bin %> <%= command.id %> --test-level RunLocalTests --output-dir --target-org me@my.org -# flags.result-format.summary - -Format of the test results. - # flags.class-names.summary Apex test class names to run; default is all classes. diff --git a/package.json b/package.json index 8bda320b..42f7b1f8 100644 --- a/package.json +++ b/package.json @@ -10,16 +10,13 @@ "@salesforce/core": "^7.4.1", "@salesforce/kit": "^3.1.6", "@salesforce/sf-plugins-core": "^11.1.0", - "chalk": "^5.3.0", - "color-convert": "^2.0.1", - "color-name": "^2.0.0" + "ansis": "^3.2.0" }, "devDependencies": { "@oclif/plugin-command-snapshot": "^5.2.2", "@salesforce/cli-plugins-testkit": "^5.3.14", "@salesforce/dev-scripts": "^10.1.1", "@salesforce/plugin-command-reference": "^3.1.2", - "@types/color-convert": "^2.0.3", "eslint-plugin-sf-plugin": "^1.18.5", "oclif": "^4.12.1", "ts-node": "^10.9.2", diff --git a/src/commands/apex/get/log.ts b/src/commands/apex/get/log.ts index a13322f8..891475cf 100644 --- a/src/commands/apex/get/log.ts +++ b/src/commands/apex/get/log.ts @@ -14,7 +14,7 @@ import { SfCommand, } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; -import { colorLogs } from '../../../utils.js'; +import { colorLogs } from '../../../logColorize.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-apex', 'get'); diff --git a/src/commands/apex/get/test.ts b/src/commands/apex/get/test.ts index aef55c7d..45e529b7 100644 --- a/src/commands/apex/get/test.ts +++ b/src/commands/apex/get/test.ts @@ -16,7 +16,7 @@ import { } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; import { RunResult, TestReporter } from '../../../reporters/index.js'; -import { resultFormat } from '../../../utils.js'; +import { codeCoverageFlag, resultFormatFlag } from '../../../flags.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-apex', 'gettest'); @@ -40,26 +40,14 @@ export default class Test extends SfCommand { startsWith: '707', length: 'both', }), - 'code-coverage': Flags.boolean({ - aliases: ['codecoverage'], - deprecateAliases: true, - char: 'c', - summary: messages.getMessage('flags.code-coverage.summary'), - }), + 'code-coverage': codeCoverageFlag, 'output-dir': Flags.directory({ aliases: ['outputdir', 'output-directory'], deprecateAliases: true, char: 'd', summary: messages.getMessage('flags.output-dir.summary'), }), - 'result-format': Flags.string({ - deprecateAliases: true, - aliases: ['resultformat'], - char: 'r', - summary: messages.getMessage('flags.result-format.summary'), - options: resultFormat, - default: 'human', - }), + 'result-format': resultFormatFlag, }; public async run(): Promise { diff --git a/src/commands/apex/list/log.ts b/src/commands/apex/list/log.ts index e8eef9ab..dc1a1a53 100644 --- a/src/commands/apex/list/log.ts +++ b/src/commands/apex/list/log.ts @@ -18,6 +18,7 @@ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-apex', 'list'); export type LogListResult = LogRecord[]; +type LogForTable = Omit & { DurationMilliseconds: string; User: string }; export default class Log extends SfCommand { public static readonly summary = messages.getMessage('summary'); @@ -35,61 +36,45 @@ export default class Log extends SfCommand { public async run(): Promise { const { flags } = await this.parse(Log); - const conn = flags['target-org'].getConnection(flags['api-version']); - const logService = new LogService(conn); - const logRecords = await logService.getLogRecords(); + const logService = new LogService(flags['target-org'].getConnection(flags['api-version'])); + const logRecords = (await logService.getLogRecords()).map(formatStartTime); if (logRecords.length === 0) { this.log(messages.getMessage('noDebugLogsFound')); return []; } - logRecords.map((logRecord) => { - logRecord.StartTime = this.formatTime(logRecord.StartTime); - }); - if (!flags.json) { // while not required to prevent table output, save a few iterations if only printing json - const cleanLogs = logRecords.map((logRecord) => ({ - app: logRecord.Application, - duration: String(logRecord.DurationMilliseconds), - id: logRecord.Id, - location: logRecord.Location, - size: String(logRecord.LogLength), - user: logRecord.LogUser.Name, - operation: logRecord.Operation, - request: logRecord.Request, - time: logRecord.StartTime, - status: logRecord.Status, - })); - - this.table( - cleanLogs, - { - app: { header: messages.getMessage('appColHeader') }, - duration: { header: messages.getMessage('durationColHeader') }, - id: { header: messages.getMessage('idColHeader') }, - location: { header: messages.getMessage('locationColHeader') }, - size: { header: messages.getMessage('sizeColHeader') }, - user: { header: messages.getMessage('userColHeader') }, - operation: { header: messages.getMessage('operationColHeader') }, - request: { header: messages.getMessage('requestColHeader') }, - time: { header: messages.getMessage('timeColHeader') }, - status: { header: messages.getMessage('statusColHeader') }, - }, - { 'no-truncate': true } - ); + this.table(logRecords.map(formatForTable), tableHeaders, { 'no-truncate': true }); } return logRecords; } - - // eslint-disable-next-line class-methods-use-this - private formatTime(time: string): string { - const milliIndex = time.indexOf('.'); - if (milliIndex !== -1) { - return time.substring(0, milliIndex) + time.substring(milliIndex + 4); - } - return time; - } } + +const formatForTable = (logRecord: LogRecord): LogForTable => ({ + ...logRecord, + DurationMilliseconds: String(logRecord.DurationMilliseconds), + User: logRecord.LogUser.Name, +}); + +export const formatStartTime = (lr: LogRecord): LogRecord => ({ ...lr, StartTime: formatTime(lr.StartTime) }); + +const formatTime = (time: string): string => { + const msIndex = time.indexOf('.'); + return msIndex !== -1 ? time.substring(0, msIndex) + time.substring(msIndex + 4) : time; +}; + +const tableHeaders = { + Application: { header: 'Application' }, + DurationMilliseconds: { header: 'Duration (ms)' }, + Id: { header: 'Id' }, + Location: { header: 'Location' }, + LogLength: { header: 'Size (B)' }, + User: { header: 'Log User' }, + Operation: { header: 'Operation' }, + Request: { header: 'Request' }, + StartTime: { header: 'Start Time' }, + Status: { header: 'Status' }, +}; diff --git a/src/commands/apex/run/test.ts b/src/commands/apex/run/test.ts index f707b48a..f9365f49 100644 --- a/src/commands/apex/run/test.ts +++ b/src/commands/apex/run/test.ts @@ -18,7 +18,7 @@ import { import { Messages, SfError } from '@salesforce/core'; import { Duration } from '@salesforce/kit'; import { RunResult, TestReporter } from '../../../reporters/index.js'; -import { resultFormat } from '../../../utils.js'; +import { codeCoverageFlag, resultFormatFlag } from '../../../flags.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-apex', 'runtest'); @@ -37,12 +37,7 @@ export default class Test extends SfCommand { 'target-org': requiredOrgFlagWithDeprecations, 'api-version': orgApiVersionFlagWithDeprecations, loglevel, - 'code-coverage': Flags.boolean({ - aliases: ['codecoverage'], - deprecateAliases: true, - char: 'c', - summary: messages.getMessage('flags.code-coverage.summary'), - }), + 'code-coverage': codeCoverageFlag, 'output-dir': Flags.directory({ aliases: ['outputdir', 'output-directory'], deprecateAliases: true, @@ -65,14 +60,7 @@ export default class Test extends SfCommand { description: messages.getMessage('flags.class-names.description'), exclusive: exclusiveTestSpecifiers.filter((specifier) => specifier !== 'class-names'), }), - 'result-format': Flags.string({ - deprecateAliases: true, - aliases: ['resultformat'], - char: 'r', - summary: messages.getMessage('flags.result-format.summary'), - options: resultFormat, - default: 'human', - }), + 'result-format': resultFormatFlag, 'suite-names': arrayWithDeprecation({ deprecateAliases: true, aliases: ['suitenames'], diff --git a/src/commands/apex/tail/log.ts b/src/commands/apex/tail/log.ts index a75ec91c..2d72ecf4 100644 --- a/src/commands/apex/tail/log.ts +++ b/src/commands/apex/tail/log.ts @@ -14,7 +14,7 @@ import { loglevel, } from '@salesforce/sf-plugins-core'; import { Connection, Messages } from '@salesforce/core'; -import { colorizeLog } from '../../../legacyColorization.js'; +import { colorLogs } from '../../../logColorize.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-apex', 'tail'); @@ -66,10 +66,11 @@ export default class Log extends SfCommand { this.log(messages.getMessage('finishedTailing')); } + // the colorize function used to be async, but not isn't. Preserving the public method signature + // eslint-disable-next-line @typescript-eslint/require-await public async logTailer(fullLog: string): Promise { if (fullLog) { - const output = this.color ? await colorizeLog(fullLog) : fullLog; - this.log(output); + this.log(this.color ? colorLogs(fullLog) : fullLog); } } diff --git a/src/flags.ts b/src/flags.ts new file mode 100644 index 00000000..9032972a --- /dev/null +++ b/src/flags.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { Messages } from '@salesforce/core'; +import { Flags } from '@salesforce/sf-plugins-core'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-apex', 'flags'); + +export const resultFormatFlag = Flags.string({ + deprecateAliases: true, + aliases: ['resultformat'], + char: 'r', + summary: messages.getMessage('flags.result-format.summary'), + options: ['human', 'tap', 'junit', 'json'] as const, + default: 'human', +}); + +export const codeCoverageFlag = Flags.boolean({ + aliases: ['codecoverage'], + deprecateAliases: true, + char: 'c', + summary: messages.getMessage('flags.code-coverage.summary'), +}); diff --git a/src/legacyColorization.ts b/src/legacyColorization.ts deleted file mode 100644 index 102e38b2..00000000 --- a/src/legacyColorization.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2021, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import { readFile } from 'node:fs/promises'; -import chalk from 'chalk'; -import convert from 'color-convert'; -import colorName from 'color-name'; -import { Logger } from '@salesforce/core'; - -type ColorMap = Record< - 'CONSTRUCTOR_' | 'EXCEPTION_' | 'FATAL_' | 'METHOD_' | 'SOQL_' | 'USER_' | 'VARIABLE_', - keyof typeof colorName ->; - -const DEFAULT_COLOR_MAP: ColorMap = { - CONSTRUCTOR_: 'magenta', - EXCEPTION_: 'red', - FATAL_: 'red', - METHOD_: 'blue', - SOQL_: 'yellow', - USER_: 'green', - VARIABLE_: 'darkcyan', -}; - -/** - * @description this is a holdover from the toolbelt API, which allows for custom colorization of the logs. - * @param log - full debug log retrieved from an org. - * @returns colorized log - */ -export async function colorizeLog(log: string): Promise { - const logger = await Logger.child('apexLogApi', { tag: 'tail' }); - let colorMap = DEFAULT_COLOR_MAP; - - const localColorMapFile = process.env.SFDX_APEX_LOG_COLOR_MAP; - if (localColorMapFile) { - try { - colorMap = JSON.parse(await readFile(localColorMapFile, 'utf-8')) as ColorMap; - } catch (err) { - logger.warn(`Color registry not found: ${localColorMapFile}`); - } - } - - const logLines = log.split(/\n/g); - if (!logLines || logLines.length < 1) { - logger.warn('colorizeLog unable to split logLines'); - return log; - } - - const line1 = chalk.bold(logLines.shift()); - - return [ - line1, - ...logLines.map((logLine) => { - for (const [key, color] of Object.entries(colorMap)) { - if (logLine.includes(`|${key}`)) { - const hex = convert.keyword.hex(color); - const colorFn = chalk.hex(hex); - - if (typeof colorFn !== 'function') { - logger.warn(`Color ${color} is not supported`); - return logLine; - } - - const count = (logLine.match(/\|/g) ?? []).length; - if (count === 1) { - return colorFn(logLine); - } - const first = logLine.indexOf('|', logLine.indexOf('|') + 1); - return `${colorFn(logLine.substring(0, first))}${logLine.substring(first)}`; - } - } - return logLine; - }), - ].join('\n'); -} diff --git a/src/logColorize.ts b/src/logColorize.ts new file mode 100644 index 00000000..0432d8a4 --- /dev/null +++ b/src/logColorize.ts @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { StandardColors } from '@salesforce/sf-plugins-core'; +import ansis from 'ansis'; + +/** these color matching pieces of content within a log line */ +const contentColorMap = [ + [new RegExp(/\|VARIABLE_\w*\|/g), ansis.bold.cyan], + [new RegExp(/\|METHOD_\w*\|/g), ansis.blue], + [new RegExp(/\|SOQL_\w*\|/g), StandardColors.warning], + [new RegExp(/\|CONSTRUCTOR_\w*\|/g), ansis.magenta], + [new RegExp(/\|USER\w*\|/g), StandardColors.success], + [new RegExp(/\b([\w]+\.)+(\w)+\b/g), ansis.blueBright], + [new RegExp(/\b(DEBUG)\b/g), ansis.bold.cyan], + [new RegExp(/\b(HINT|INFO|INFORMATION|EXCEPTION_\w*|FATAL_\w*)\b/g), StandardColors.success], + [new RegExp(/\b(WARNING|WARN)\b/g), StandardColors.warning], + [new RegExp(/\b(ERROR|FAILURE|FAIL)\b/g), StandardColors.error], + [new RegExp(/\b([a-zA-Z.]*Exception)\b/g), StandardColors.error], + [new RegExp(/"[^"]*"/g), StandardColors.error], + [new RegExp(/\b([0-9]+|true|false|null)\b/g), ansis.blueBright], +] as const; + +/** apply a single color to a single log's matching content */ +const colorLogMatchingContent = + ([regex, colorFn]: readonly [RegExp, ansis.Ansis]) => + (log: string): string => + log.replace(regex, (match) => colorFn(match)); + +const [colorFn1, ...colorFns] = [...contentColorMap.map(colorLogMatchingContent)]; + +/** one or more functions that have the same signature, returns one composed function that does them all sequentially */ +export const compose = (fn1: (a: R) => R, ...fns: Array<(a: R) => R>): ((a: R) => R) => + fns.reduce((prevFn, nextFn) => (value) => prevFn(nextFn(value)), fn1); + +/** all colorizers in one */ +export const colorize = compose(colorFn1, ...colorFns); + +export const colorLogs = (log: string): string => { + const [head, ...tail] = log.split('\n'); + // the first line is bolded and not otherwise styles + return tail.length === 0 + ? ansis.bold(head) + : [ + ansis.bold(head), // the first line is bolded and not otherwise styles + colorize(tail.join('\n')), + ].join('\n'); +}; diff --git a/src/reporters/runReporter.ts b/src/reporters/runReporter.ts index 1cdc3bcc..22e41e70 100644 --- a/src/reporters/runReporter.ts +++ b/src/reporters/runReporter.ts @@ -8,7 +8,7 @@ import os from 'node:os'; import { ExecuteAnonymousResponse } from '@salesforce/apex-node'; import { Messages } from '@salesforce/core'; -import { colorError, colorSuccess } from '../utils.js'; +import { StandardColors } from '@salesforce/sf-plugins-core'; import { ExecuteResult } from '../commands/apex/run.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); @@ -19,8 +19,8 @@ export default class RunReporter { const outputText: string[] = []; if (response.success) { outputText.push( - colorSuccess(messages.getMessage('executeCompileSuccess')), - colorSuccess(messages.getMessage('executeRuntimeSuccess')), + StandardColors.success(messages.getMessage('executeCompileSuccess')), + StandardColors.success(messages.getMessage('executeRuntimeSuccess')), '', response.logs ?? '' ); @@ -31,14 +31,14 @@ export default class RunReporter { const diagnostic = response.diagnostic[0]; if (!response.compiled) { outputText.push( - colorError(`Error: Line: ${diagnostic.lineNumber}, Column: ${diagnostic.columnNumber}`), - colorError(`Error: ${diagnostic.compileProblem}\n`) + StandardColors.error(`Error: Line: ${diagnostic.lineNumber}, Column: ${diagnostic.columnNumber}`), + StandardColors.error(`Error: ${diagnostic.compileProblem}\n`) ); } else { outputText.push( - colorSuccess(messages.getMessage('executeCompileSuccess')), - colorError(`Error: ${diagnostic.exceptionMessage}`), - colorError(`Error: ${diagnostic.exceptionStackTrace}`), + StandardColors.success(messages.getMessage('executeCompileSuccess')), + StandardColors.error(`Error: ${diagnostic.exceptionMessage}`), + StandardColors.error(`Error: ${diagnostic.exceptionStackTrace}`), '', response.logs ?? '' ); diff --git a/src/reporters/testReporter.ts b/src/reporters/testReporter.ts index 1f199127..087b7d85 100644 --- a/src/reporters/testReporter.ts +++ b/src/reporters/testReporter.ts @@ -18,9 +18,10 @@ import { import { Ux } from '@salesforce/sf-plugins-core'; import { Connection, Messages } from '@salesforce/core'; import { Duration } from '@salesforce/kit'; -import { FAILURE_EXIT_CODE } from '../utils.js'; import { JsonReporter, RunResult } from './jsonReporter.js'; +const FAILURE_EXIT_CODE = 100; + Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-apex', 'runtest'); diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index adc68fca..00000000 --- a/src/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2020, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ - -import chalk from 'chalk'; -export const FAILURE_EXIT_CODE = 100; -export const colorSuccess = chalk.bold.green; -export const colorError = chalk.bold.red; - -export const resultFormat = ['human', 'tap', 'junit', 'json']; - -const colorMap = new Map([ - [new RegExp(/\b([\w]+\.)+(\w)+\b/g), chalk.blueBright], - [new RegExp(/\b(DEBUG)\b/g), chalk.bold.cyan], - [new RegExp(/\b(HINT|INFO|INFORMATION)\b/g), chalk.bold.green], - [new RegExp(/\b(WARNING|WARN)\b/g), chalk.bold.yellow], - [new RegExp(/\b(ERROR|FAILURE|FAIL)\b/g), chalk.bold.red], - [new RegExp(/\b([a-zA-Z.]*Exception)\b/g), chalk.bold.red], - [new RegExp(/"[^"]*"/g), chalk.bold.red], - [new RegExp(/\b([0-9]+|true|false|null)\b/g), chalk.blueBright], -]); - -function replace(regex: RegExp, word: string): string { - const color = colorMap.get(regex); - if (!color) { - throw new Error('Error retrieving colors'); - } - return word.replace(regex, (match) => `${color(match)}`); -} - -export function colorLogs(log: string): string { - for (const c of colorMap.keys()) { - log = replace(c, log); - } - return log; -} diff --git a/test/commands/apex/get/log.test.ts b/test/commands/apex/get/log.test.ts index 9725055f..59f126a1 100644 --- a/test/commands/apex/get/log.test.ts +++ b/test/commands/apex/get/log.test.ts @@ -11,7 +11,11 @@ import { LogService } from '@salesforce/apex-node'; import { expect } from 'chai'; import { SfCommand } from '@salesforce/sf-plugins-core'; import { Org } from '@salesforce/core'; -import Log from '../../../../src/commands/apex/get/log.js'; +import ansis from 'ansis'; +import Log, { LogGetResult } from '../../../../src/commands/apex/get/log.js'; + +const strip = new ansis.Ansis().strip; +const logStripper = (log: LogGetResult[number]) => (typeof log === 'string' ? strip(log) : { log: strip(log.log) }); describe('apex:log:get', () => { let config: Config; @@ -59,17 +63,17 @@ describe('apex:log:get', () => { it('multiple results', async () => { sandbox.stub(LogService.prototype, 'getLogs').resolves([{ log: 'myLog' }, { log: 'myLog2' }]); - const result = await new Log([], config).run(); + const result = (await new Log([], config).run()).map(logStripper); expect(result).to.deep.equal([{ log: 'myLog' }, { log: 'myLog2' }]); - expect(logStub.firstCall.args[0]).to.equal('myLog'); - expect(logStub.secondCall.args[0]).to.equal('myLog2'); + expect(logStripper(logStub.firstCall.args[0] as string)).to.equal('myLog'); + expect(logStripper(logStub.secondCall.args[0] as string)).to.equal('myLog2'); }); it('multiple results --json', async () => { sandbox.stub(LogService.prototype, 'getLogs').resolves([{ log: 'myLog' }, { log: 'myLog2' }]); - const result = await new Log(['--json'], config).run(); + const result = (await new Log(['--json'], config).run()).map(logStripper); expect(result).to.deep.equal([{ log: 'myLog' }, { log: 'myLog2' }]); - expect(logStub.firstCall.args[0]).to.equal('myLog'); - expect(logStub.secondCall.args[0]).to.equal('myLog2'); + expect(logStripper(logStub.firstCall.args[0] as string)).to.equal('myLog'); + expect(logStripper(logStub.secondCall.args[0] as string)).to.equal('myLog2'); }); }); diff --git a/test/commands/apex/list/log.test.ts b/test/commands/apex/list/log.test.ts index 91a23e72..55843599 100644 --- a/test/commands/apex/list/log.test.ts +++ b/test/commands/apex/list/log.test.ts @@ -10,7 +10,7 @@ import sinon from 'sinon'; import { SfCommand } from '@salesforce/sf-plugins-core'; import { Org } from '@salesforce/core'; import { expect } from 'chai'; -import Log from '../../../../src/commands/apex/list/log.js'; +import Log, { formatStartTime } from '../../../../src/commands/apex/list/log.js'; const rawLogResult = { status: 0, @@ -83,66 +83,47 @@ describe('apex:log:list', () => { }); it('will list multiple logs', async () => { - sandbox.stub(LogService.prototype, 'getLogRecords').resolves(logRecords); - const result = await new Log([], config).run(); - expect(result).to.deep.equal(logRecords); + sandbox.stub(LogService.prototype, 'getLogRecords').resolves(structuredClone(logRecords)); + await new Log([], config).run(); expect(tableStub.firstCall.args[0]).to.deep.equal([ { - app: 'Unknown', - duration: '75', - id: '07L5tgg0005PGdTnEAL', - location: 'Unknown', - operation: 'API', - request: 'API', - size: '450', - status: 'Assertion Failed', - time: '2020-10-13T05:39:43+0000', - user: 'Test User', + Application: 'Unknown', + DurationMilliseconds: '75', + Id: '07L5tgg0005PGdTnEAL', + Location: 'Unknown', + Operation: 'API', + Request: 'API', + LogLength: 450, + Status: 'Assertion Failed', + StartTime: '2020-10-13T05:39:43+0000', + User: 'Test User', + LogUser: { + Name: 'Test User', + attributes: {}, + }, }, { - app: 'Unknown', - duration: '75', - id: '07L5tgg0005PGdTnFPL', - location: 'Unknown', - operation: 'API', - request: 'API', - size: '450', - status: 'Successful', - time: '2020-10-13T05:39:43+0000', - user: 'Test User2', + Application: 'Unknown', + DurationMilliseconds: '75', + Id: '07L5tgg0005PGdTnFPL', + Location: 'Unknown', + Operation: 'API', + Request: 'API', + LogLength: 450, + Status: 'Successful', + StartTime: '2020-10-13T05:39:43+0000', + User: 'Test User2', + LogUser: { + Name: 'Test User2', + attributes: {}, + }, }, ]); }); it('will list multiple logs --json', async () => { sandbox.stub(LogService.prototype, 'getLogRecords').resolves(logRecords); - const result = await new Log([], config).run(); - expect(result).to.deep.equal(logRecords); - expect(tableStub.firstCall.args[0]).to.deep.equal([ - { - app: 'Unknown', - duration: '75', - id: '07L5tgg0005PGdTnEAL', - location: 'Unknown', - operation: 'API', - request: 'API', - size: '450', - status: 'Assertion Failed', - time: '2020-10-13T05:39:43+0000', - user: 'Test User', - }, - { - app: 'Unknown', - duration: '75', - id: '07L5tgg0005PGdTnFPL', - location: 'Unknown', - operation: 'API', - request: 'API', - size: '450', - status: 'Successful', - time: '2020-10-13T05:39:43+0000', - user: 'Test User2', - }, - ]); + const result = await new Log(['--json'], config).run(); + expect(result).to.deep.equal(logRecords.map(formatStartTime)); }); }); diff --git a/test/commands/apex/loggingCommands.nut.ts b/test/commands/apex/loggingCommands.nut.ts index d78f96ee..adf68898 100644 --- a/test/commands/apex/loggingCommands.nut.ts +++ b/test/commands/apex/loggingCommands.nut.ts @@ -85,7 +85,7 @@ describe('apex log *', () => { it('will list the debug logs', async () => { const result = execCmd('apex:list:log', { ensureExitCode: 0 }).shellOutput.stdout; expect(result).to.match( - / APPLICATION DURATION \(MS\) ID\s+LOCATION\s+SIZE \(B\) LOG USER\s+OPERATION REQUEST START TIME\s+STATUS / + / Application Duration \(ms\) Id\s+Location\s+Size \(B\) Log User\s+Operation Request Start Time\s+Status / ); expect(result).to.match(/User User Api\s+Api\s+\d{4}-\d{2}-.* Success /); }); diff --git a/test/utils.test.ts b/test/utils.test.ts index 0272ff4b..7afc2bbf 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -4,48 +4,49 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { expect } from 'chai'; -import { colorLogs } from '../src/utils.js'; +import { expect, config } from 'chai'; +import { colorize } from '../src/logColorize.js'; +config.truncateThreshold = 0; describe('Colorize Logs', () => { - it('should color time/date format correctly', async () => { + it('should color time/date format correctly', () => { const testData = '12:47:29.584'; - const coloredData = colorLogs(testData); + const coloredData = colorize(testData); expect(coloredData).to.eql( - '\u001b[94m12\u001b[39m:\u001b[94m47\u001b[39m:\u001b[94m29.\u001b[94m584\u001b[39m\u001b[39m' + '\u001b[94m12\u001b[39m:\u001b[94m47\u001b[39m:\u001b[94m29\u001b[39m.\u001b[94m584\u001b[39m' ); }); - it('should color exception message correctly', async () => { + it('should color exception message correctly', () => { const testData = '$CalloutInTestmethodException: Methods defined as TestMethod do not support Web service callouts"'; - const coloredData = colorLogs(testData); + const coloredData = colorize(testData); expect(coloredData).to.eql( '$\u001b[1m\u001b[31mCalloutInTestmethodException\u001b[39m\u001b[22m: Methods defined as TestMethod do not support Web service callouts"' ); }); - it('should color debug message correctly', async () => { + it('should color debug message correctly', () => { const testData = 'SYSTEM,DEBUG;VALIDATION'; - const coloredData = colorLogs(testData); + const coloredData = colorize(testData); expect(coloredData).to.eql('SYSTEM,\u001b[1m\u001b[36mDEBUG\u001b[39m\u001b[22m;VALIDATION'); }); - it('should color basic strings correctly', async () => { + it('should color basic strings correctly', () => { const testData = 'testdevhub@ria.com'; - const coloredData = colorLogs(testData); + const coloredData = colorize(testData); expect(coloredData).to.eql('testdevhub@\u001b[94mria.com\u001b[39m'); }); - it('should color info text correctly', async () => { + it('should color info text correctly', () => { const testData = 'APEX_PROFILING,INFO;'; - const coloredData = colorLogs(testData); + const coloredData = colorize(testData); expect(coloredData).to.eql('APEX_PROFILING,\u001b[1m\u001b[32mINFO\u001b[39m\u001b[22m;'); }); - it('should color warn text correctly', async () => { + it('should color warn text correctly', () => { const testData = 'APEX_PROFILING,WARN;'; - const coloredData = colorLogs(testData); + const coloredData = colorize(testData); expect(coloredData).to.eql('APEX_PROFILING,\u001b[1m\u001b[33mWARN\u001b[39m\u001b[22m;'); }); }); diff --git a/yarn.lock b/yarn.lock index 86ba2fd8..ea46664b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2827,18 +2827,6 @@ dependencies: "@types/node" "*" -"@types/color-convert@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.3.tgz#e93f5c991eda87a945058b47044f5f0008b0dce9" - integrity sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg== - dependencies: - "@types/color-name" "*" - -"@types/color-name@*": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.3.tgz#c488ac2e519c9795faa0d54e8156d54e66adc4e6" - integrity sha512-87W6MJCKZYDhLAx/J1ikW8niMvmGRyY+rpUxWpL1cO7F8Uu5CHuQoFv+R0/L5pgNdW4jTyda42kv60uwVIPjLw== - "@types/glob@~7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -3716,11 +3704,6 @@ color-name@^1.0.0, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-2.0.0.tgz#03ff6b1b5aec9bb3cf1ed82400c2790dfcd01d2d" - integrity sha512-SbtvAMWvASO5TE2QP07jHBMXKafgdZz8Vrsrn96fiL+O92/FN/PLARzUW5sKt013fjAprK2d2iCn2hk2Xb5oow== - color-string@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"