@herodevs/cli 1.0.0-beta.2 → 1.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +30 -325
  2. package/bin/dev.js +6 -6
  3. package/bin/run.js +3 -2
  4. package/dist/api/client.d.ts +0 -2
  5. package/dist/api/client.js +19 -18
  6. package/dist/api/nes/nes.client.d.ts +4 -0
  7. package/dist/api/nes/nes.client.js +11 -0
  8. package/dist/api/queries/nes/sbom.js +5 -0
  9. package/dist/api/types/nes.types.d.ts +17 -3
  10. package/dist/api/types/nes.types.js +11 -1
  11. package/dist/commands/report/committers.d.ts +3 -2
  12. package/dist/commands/report/committers.js +75 -33
  13. package/dist/commands/report/purls.d.ts +4 -2
  14. package/dist/commands/report/purls.js +51 -31
  15. package/dist/commands/scan/eol.d.ts +13 -4
  16. package/dist/commands/scan/eol.js +112 -37
  17. package/dist/commands/scan/sbom.d.ts +4 -1
  18. package/dist/commands/scan/sbom.js +86 -33
  19. package/dist/hooks/prerun.js +8 -0
  20. package/dist/service/committers.svc.js +24 -3
  21. package/dist/service/eol/cdx.svc.d.ts +52 -0
  22. package/dist/service/eol/cdx.svc.js +58 -62
  23. package/dist/service/eol/eol.svc.d.ts +0 -21
  24. package/dist/service/eol/eol.svc.js +2 -62
  25. package/dist/service/eol/sbom.worker.d.ts +1 -0
  26. package/dist/service/eol/sbom.worker.js +26 -0
  27. package/dist/service/error.svc.d.ts +8 -0
  28. package/dist/service/error.svc.js +28 -0
  29. package/dist/service/log.svc.d.ts +5 -8
  30. package/dist/service/log.svc.js +5 -18
  31. package/dist/service/nes/nes.svc.js +4 -3
  32. package/dist/service/purls.svc.js +1 -1
  33. package/dist/ui/date.ui.d.ts +1 -0
  34. package/dist/ui/date.ui.js +15 -0
  35. package/dist/ui/eol.ui.d.ts +4 -3
  36. package/dist/ui/eol.ui.js +56 -15
  37. package/dist/ui/shared.us.d.ts +3 -0
  38. package/dist/ui/shared.us.js +13 -0
  39. package/package.json +13 -14
  40. package/dist/hooks/init/update.d.ts +0 -2
  41. package/dist/hooks/init/update.js +0 -5
  42. package/dist/hooks/prerun/CommandContextHook.js +0 -8
  43. package/dist/service/line.svc.d.ts +0 -24
  44. package/dist/service/line.svc.js +0 -61
  45. /package/dist/hooks/{prerun/CommandContextHook.d.ts → prerun.d.ts} +0 -0
@@ -17,7 +17,7 @@ export function getPurlOutput(purls, output) {
17
17
  case 'csv':
18
18
  return ['purl', ...purls].map(formatCsvValue).join('\n');
19
19
  default:
20
- return JSON.stringify(purls, null, 2);
20
+ return JSON.stringify({ purls }, null, 2);
21
21
  }
22
22
  }
