From aa94b0987fd4d0860f1fc7e1e87338b6f70e5ebc Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 14 Apr 2025 12:27:26 -0500 Subject: [PATCH 01/14] chore: add consts --- src/ui/{shared.us.ts => shared.ui.ts} | 4 ++++ 1 file changed, 4 insertions(+) rename src/ui/{shared.us.ts => shared.ui.ts} (85%) diff --git a/src/ui/shared.us.ts b/src/ui/shared.ui.ts similarity index 85% rename from src/ui/shared.us.ts rename to src/ui/shared.ui.ts index 7e7483f8..d9ee0636 100644 --- a/src/ui/shared.us.ts +++ b/src/ui/shared.ui.ts @@ -14,3 +14,7 @@ export const INDICATORS: Record = { OK: ux.colorize(STATUS_COLORS.OK, '✔'), LTS: ux.colorize(STATUS_COLORS.LTS, '⚡'), }; + +export const MAX_PURL_LENGTH = 60; + +export const MAX_PACKAGE_NAME_LENGTH = 30; From e8b17cab41ce30eb53da1fbeb09616495c09dff4 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 14 Apr 2025 12:28:01 -0500 Subject: [PATCH 02/14] feat: refactor truncate purl method to accept max --- src/ui/eol.ui.ts | 53 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/src/ui/eol.ui.ts b/src/ui/eol.ui.ts index 46cf1fab..9bc7b0ee 100644 --- a/src/ui/eol.ui.ts +++ b/src/ui/eol.ui.ts @@ -1,11 +1,13 @@ import { ux } from '@oclif/core'; +import Table from 'cli-table3'; +import { PackageURL } from 'packageurl-js'; import type { ScanResultComponentsMap } from '../api/types/hd-cli.types.ts'; -import type { ComponentStatus } from '../api/types/nes.types.ts'; +import type { ComponentStatus, InsightsEolScanComponent } from '../api/types/nes.types.ts'; import { parseMomentToSimpleDate } from './date.ui.ts'; -import { INDICATORS, STATUS_COLORS } from './shared.us.ts'; +import { INDICATORS, MAX_PACKAGE_NAME_LENGTH, MAX_PURL_LENGTH, STATUS_COLORS } from './shared.ui.ts'; -export function truncatePurl(purl: string): string { - return purl.length > 60 ? `${purl.slice(0, 57)}...` : purl; +export function truncateString(purl: string, maxLength: number): string { + return purl.length > maxLength ? `${purl.slice(0, maxLength - 3)}...` : purl; } export function colorizeStatus(status: ComponentStatus): string { @@ -14,7 +16,7 @@ export function colorizeStatus(status: ComponentStatus): string { function formatSimpleComponent(purl: string, status: ComponentStatus): string { const color = STATUS_COLORS[status]; - return ` ${INDICATORS[status]} ${ux.colorize(color, truncatePurl(purl))}`; + return ` ${INDICATORS[status]} ${ux.colorize(color, truncateString(purl, MAX_PURL_LENGTH))}`; } function getDaysEolString(daysEol: number | null): string { @@ -77,3 +79,44 @@ export function createStatusDisplay( return statusOutput; } + +export function createTableForStatus( + components: ScanResultComponentsMap, + status: ComponentStatus, +): { table: Table.Table; count: number } { + let count = 0; + const table = new Table({ + head: ['NAME', 'VERSION', 'EOL', 'DAYS EOL', 'TYPE'], + colWidths: [MAX_PACKAGE_NAME_LENGTH, 10, 12, 10, 12], + wordWrap: true, + style: { + 'padding-left': 1, + 'padding-right': 1, + head: [], + border: [], + }, + }); + + for (const [_, component] of components.entries()) { + if (component.info.status !== status) continue; + + const row = convertComponentToTableRow(component); + table.push(row); + count++; + } + return { table, count }; +} + +export function convertComponentToTableRow(component: InsightsEolScanComponent) { + const purlParts = PackageURL.fromString(component.purl); + const { eolAt, daysEol } = component.info; + + return [ + { content: truncateString(purlParts.name, MAX_PACKAGE_NAME_LENGTH) }, + { content: purlParts.version }, + { content: parseMomentToSimpleDate(eolAt) }, + { content: daysEol }, + { content: purlParts.type }, + // vulns: component.vulns.length, // TODO: add vulns to monorepo api + ]; +} From 1888339b0f784efe7ab3f65bb17d8044418002a5 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 14 Apr 2025 12:28:30 -0500 Subject: [PATCH 03/14] feat: add flag for displaying results in table --- src/commands/scan/eol.ts | 46 ++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/commands/scan/eol.ts b/src/commands/scan/eol.ts index c771b55e..07e3a350 100644 --- a/src/commands/scan/eol.ts +++ b/src/commands/scan/eol.ts @@ -1,15 +1,15 @@ +import { Command, Flags, ux } from '@oclif/core'; +import type { Table } from 'cli-table3'; import fs from 'node:fs'; import path from 'node:path'; -import { Command, Flags, ux } from '@oclif/core'; import { batchSubmitPurls } from '../../api/nes/nes.client.ts'; import type { ScanResult } from '../../api/types/hd-cli.types.js'; -import type { InsightsEolScanComponent } from '../../api/types/nes.types.ts'; +import type { ComponentStatus, InsightsEolScanComponent } from '../../api/types/nes.types.ts'; import type { Sbom } from '../../service/eol/cdx.svc.ts'; import { getErrorMessage, isErrnoException } from '../../service/error.svc.ts'; -import { extractPurls } from '../../service/purls.svc.ts'; -import { parsePurlsFile } from '../../service/purls.svc.ts'; -import { createStatusDisplay } from '../../ui/eol.ui.ts'; -import { INDICATORS, STATUS_COLORS } from '../../ui/shared.us.ts'; +import { extractPurls, parsePurlsFile } from '../../service/purls.svc.ts'; +import { createStatusDisplay, createTableForStatus } from '../../ui/eol.ui.ts'; +import { INDICATORS, STATUS_COLORS } from '../../ui/shared.ui.ts'; import ScanSbom from './sbom.ts'; export default class ScanEol extends Command { @@ -44,9 +44,9 @@ export default class ScanEol extends Command { description: 'Show all components (default is EOL and LTS only)', default: false, }), - getCustomerSupport: Flags.boolean({ - char: 'c', - description: 'Get Never-Ending Support for End-of-Life components', + table: Flags.boolean({ + char: 't', + description: 'Display the results in a table', default: false, }), }; @@ -54,10 +54,6 @@ export default class ScanEol extends Command { public async run(): Promise<{ components: InsightsEolScanComponent[] }> { const { flags } = await this.parse(ScanEol); - if (flags.getCustomerSupport) { - this.log(ux.colorize('yellow', 'Never-Ending Support is on the way. Please stay tuned for this feature.')); - } - const scan = await this.getScan(flags, this.config); ux.action.stop('\nScan completed'); @@ -69,7 +65,12 @@ export default class ScanEol extends Command { } if (!this.jsonEnabled()) { - await this.displayResults(scan, flags.all); + if (flags.table) { + this.log(`${scan.components.size} components scanned`); + await this.displayResultsInTable(scan, flags.all); + } else { + await this.displayResults(scan, flags.all); + } } return { components }; @@ -166,6 +167,23 @@ export default class ScanEol extends Command { this.logLegend(); } + private async displayResultsInTable(scan: ScanResult, all: boolean): Promise { + const statuses: ComponentStatus[] = all ? ['UNKNOWN', 'OK', 'LTS', 'EOL'] : ['LTS', 'EOL']; + + for (const status of statuses) { + const { table, count } = createTableForStatus(scan.components, status); + + if (count > 0) { + this.displayTable(table, count, status); + } + } + } + + private displayTable(table: Table, count: number, status: ComponentStatus): void { + this.log(ux.colorize(STATUS_COLORS[status], `${INDICATORS[status]} ${count} ${status} Component(s):`)); + this.log(ux.colorize(STATUS_COLORS[status], table.toString())); + } + private displayNoComponentsMessage(all: boolean): void { if (!all) { this.log(ux.colorize('yellow', 'No End-of-Life or Long Term Support components found in scan.')); From 42985bbd6a9860dd0cfd3c71ea264a05c692ab60 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 14 Apr 2025 12:28:46 -0500 Subject: [PATCH 04/14] feat: install dependencies needed for table view --- package-lock.json | 41 +++++++++++++++++++++++++++++++++++++---- package.json | 4 +++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c41ac13..343edca3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,9 @@ "@oclif/core": "^4", "@oclif/plugin-help": "^6", "@oclif/plugin-update": "^4", - "graphql": "^16.8.1" + "cli-table3": "^0.6.5", + "graphql": "^16.8.1", + "packageurl-js": "^2.0.1" }, "bin": { "hd": "bin/run.js" @@ -1976,6 +1978,16 @@ "license": "(Apache-2.0 AND BSD-3-Clause)", "optional": true }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -2333,6 +2345,12 @@ "node": ">=14.16" } }, + "node_modules/@cyclonedx/cdxgen/node_modules/packageurl-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/packageurl-js/-/packageurl-js-1.0.2.tgz", + "integrity": "sha512-fWC4ZPxo80qlh3xN5FxfIoQD3phVY4+EyzTIqyksjhKNDmaicdpxSvkWwIrYTtv9C1/RcUN6pxaTwGmj2NzS6A==", + "license": "MIT" + }, "node_modules/@cyclonedx/cdxgen/node_modules/type-fest": { "version": "4.38.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.38.0.tgz", @@ -6128,6 +6146,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, "node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -10455,9 +10488,9 @@ "license": "BlueOak-1.0.0" }, "node_modules/packageurl-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/packageurl-js/-/packageurl-js-1.0.2.tgz", - "integrity": "sha512-fWC4ZPxo80qlh3xN5FxfIoQD3phVY4+EyzTIqyksjhKNDmaicdpxSvkWwIrYTtv9C1/RcUN6pxaTwGmj2NzS6A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/packageurl-js/-/packageurl-js-2.0.1.tgz", + "integrity": "sha512-N5ixXjzTy4QDQH0Q9YFjqIWd6zH6936Djpl2m9QNFmDv5Fum8q8BjkpAcHNMzOFE0IwQrFhJWex3AN6kS0OSwg==", "license": "MIT" }, "node_modules/pacote": { diff --git a/package.json b/package.json index e1bba91f..b2dfc5a2 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,9 @@ "@oclif/core": "^4", "@oclif/plugin-help": "^6", "@oclif/plugin-update": "^4", - "graphql": "^16.8.1" + "cli-table3": "^0.6.5", + "graphql": "^16.8.1", + "packageurl-js": "^2.0.1" }, "devDependencies": { "@biomejs/biome": "^1.8.3", From fbd5fd5f350be74c9cc538ff0ebcc568d2533096 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 14 Apr 2025 12:48:07 -0500 Subject: [PATCH 05/14] chore: add unit tests for table display --- src/ui/eol.ui.ts | 9 +++-- src/ui/shared.ui.ts | 2 +- test/ui/eol.ui.test.ts | 84 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 11 deletions(-) diff --git a/src/ui/eol.ui.ts b/src/ui/eol.ui.ts index 9bc7b0ee..76000f25 100644 --- a/src/ui/eol.ui.ts +++ b/src/ui/eol.ui.ts @@ -4,7 +4,7 @@ import { PackageURL } from 'packageurl-js'; import type { ScanResultComponentsMap } from '../api/types/hd-cli.types.ts'; import type { ComponentStatus, InsightsEolScanComponent } from '../api/types/nes.types.ts'; import { parseMomentToSimpleDate } from './date.ui.ts'; -import { INDICATORS, MAX_PACKAGE_NAME_LENGTH, MAX_PURL_LENGTH, STATUS_COLORS } from './shared.ui.ts'; +import { INDICATORS, MAX_PURL_LENGTH, MAX_TABLE_COLUMN_WIDTH, STATUS_COLORS } from './shared.ui.ts'; export function truncateString(purl: string, maxLength: number): string { return purl.length > maxLength ? `${purl.slice(0, maxLength - 3)}...` : purl; @@ -82,12 +82,13 @@ export function createStatusDisplay( export function createTableForStatus( components: ScanResultComponentsMap, - status: ComponentStatus, + status: ComponentStatus ): { table: Table.Table; count: number } { let count = 0; + const table = new Table({ head: ['NAME', 'VERSION', 'EOL', 'DAYS EOL', 'TYPE'], - colWidths: [MAX_PACKAGE_NAME_LENGTH, 10, 12, 10, 12], + colWidths: [MAX_TABLE_COLUMN_WIDTH, 10, 12, 10, 12], wordWrap: true, style: { 'padding-left': 1, @@ -112,7 +113,7 @@ export function convertComponentToTableRow(component: InsightsEolScanComponent) const { eolAt, daysEol } = component.info; return [ - { content: truncateString(purlParts.name, MAX_PACKAGE_NAME_LENGTH) }, + { content: purlParts.name }, { content: purlParts.version }, { content: parseMomentToSimpleDate(eolAt) }, { content: daysEol }, diff --git a/src/ui/shared.ui.ts b/src/ui/shared.ui.ts index d9ee0636..1d90ad6a 100644 --- a/src/ui/shared.ui.ts +++ b/src/ui/shared.ui.ts @@ -17,4 +17,4 @@ export const INDICATORS: Record = { export const MAX_PURL_LENGTH = 60; -export const MAX_PACKAGE_NAME_LENGTH = 30; +export const MAX_TABLE_COLUMN_WIDTH = 30; diff --git a/test/ui/eol.ui.test.ts b/test/ui/eol.ui.test.ts index a8001164..619bb755 100644 --- a/test/ui/eol.ui.test.ts +++ b/test/ui/eol.ui.test.ts @@ -1,30 +1,102 @@ import assert from 'node:assert'; import { describe, it } from 'node:test'; -import { truncatePurl } from '../../src/ui/eol.ui.ts'; +import { convertComponentToTableRow, createTableForStatus, truncateString } from '../../src/ui/eol.ui.ts'; +import { createMockComponent, createMockScan } from '../utils/mocks/scan-result-component.mock.ts'; describe('EOL UI', () => { - describe('truncatePurl', () => { - it('returns original PURL if length is 60 or less', () => { + describe('truncateString', () => { + it('returns original PURL if length is sixty characters or less', () => { // Arrange const purl = 'pkg:npm/test@1.0.0'; // Act - const result = truncatePurl(purl); + const result = truncateString(purl, 60); // Assert assert.strictEqual(result, purl); }); - it('truncates PURL if length is greater than 60', () => { + it('truncates PURL if length is greater than sixty characters', () => { // Arrange const longPurl = 'pkg:npm/very-long-package-name-that-exceeds-sixty-characters-significantly@1.0.0'; const expected = `${longPurl.slice(0, 57)}...`; // Act - const result = truncatePurl(longPurl); + const result = truncateString(longPurl, 60); // Assert assert.strictEqual(result, expected); }); }); + + describe('convertComponentToTableRow', () => { + it('converts a component to a table row', () => { + // Arrange + const component = createMockComponent( + 'pkg:npm/very-long-package-name-that-exceeds-thirty-characters@1.0.0', + 'EOL', + new Date('2023-01-01'), + 365, + ); + + // Act + const result = convertComponentToTableRow(component); + + // Assert + assert.strictEqual(result.length, 5); + assert.strictEqual(result[0].content, 'very-long-package-name-that-exceeds-thirty-characters'); + assert.strictEqual(result[1].content, '1.0.0'); + assert.strictEqual(result[2].content, '2023-01-01'); + assert.strictEqual(result[3].content, 365); + assert.strictEqual(result[4].content, 'npm'); + }); + + it('handles null values for eolAt and daysEol', () => { + // Arrange + const component = createMockComponent('pkg:npm/test@1.0.0', 'OK'); + + // Act + const result = convertComponentToTableRow(component); + + // Assert + assert.strictEqual(result.length, 5); + assert.strictEqual(result[0].content, 'test'); + assert.strictEqual(result[1].content, '1.0.0'); + assert.strictEqual(result[2].content, ''); + assert.strictEqual(result[3].content, null); + assert.strictEqual(result[4].content, 'npm'); + }); + }); + + describe('createTableForStatus', () => { + it('creates a table with components of matching status', () => { + // Arrange + const components = createMockScan([ + createMockComponent('pkg:npm/test1@1.0.0', 'EOL', new Date('2023-01-01'), 365), + createMockComponent('pkg:npm/test2@2.0.0', 'OK'), + createMockComponent('pkg:npm/test3@3.0.0', 'EOL', new Date('2023-02-01'), 400), + ]).components; + + // Act + const { table, count } = createTableForStatus(components, 'EOL'); + + // Assert + assert.strictEqual(count, 2); + assert.strictEqual(table.length, 2); // Only data rows (excluding header) + assert.deepStrictEqual(table.options.head, ['NAME', 'VERSION', 'EOL', 'DAYS EOL', 'TYPE']); + assert.deepStrictEqual(table.options.colWidths, [30, 10, 12, 10, 12]); + }); + + it('returns empty table when no components match status', () => { + // Arrange + const components = createMockScan([createMockComponent('pkg:npm/test1@1.0.0', 'OK')]).components; + + // Act + const { table, count } = createTableForStatus(components, 'EOL'); + + // Assert + assert.strictEqual(count, 0); + assert.strictEqual(table.length, 0); // No data rows + }); + }); }); From 7ee9e3f054fd393188988e472a4ca47ca44ea452 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 14 Apr 2025 12:50:46 -0500 Subject: [PATCH 06/14] chore: add e2e test for table flag --- e2e/scan/eol.test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/e2e/scan/eol.test.ts b/e2e/scan/eol.test.ts index 9f42ac1d..c7addc1d 100644 --- a/e2e/scan/eol.test.ts +++ b/e2e/scan/eol.test.ts @@ -108,6 +108,30 @@ describe('scan:eol e2e', () => { doesNotThrow(() => JSON.parse(stdout)); }); + it('displays results in table format when using the -t flag', async () => { + const cmd = `scan:eol --dir ${simpleDir} -t`; + const { stdout } = await run(cmd); + + // Match table header + match(stdout, /┌.*┬.*┬.*┬.*┬.*┐/, 'Should show table top border'); + match( + stdout, + /│ NAME\s*│ VERSION\s*│ EOL\s*│ DAYS EOL\s*│ TYPE\s*│/, // TODO: add vulns to monorepo api + 'Should show table headers' + ); + match(stdout, /├.*┼.*┼.*┼.*┼.*┤/, 'Should show table header separator'); + + // Match table content + match( + stdout, + /│ bootstrap\s*│ 3\.1\.1\s*│ 2019-07-24\s*│ \d+\s*│ npm\s*│/, + 'Should show bootstrap package in table' + ); + + // Match table footer + match(stdout, /└.*┴.*┴.*┴.*┴.*┘/, 'Should show table bottom border'); + }); + describe('--all flag', () => { it('excludes OK packages by default', async () => { const cmd = `scan:eol --dir ${simpleDir}`; From a639836945cd53e3f6c18236d72e13bc64d854d1 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 14 Apr 2025 12:57:57 -0500 Subject: [PATCH 07/14] chore: run linting --- e2e/scan/eol.test.ts | 4 ++-- src/commands/scan/eol.ts | 4 ++-- src/ui/eol.ui.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/scan/eol.test.ts b/e2e/scan/eol.test.ts index c7addc1d..81096038 100644 --- a/e2e/scan/eol.test.ts +++ b/e2e/scan/eol.test.ts @@ -117,7 +117,7 @@ describe('scan:eol e2e', () => { match( stdout, /│ NAME\s*│ VERSION\s*│ EOL\s*│ DAYS EOL\s*│ TYPE\s*│/, // TODO: add vulns to monorepo api - 'Should show table headers' + 'Should show table headers', ); match(stdout, /├.*┼.*┼.*┼.*┼.*┤/, 'Should show table header separator'); @@ -125,7 +125,7 @@ describe('scan:eol e2e', () => { match( stdout, /│ bootstrap\s*│ 3\.1\.1\s*│ 2019-07-24\s*│ \d+\s*│ npm\s*│/, - 'Should show bootstrap package in table' + 'Should show bootstrap package in table', ); // Match table footer diff --git a/src/commands/scan/eol.ts b/src/commands/scan/eol.ts index 07e3a350..da92b377 100644 --- a/src/commands/scan/eol.ts +++ b/src/commands/scan/eol.ts @@ -1,7 +1,7 @@ -import { Command, Flags, ux } from '@oclif/core'; -import type { Table } from 'cli-table3'; import fs from 'node:fs'; import path from 'node:path'; +import { Command, Flags, ux } from '@oclif/core'; +import type { Table } from 'cli-table3'; import { batchSubmitPurls } from '../../api/nes/nes.client.ts'; import type { ScanResult } from '../../api/types/hd-cli.types.js'; import type { ComponentStatus, InsightsEolScanComponent } from '../../api/types/nes.types.ts'; diff --git a/src/ui/eol.ui.ts b/src/ui/eol.ui.ts index 76000f25..c6fa7f5d 100644 --- a/src/ui/eol.ui.ts +++ b/src/ui/eol.ui.ts @@ -82,7 +82,7 @@ export function createStatusDisplay( export function createTableForStatus( components: ScanResultComponentsMap, - status: ComponentStatus + status: ComponentStatus, ): { table: Table.Table; count: number } { let count = 0; From 2976185d39c81eccff9012183f3b4c8d9f2268da Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 14 Apr 2025 16:05:25 -0500 Subject: [PATCH 08/14] chore: remove async from display method --- src/commands/scan/eol.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/scan/eol.ts b/src/commands/scan/eol.ts index da92b377..305ad8a6 100644 --- a/src/commands/scan/eol.ts +++ b/src/commands/scan/eol.ts @@ -167,7 +167,7 @@ export default class ScanEol extends Command { this.logLegend(); } - private async displayResultsInTable(scan: ScanResult, all: boolean): Promise { + private displayResultsInTable(scan: ScanResult, all: boolean) { const statuses: ComponentStatus[] = all ? ['UNKNOWN', 'OK', 'LTS', 'EOL'] : ['LTS', 'EOL']; for (const status of statuses) { From 4347e717572d8335c79a809359c1ec37a9d77f64 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Tue, 15 Apr 2025 07:09:22 -0500 Subject: [PATCH 09/14] chore: refactor create table for status method --- src/commands/scan/eol.ts | 6 +++--- src/ui/eol.ui.ts | 6 ++---- test/ui/eol.ui.test.ts | 7 +++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/commands/scan/eol.ts b/src/commands/scan/eol.ts index 305ad8a6..a652f617 100644 --- a/src/commands/scan/eol.ts +++ b/src/commands/scan/eol.ts @@ -171,10 +171,10 @@ export default class ScanEol extends Command { const statuses: ComponentStatus[] = all ? ['UNKNOWN', 'OK', 'LTS', 'EOL'] : ['LTS', 'EOL']; for (const status of statuses) { - const { table, count } = createTableForStatus(scan.components, status); + const table = createTableForStatus(scan.components, status); - if (count > 0) { - this.displayTable(table, count, status); + if (table.length > 0) { + this.displayTable(table, table.length, status); } } } diff --git a/src/ui/eol.ui.ts b/src/ui/eol.ui.ts index c6fa7f5d..14bfa672 100644 --- a/src/ui/eol.ui.ts +++ b/src/ui/eol.ui.ts @@ -83,8 +83,7 @@ export function createStatusDisplay( export function createTableForStatus( components: ScanResultComponentsMap, status: ComponentStatus, -): { table: Table.Table; count: number } { - let count = 0; +): Table.Table { const table = new Table({ head: ['NAME', 'VERSION', 'EOL', 'DAYS EOL', 'TYPE'], @@ -103,9 +102,8 @@ export function createTableForStatus( const row = convertComponentToTableRow(component); table.push(row); - count++; } - return { table, count }; + return table; } export function convertComponentToTableRow(component: InsightsEolScanComponent) { diff --git a/test/ui/eol.ui.test.ts b/test/ui/eol.ui.test.ts index 619bb755..138444bd 100644 --- a/test/ui/eol.ui.test.ts +++ b/test/ui/eol.ui.test.ts @@ -78,10 +78,10 @@ describe('EOL UI', () => { ]).components; // Act - const { table, count } = createTableForStatus(components, 'EOL'); + const table = createTableForStatus(components, 'EOL'); // Assert - assert.strictEqual(count, 2); + assert.strictEqual(table.length, 2); assert.strictEqual(table.length, 2); // Only data rows (excluding header) assert.deepStrictEqual(table.options.head, ['NAME', 'VERSION', 'EOL', 'DAYS EOL', 'TYPE']); assert.deepStrictEqual(table.options.colWidths, [30, 10, 12, 10, 12]); @@ -92,10 +92,9 @@ describe('EOL UI', () => { const components = createMockScan([createMockComponent('pkg:npm/test1@1.0.0', 'OK')]).components; // Act - const { table, count } = createTableForStatus(components, 'EOL'); + const table = createTableForStatus(components, 'EOL'); // Assert - assert.strictEqual(count, 0); assert.strictEqual(table.length, 0); // No data rows }); }); From becb95a45d2e492cf59d1d006694349427f50df9 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Tue, 15 Apr 2025 07:10:20 -0500 Subject: [PATCH 10/14] chore: remove unnecessary await --- src/commands/scan/eol.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/scan/eol.ts b/src/commands/scan/eol.ts index a652f617..2a8b61d7 100644 --- a/src/commands/scan/eol.ts +++ b/src/commands/scan/eol.ts @@ -67,9 +67,9 @@ export default class ScanEol extends Command { if (!this.jsonEnabled()) { if (flags.table) { this.log(`${scan.components.size} components scanned`); - await this.displayResultsInTable(scan, flags.all); + this.displayResultsInTable(scan, flags.all); } else { - await this.displayResults(scan, flags.all); + this.displayResults(scan, flags.all); } } @@ -148,7 +148,7 @@ export default class ScanEol extends Command { } } - private async displayResults(scan: ScanResult, all: boolean): Promise { + private displayResults(scan: ScanResult, all: boolean) { const { UNKNOWN, OK, LTS, EOL } = createStatusDisplay(scan.components, all); if (!UNKNOWN.length && !OK.length && !LTS.length && !EOL.length) { From 1b8f978ddd677170cb19c6f4ae8b0c22085eddfc Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Tue, 15 Apr 2025 07:11:47 -0500 Subject: [PATCH 11/14] chore: switch to values --- src/ui/eol.ui.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/eol.ui.ts b/src/ui/eol.ui.ts index 14bfa672..9de8a94b 100644 --- a/src/ui/eol.ui.ts +++ b/src/ui/eol.ui.ts @@ -97,7 +97,7 @@ export function createTableForStatus( }, }); - for (const [_, component] of components.entries()) { + for (const component of components.values()) { if (component.info.status !== status) continue; const row = convertComponentToTableRow(component); From e14c74cff1b66c85a77d5e639431a5501e869777 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Tue, 15 Apr 2025 07:13:09 -0500 Subject: [PATCH 12/14] chore: update based on pr feedback --- src/commands/scan/eol.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands/scan/eol.ts b/src/commands/scan/eol.ts index 2a8b61d7..b8ef071b 100644 --- a/src/commands/scan/eol.ts +++ b/src/commands/scan/eol.ts @@ -168,7 +168,11 @@ export default class ScanEol extends Command { } private displayResultsInTable(scan: ScanResult, all: boolean) { - const statuses: ComponentStatus[] = all ? ['UNKNOWN', 'OK', 'LTS', 'EOL'] : ['LTS', 'EOL']; + const statuses: ComponentStatus[] = ['LTS', 'EOL']; + + if (all) { + statuses.unshift('UNKNOWN', 'OK'); + } for (const status of statuses) { const table = createTableForStatus(scan.components, status); From 840d2618393d615fc9f5c279f3a102d2d5f80872 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Tue, 15 Apr 2025 08:21:13 -0500 Subject: [PATCH 13/14] chore: run linting --- src/ui/eol.ui.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ui/eol.ui.ts b/src/ui/eol.ui.ts index 9de8a94b..fcd16934 100644 --- a/src/ui/eol.ui.ts +++ b/src/ui/eol.ui.ts @@ -80,11 +80,7 @@ export function createStatusDisplay( return statusOutput; } -export function createTableForStatus( - components: ScanResultComponentsMap, - status: ComponentStatus, -): Table.Table { - +export function createTableForStatus(components: ScanResultComponentsMap, status: ComponentStatus): Table.Table { const table = new Table({ head: ['NAME', 'VERSION', 'EOL', 'DAYS EOL', 'TYPE'], colWidths: [MAX_TABLE_COLUMN_WIDTH, 10, 12, 10, 12], From 992476b4415bf5591f693972eca693f156f84cb1 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Tue, 15 Apr 2025 08:22:51 -0500 Subject: [PATCH 14/14] chore: clarify truncate string method --- src/ui/eol.ui.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/eol.ui.ts b/src/ui/eol.ui.ts index fcd16934..40864137 100644 --- a/src/ui/eol.ui.ts +++ b/src/ui/eol.ui.ts @@ -7,7 +7,8 @@ import { parseMomentToSimpleDate } from './date.ui.ts'; import { INDICATORS, MAX_PURL_LENGTH, MAX_TABLE_COLUMN_WIDTH, STATUS_COLORS } from './shared.ui.ts'; export function truncateString(purl: string, maxLength: number): string { - return purl.length > maxLength ? `${purl.slice(0, maxLength - 3)}...` : purl; + const ellipses = '...'; + return purl.length > maxLength ? `${purl.slice(0, maxLength - ellipses.length)}${ellipses}` : purl; } export function colorizeStatus(status: ComponentStatus): string {