23
23
  /**
@@ -0,0 +1 @@
1
+ export declare function parseMomentToSimpleDate(momentDate: string | Date | number | null): string;
@@ -0,0 +1,15 @@
1
+ export function parseMomentToSimpleDate(momentDate) {
2
+ // Only return empty string for null
3
+ if (momentDate === null)
4
+ return '';
5
+ try {
6
+ const dateObj = new Date(momentDate);
7
+ if (Number.isNaN(dateObj.getTime())) {
8
+ throw new Error('Invalid date');
9
+ }
10
+ return dateObj.toISOString().split('T')[0];
11
+ }
12
+ catch {
13
+ throw new Error('Invalid date');
14
+ }
15
+ }
@@ -1,3 +1,4 @@
1
- import type { Answers } from 'inquirer';
2
- import { type Line } from '../service/line.svc.ts';
3
- export declare function promptComponentDetails(lines: Line[]): Promise<Answers>;
1
+ import type { ComponentStatus, ScanResultComponentsMap } from '../api/types/nes.types.ts';
2
+ export declare function truncatePurl(purl: string): string;
3
+ export declare function colorizeStatus(status: ComponentStatus): string;
4
+ export declare function createStatusDisplay(components: ScanResultComponentsMap, all: boolean): Record<ComponentStatus, string[]>;
package/dist/ui/eol.ui.js CHANGED
@@ -1,17 +1,58 @@
1
- import inquirer from 'inquirer';
2
- import { formatLine } from "../service/line.svc.js";
3
- export function promptComponentDetails(lines) {
4
- const context = {
5
- longest: lines.map((l) => l.purl.length).reduce((a, l) => Math.max(a, l), 0),
6
- total: lines.length,
1
+ import { ux } from '@oclif/core';
2
+ import { parseMomentToSimpleDate } from "./date.ui.js";
3
+ import { INDICATORS, STATUS_COLORS } from "./shared.us.js";
4
+ export function truncatePurl(purl) {
5
+ return purl.length > 60 ? `${purl.slice(0, 57)}...` : purl;
6
+ }
7
+ export function colorizeStatus(status) {
8
+ return ux.colorize(STATUS_COLORS[status], status);
9
+ }
10
+ function formatSimpleComponent(purl, status) {
11
+ const color = STATUS_COLORS[status];
12
+ return ` ${INDICATORS[status]} ${ux.colorize(color, truncatePurl(purl))}`;
13
+ }
14
+ function getDaysEolString(daysEol) {
15
+ // UNKNOWN || OK
16
+ if (daysEol === null) {
17
+ return '';
18
+ }
19
+ // LTS
20
+ if (daysEol < 0) {
21
+ return `${Math.abs(daysEol)} days from now`;
22
+ }
23
+ // EOL
24
+ if (daysEol === 0) {
25
+ return 'today';
26
+ }
27
+ return `${daysEol} days ago`;
28
+ }
29
+ function formatDetailedComponent(purl, eolAt, daysEol, status) {
30
+ const simpleComponent = formatSimpleComponent(purl, status);
31
+ const eolAtString = parseMomentToSimpleDate(eolAt);
32
+ const daysEolString = getDaysEolString(daysEol);
33
+ const output = [`${simpleComponent}`, ` ⮑ EOL Date: ${eolAtString} (${daysEolString})`]
34
+ .filter(Boolean)
35
+ .join('\n');
36
+ return output;
37
+ }
38
+ export function createStatusDisplay(components, all) {
39
+ const statusOutput = {
40
+ UNKNOWN: [],
41
+ OK: [],
42
+ LTS: [],
43
+ EOL: [],
7
44
  };
8
- return inquirer.prompt([
9
- {
10
- choices: lines.map((l, idx) => formatLine(l, idx, context)),
11
- message: 'Which components',
12
- name: 'selected',
13
- pageSize: 20,
14
- type: 'checkbox',
15
- },
16
- ]);
45
+ // Single loop to separate and format components
46
+ for (const [purl, component] of components.entries()) {
47
+ const { status, eolAt, daysEol } = component.info;
48
+ if (all) {
49
+ if (status === 'UNKNOWN' || status === 'OK') {
50
+ statusOutput[status].push(formatSimpleComponent(purl, status));
51
+ }
52
+ }
53
+ if (status === 'LTS' || status === 'EOL') {
54
+ statusOutput[status].push(formatDetailedComponent(purl, eolAt, daysEol, status));
55
+ }
56
+ }
57
+ return statusOutput;
17
58
  }
@@ -0,0 +1,3 @@
1
+ import type { ComponentStatus } from '../api/types/nes.types.ts';
2
+ export declare const STATUS_COLORS: Record<ComponentStatus, string>;
3
+ export declare const INDICATORS: Record<ComponentStatus, string>;
@@ -0,0 +1,13 @@
1
+ import { ux } from '@oclif/core';
2
+ export const STATUS_COLORS = {
3
+ EOL: 'red',
4
+ UNKNOWN: 'default',
5
+ OK: 'green',
6
+ LTS: 'yellow',
7
+ };
8
+ export const INDICATORS = {
9
+ EOL: ux.colorize(STATUS_COLORS.EOL, '✗'),
10
+ UNKNOWN: ux.colorize(STATUS_COLORS.UNKNOWN, '•'),
11
+ OK: ux.colorize(STATUS_COLORS.OK, '✔'),
12
+ LTS: ux.colorize(STATUS_COLORS.LTS, '⚡'),
13
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herodevs/cli",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.2.0-beta.1",
4
4
  "author": "HeroDevs, Inc",
5
5
  "bin": {
6
6
  "hd": "./bin/run.js"
@@ -19,13 +19,14 @@
19
19
  "clean": "shx rm -rf dist && npm run clean:files && shx rm -rf node_modules",
20
20
  "clean:files": "shx rm -f nes.**.csv nes.**.json nes.**.text",
21
21
  "dev": "npm run build && ./bin/dev.js",
22
+ "dev:debug": "npm run build && DEBUG=* ./bin/dev.js",
22
23
  "format": "biome format --write",
23
24
  "lint": "biome lint --write",
24
25
  "postpack": "shx rm -f oclif.manifest.json",
25
26
  "prepack": "oclif manifest && oclif readme",
26
27
  "pretest": "npm run lint && npm run typecheck",
27
28
  "readme": "npm run ci:fix && npm run build && npm exec oclif readme",
28
- "test": "node --disable-warning=ExperimentalWarning --experimental-strip-types --test \"test/**/*.test.ts\"",
29
+ "test": "globstar -- node --import tsx --test \"test/**/*.test.ts\"",
29
30
  "typecheck": "tsc --noEmit",
30
31
  "version": "oclif readme && git add README.md"
31
32
  },
@@ -39,9 +40,7 @@
39
40
  "@cyclonedx/cdxgen": "^11.2.2",
40
41
  "@oclif/core": "^4",
41
42
  "@oclif/plugin-help": "^6",
42
- "@oclif/plugin-plugins": "^5",
43
- "graphql": "^16.8.1",
44
- "inquirer": "^12.5.0"
43
+ "graphql": "^16.8.1"
45
44
  },
46
45
  "devDependencies": {
47
46
  "@biomejs/biome": "^1.8.3",
@@ -49,13 +48,16 @@
49
48
  "@types/inquirer": "^9.0.7",
50
49
  "@types/node": "^22",
51
50
  "@types/sinon": "^17.0.4",
51
+ "globstar": "^1.0.0",
52
52
  "oclif": "^4",
53
53
  "shx": "^0.3.3",
54
54
  "sinon": "^19.0.2",
55
+ "ts-node": "^10",
56
+ "tsx": "^4.19.3",
55
57
  "typescript": "^5.8.0"
56
58
  },
57
59
  "engines": {
58
- "node": ">=22.0.0"
60
+ "node": ">=18.0.0"
59
61
  },
60
62
  "files": [
61
63
  "./bin",
@@ -70,15 +72,12 @@
70
72
  "commands": "./dist/commands",
71
73
  "plugins": [
72
74
  "@oclif/plugin-help",
73
- "@oclif/plugin-plugins",
74
- "@oclif/plugin-update"
75
+ "@oclif/plugin-plugins"
75
76
  ],
76
- "topicSeparator": " ",
77
- "update": {
78
- "node": {
79
- "version": ">=22.0.0"
80
- }
81
- }
77
+ "hooks": {
78
+ "prerun": "./dist/hooks/prerun.js"
79
+ },
80
+ "topicSeparator": " "
82
81
  },
83
82
  "types": "dist/index.d.ts"
84
83
  }
@@ -1,2 +0,0 @@
1
- import type { Hook } from '@oclif/core';
2
- export declare const initUpdate: Hook<'init'>;
@@ -1,5 +0,0 @@
1
- import updateConfig from '../../config/update.js';
2
- export const initUpdate = async ({ config }) => {
3
- // Apply update configuration
4
- Object.assign(config, updateConfig);
5
- };
@@ -1,8 +0,0 @@
1
- import { initOclifLog, log } from "../../service/log.svc.js";
2
- const hook = async (opts) => {
3
- initOclifLog(opts.context.log, opts.context.log, opts.context.debug);
4
- log.info = opts.context.log || log.info;
5
- log.warn = opts.context.log || log.warn;
6
- log.debug = opts.context.debug || log.debug;
7
- };
8
- export default hook;
@@ -1,24 +0,0 @@
1
- import type { ComponentStatus, ScanResultComponent } from '../api/types/nes.types.ts';
2
- export interface Line {
3
- daysEol: number | null;
4
- purl: ScanResultComponent['purl'];
5
- info: {
6
- eolAt: Date | null;
7
- isEol: boolean;
8
- };
9
- status: ComponentStatus;
10
- }
11
- export declare function getStatusFromComponent(component: ScanResultComponent, daysEol: number | null): ComponentStatus;
12
- export declare function daysBetween(date1: Date, date2: Date): number;
13
- export declare function getDaysEolFromEolAt(eolAt: Date | null): number | null;
14
- export declare function getMessageAndStatus(status: string, daysEol: number | null): {
15
- stat: string;
16
- msg: string;
17
- };
18
- export declare function formatLine(l: Line, idx: number, ctx: {
19
- longest: number;
20
- total: number;
21
- }): {
22
- name: string;
23
- value: Line;
24
- };
@@ -1,61 +0,0 @@
1
- export function getStatusFromComponent(component, daysEol) {
2
- const { info } = component;
3
- if (component.status) {
4
- if (info.isEol && component.status && component.status !== 'EOL') {
5
- throw new Error(`isEol is true but status is not EOL: ${component.purl}`);
6
- }
7
- return component.status;
8
- }
9
- // If API fails to set status, we derive it based on other properties
10
- if (daysEol === null) {
11
- return info.isEol ? 'EOL' : 'OK';
12
- }
13
- if (daysEol > 0) {
14
- // daysEol is positive means we're past the EOL date
15
- return 'EOL';
16
- }
17
- // daysEol is zero or negative means we haven't reached EOL yet
18
- return 'LTS';
19
- }
20
- export function daysBetween(date1, date2) {
21
- const msPerDay = 1000 * 60 * 60 * 24 + 15; // milliseconds in a day plus 15 ms
22
- return Math.round((date2.getTime() - date1.getTime()) / msPerDay);
23
- }
24
- export function getDaysEolFromEolAt(eolAt) {
25
- return eolAt ? Math.abs(daysBetween(new Date(), eolAt)) : null;
26
- }
27
- export function getMessageAndStatus(status, daysEol) {
28
- let msg = '';
29
- let stat = '';
30
- const stringifiedDaysEol = daysEol ? daysEol.toString() : 'unknown';
31
- switch (status) {
32
- case 'EOL': {
33
- stat = 'EOL';
34
- msg = `EOL'd ${stringifiedDaysEol} days ago.`;
35
- break;
36
- }
37
- case 'LTS': {
38
- stat = 'LTS';
39
- msg = `Will go EOL in ${stringifiedDaysEol} days.`;
40
- break;
41
- }
42
- case 'OK': {
43
- stat = 'OK';
44
- break;
45
- }
46
- default:
47
- throw new Error(`Unknown status: ${status}`);
48
- }
49
- return { stat, msg };
50
- }
51
- export function formatLine(l, idx, ctx) {
52
- const { daysEol, purl, status } = l;
53
- const { stat, msg } = getMessageAndStatus(status, daysEol);
54
- const padlen = ctx.total.toString().length;
55
- const rownum = `${idx + 1}`.padStart(padlen, ' ');
56
- const name = purl.padEnd(ctx.longest, ' ');
57
- return {
58
- name: `${rownum}. [${stat}] ${name} | ${msg}`,
59
- value: l,
60
- };
61
- }