@herodevs/cli 1.5.0-beta.2 → 1.6.0-beta.0

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.
package/README.md CHANGED
@@ -9,6 +9,16 @@ The HeroDevs CLI
9
9
  <!-- toc -->
10
10
  * [@herodevs/cli](#herodevscli)
11
11
  <!-- tocstop -->
12
+
13
+ ## Scanning Behavior
14
+
15
+ The CLI's scanning commands (`hd scan eol` and `hd scan sbom`) are designed to be non-invasive:
16
+
17
+ * They do not install dependencies or modify package manager files (package-lock.json, yarn.lock, etc.)
18
+ * They analyze the project in its current state
19
+ * If you need dependencies installed for accurate scanning, please install them manually before running the scan
20
+
21
+
12
22
  ## Usage
13
23
  <!-- usage -->
14
24
  ```sh-session
@@ -16,7 +26,7 @@ $ npm install -g @herodevs/cli
16
26
  $ hd COMMAND
17
27
  running command...
18
28
  $ hd (--version)
19
- @herodevs/cli/1.5.0-beta.2 linux-x64 node-v22.14.0
29
+ @herodevs/cli/1.6.0-beta.0 linux-x64 node-v22.14.0
20
30
  $ hd --help [COMMAND]
21
31
  USAGE
22
32
  $ hd COMMAND
@@ -63,7 +73,7 @@ USAGE
63
73
  FLAGS
64
74
  -c, --csv Output in CSV format
65
75
  -m, --months=<value> [default: 12] The number of months of git history to review
66
- -s, --save Save the committers report as nes.committers.<output>
76
+ -s, --save Save the committers report as eol.committers.<output>
67
77
 
68
78
  GLOBAL FLAGS
69
79
  --json Format output as json.
@@ -81,7 +91,7 @@ EXAMPLES
81
91
  $ hd report committers --csv
82
92
  ```
83
93
 
84
- _See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v1.5.0-beta.2/src/commands/report/committers.ts)_
94
+ _See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v1.6.0-beta.0/src/commands/report/committers.ts)_
85
95
 
86
96
  ## `hd report purls`
87
97
 
@@ -95,7 +105,7 @@ FLAGS
95
105
  -c, --csv Save output in CSV format (only applies when using --save)
96
106
  -d, --dir=<value> The directory to scan in order to create a cyclonedx sbom
97
107
  -f, --file=<value> The file path of an existing cyclonedx sbom to scan for EOL
98
- -s, --save Save the list of purls as nes.purls.<output>
108
+ -s, --save Save the list of purls as eol.purls.<output>
99
109
 
100
110
  GLOBAL FLAGS
101
111
  --json Format output as json.
@@ -115,7 +125,7 @@ EXAMPLES
115
125
  $ hd report purls --save --csv
116
126
  ```
117
127
 
118
- _See code: [src/commands/report/purls.ts](https://github.com/herodevs/cli/blob/v1.5.0-beta.2/src/commands/report/purls.ts)_
128
+ _See code: [src/commands/report/purls.ts](https://github.com/herodevs/cli/blob/v1.6.0-beta.0/src/commands/report/purls.ts)_
119
129
 
120
130
  ## `hd scan eol`
121
131
 
@@ -130,7 +140,7 @@ FLAGS
130
140
  -d, --dir=<value> The directory to scan in order to create a cyclonedx sbom
131
141
  -f, --file=<value> The file path of an existing cyclonedx sbom to scan for EOL
132
142
  -p, --purls=<value> The file path of a list of purls to scan for EOL
133
- -s, --save Save the generated SBOM as nes.sbom.json in the scanned directory
143
+ -s, --save Save the generated report as eol.report.json in the scanned directory
134
144
  -t, --table Display the results in a table
135
145
 
136
146
  GLOBAL FLAGS
@@ -149,7 +159,7 @@ EXAMPLES
149
159
  $ hd scan eol -a --dir=./my-project
150
160
  ```
151
161
 
152
- _See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v1.5.0-beta.2/src/commands/scan/eol.ts)_
162
+ _See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v1.6.0-beta.0/src/commands/scan/eol.ts)_
153
163
 
154
164
  ## `hd scan sbom`
155
165
 
@@ -163,7 +173,7 @@ FLAGS
163
173
  -b, --background Run the scan in the background
164
174
  -d, --dir=<value> The directory to scan in order to create a cyclonedx sbom
165
175
  -f, --file=<value> The file path of an existing cyclonedx sbom to scan for EOL
166
- -s, --save Save the generated SBOM as nes.sbom.json in the scanned directory
176
+ -s, --save Save the generated SBOM as eol.sbom.json in the scanned directory
167
177
 
168
178
  GLOBAL FLAGS
169
179
  --json Format output as json.
@@ -177,7 +187,7 @@ EXAMPLES
177
187
  $ hd scan sbom --file=path/to/sbom.json
178
188
  ```
179
189
 
180
- _See code: [src/commands/scan/sbom.ts](https://github.com/herodevs/cli/blob/v1.5.0-beta.2/src/commands/scan/sbom.ts)_
190
+ _See code: [src/commands/scan/sbom.ts](https://github.com/herodevs/cli/blob/v1.6.0-beta.0/src/commands/scan/sbom.ts)_
181
191
 
182
192
  ## `hd update [CHANNEL]`
183
193
 
@@ -215,5 +225,5 @@ EXAMPLES
215
225
  $ hd update --available
216
226
  ```
217
227
 
218
- _See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.6.37/src/commands/update.ts)_
228
+ _See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.6.38/src/commands/update.ts)_
219
229
  <!-- commandsstop -->
package/bin/dev.js CHANGED
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execute } from '@oclif/core';
4
-
5
3
  // Localhost
6
- process.env.GRAPHQL_HOST = 'http://localhost:3000';
4
+ // process.env.GRAPHQL_HOST = 'http://localhost:3000';
7
5
 
8
6
  // Dev
9
- // process.env.GRAPHQL_HOST = 'https://api.dev.nes.herodevs.com';
7
+ process.env.GRAPHQL_HOST = 'https://api.dev.nes.herodevs.com';
10
8
 
11
9
  // Prod
12
10
  // process.env.GRAPHQL_HOST = 'https://api.nes.herodevs.com';
13
11
 
14
- await execute({ development: true, dir: import.meta.url });
12
+ import main from './main.js';
13
+
14
+ try {
15
+ await main(false);
16
+ } catch (error) {
17
+ process.exit(1);
18
+ }
package/bin/main.js ADDED
@@ -0,0 +1,29 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { execute } from '@oclif/core';
3
+
4
+ async function main(isProduction = false) {
5
+ const { positionals } = parseArgs({
6
+ allowPositionals: true,
7
+ strict: false, // Don't validate flags
8
+ });
9
+
10
+ // If no arguments at all, default to scan:eol -t
11
+ if (positionals.length === 0) {
12
+ process.argv.splice(2, 0, 'scan:eol', '-t');
13
+ }
14
+ // If only flags are provided, set scan:eol as the command for those flags
15
+ else if (positionals.length === 1 && positionals[0].startsWith('-')) {
16
+ process.argv.splice(2, 0, 'scan:eol');
17
+ }
18
+
19
+ try {
20
+ await execute({
21
+ development: !isProduction,
22
+ dir: new URL('./dev.js', import.meta.url),
23
+ });
24
+ } catch (error) {
25
+ process.exit(1);
26
+ }
27
+ }
28
+
29
+ export default main;
package/bin/run.js CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execute } from '@oclif/core';
3
+ import main from './main.js';
4
4
 
5
- await execute({ dir: import.meta.url });
5
+ try {
6
+ await main(true);
7
+ } catch (error) {
8
+ process.exit(1);
9
+ }
@@ -26,7 +26,7 @@ export default class Committers extends Command {
26
26
  }),
27
27
  save: Flags.boolean({
28
28
  char: 's',
29
- description: 'Save the committers report as nes.committers.<output>',
29
+ description: 'Save the committers report as eol.committers.<output>',
30
30
  default: false,
31
31
  }),
32
32
  };
@@ -46,7 +46,7 @@ export default class Committers extends Command {
46
46
  // JSON mode
47
47
  if (save) {
48
48
  try {
49
- fs.writeFileSync(path.resolve('nes.committers.json'), JSON.stringify(reportData, null, 2));
49
+ fs.writeFileSync(path.resolve('eol.committers.json'), JSON.stringify(reportData, null, 2));
50
50
  this.log('Report written to json');
51
51
  }
52
52
  catch (error) {
@@ -61,7 +61,7 @@ export default class Committers extends Command {
61
61
  const csvOutput = formatAsCsv(reportData);
62
62
  if (save) {
63
63
  try {
64
- fs.writeFileSync(path.resolve('nes.committers.csv'), csvOutput);
64
+ fs.writeFileSync(path.resolve('eol.committers.csv'), csvOutput);
65
65
  this.log('Report written to csv');
66
66
  }
67
67
  catch (error) {
@@ -75,7 +75,7 @@ export default class Committers extends Command {
75
75
  }
76
76
  if (save) {
77
77
  try {
78
- fs.writeFileSync(path.resolve('nes.committers.txt'), textOutput);
78
+ fs.writeFileSync(path.resolve('eol.committers.txt'), textOutput);
79
79
  this.log('Report written to txt');
80
80
  }
81
81
  catch (error) {
@@ -26,7 +26,7 @@ export default class ReportPurls extends Command {
26
26
  save: Flags.boolean({
27
27
  char: 's',
28
28
  default: false,
29
- description: 'Save the list of purls as nes.purls.<output>',
29
+ description: 'Save the list of purls as eol.purls.<output>',
30
30
  }),
31
31
  csv: Flags.boolean({
32
32
  char: 'c',
@@ -50,7 +50,7 @@ export default class ReportPurls extends Command {
50
50
  if (save) {
51
51
  try {
52
52
  const outputFile = csv && !this.jsonEnabled() ? 'csv' : 'json';
53
- const outputPath = path.join(_dirFlag || process.cwd(), `nes.purls.${outputFile}`);
53
+ const outputPath = path.join(_dirFlag || process.cwd(), `eol.purls.${outputFile}`);
54
54
  const purlOutput = getPurlOutput(purls, outputFile);
55
55
  fs.writeFileSync(outputPath, purlOutput);
56
56
  this.log('Purls saved to %s', outputPath);
@@ -32,7 +32,7 @@ export default class ScanEol extends Command {
32
32
  save: Flags.boolean({
33
33
  char: 's',
34
34
  default: false,
35
- description: 'Save the generated SBOM as nes.sbom.json in the scanned directory',
35
+ description: 'Save the generated report as eol.report.json in the scanned directory',
36
36
  }),
37
37
  all: Flags.boolean({
38
38
  char: 'a',
@@ -107,10 +107,10 @@ export default class ScanEol extends Command {
107
107
  }
108
108
  async saveReport(components) {
109
109
  const { flags } = await this.parse(ScanEol);
110
- const reportPath = path.join(flags.dir || process.cwd(), 'nes.eol.json');
110
+ const reportPath = path.join(flags.dir || process.cwd(), 'eol.report.json');
111
111
  try {
112
112
  fs.writeFileSync(reportPath, JSON.stringify({ components }, null, 2));
113
- this.log('Report saved to nes.eol.json');
113
+ this.log('Report saved to eol.report.json');
114
114
  }
115
115
  catch (error) {
116
116
  if (!isErrnoException(error)) {
@@ -118,10 +118,10 @@ export default class ScanEol extends Command {
118
118
  }
119
119
  switch (error.code) {
120
120
  case 'EACCES':
121
- this.error('Permission denied. Unable to save report to nes.eol.json');
121
+ this.error('Permission denied. Unable to save report to eol.report.json');
122
122
  break;
123
123
  case 'ENOSPC':
124
- this.error('No space left on device. Unable to save report to nes.eol.json');
124
+ this.error('No space left on device. Unable to save report to eol.report.json');
125
125
  break;
126
126
  default:
127
127
  this.error(`Failed to save report: ${getErrorMessage(error)}`);
@@ -23,7 +23,7 @@ export default class ScanSbom extends Command {
23
23
  save: Flags.boolean({
24
24
  char: 's',
25
25
  default: false,
26
- description: 'Save the generated SBOM as nes.sbom.json in the scanned directory',
26
+ description: 'Save the generated SBOM as eol.sbom.json in the scanned directory',
27
27
  }),
28
28
  background: Flags.boolean({
29
29
  char: 'b',
@@ -72,7 +72,7 @@ export default class ScanSbom extends Command {
72
72
  }
73
73
  else if (background) {
74
74
  this._getSbomInBackground(path);
75
- this.log(`The scan is running in the background. The file will be saved at ${path}/nes.sbom.json`);
75
+ this.log(`The scan is running in the background. The file will be saved at ${path}/eol.sbom.json`);
76
76
  return;
77
77
  }
78
78
  else {
@@ -146,7 +146,7 @@ export default class ScanSbom extends Command {
146
146
  }
147
147
  _saveSbom(dir, sbom) {
148
148
  try {
149
- const outputPath = join(dir, 'nes.sbom.json');
149
+ const outputPath = join(dir, 'eol.sbom.json');
150
150
  fs.writeFileSync(outputPath, JSON.stringify(sbom, null, 2));
151
151
  if (!this.jsonEnabled()) {
152
152
  this.log(`SBOM saved to ${outputPath}`);
@@ -1,4 +1,8 @@
1
1
  import type { CdxGenOptions } from './eol.svc.ts';
2
+ export interface SbomDependency {
3
+ ref: string;
4
+ dependsOn: string[];
5
+ }
2
6
  export interface SbomEntry {
3
7
  group: string;
4
8
  name: string;
@@ -7,7 +11,7 @@ export interface SbomEntry {
7
11
  }
8
12
  export interface Sbom {
9
13
  components: SbomEntry[];
10
- dependencies: SbomEntry[];
14
+ dependencies: SbomDependency[];
11
15
  }
12
16
  export declare const SBOM_DEFAULT__OPTIONS: {
13
17
  $0: string;
@@ -29,8 +33,8 @@ export declare const SBOM_DEFAULT__OPTIONS: {
29
33
  'include-formulation': boolean;
30
34
  includeCrypto: boolean;
31
35
  includeFormulation: boolean;
32
- 'install-deps': boolean;
33
- installDeps: boolean;
36
+ 'no-install-deps': boolean;
37
+ noInstallDeps: boolean;
34
38
  'min-confidence': number;
35
39
  minConfidence: number;
36
40
  multiProject: boolean;
@@ -21,8 +21,8 @@ export const SBOM_DEFAULT__OPTIONS = {
21
21
  'include-formulation': false,
22
22
  includeCrypto: false,
23
23
  includeFormulation: false,
24
- 'install-deps': true,
25
- installDeps: true,
24
+ 'no-install-deps': true,
25
+ noInstallDeps: true,
26
26
  'min-confidence': 0,
27
27
  minConfidence: 0,
28
28
  multiProject: true,
@@ -15,7 +15,7 @@ try {
15
15
  const options = JSON.parse(process.argv[2]);
16
16
  const { path, opts } = options;
17
17
  const { bomJson } = await createBom(path, { ...SBOM_DEFAULT__OPTIONS, ...opts });
18
- const outputPath = join(path, 'nes.sbom.json');
18
+ const outputPath = join(path, 'eol.sbom.json');
19
19
  writeFileSync(outputPath, JSON.stringify(bomJson, null, 2));
20
20
  process.exit(0);
21
21
  }
@@ -12,12 +12,12 @@ export declare function formatCsvValue(value: string): string;
12
12
  */
13
13
  export declare function getPurlOutput(purls: string[], output: string): string;
14
14
  /**
15
- * Translate an SBOM to a list of purls for api request.
15
+ * Extract all PURLs from a CycloneDX SBOM, including components and dependencies
16
16
  */
17
- export declare function extractPurls(sbom: Sbom): Promise<string[]>;
17
+ export declare function extractPurls(sbom: Sbom): string[];
18
18
  /**
19
19
  * Parse a purls file in either JSON or text format, including the format of
20
- * nes.purls.json - { purls: [ 'pkg:npm/express@4.18.2', 'pkg:npm/react@18.3.1' ] }
20
+ * eol.purls.json - { purls: [ 'pkg:npm/express@4.18.2', 'pkg:npm/react@18.3.1' ] }
21
21
  * or a text file with one purl per line.
22
22
  */
23
23
  export declare function parsePurlsFile(purlsFileString: string): string[];
@@ -21,19 +21,57 @@ export function getPurlOutput(purls, output) {
21
21
  }
22
22
  }
23
23
  /**
24
- * Translate an SBOM to a list of purls for api request.
24
+ * Extract PURLs from components recursively
25
25
  */
26
- export async function extractPurls(sbom) {
27
- const { components: comps } = sbom;
28
- return comps.map((c) => c.purl) ?? [];
26
+ function extractPurlsFromComponents(components, purlSet) {
27
+ for (const component of components) {
28
+ if (component.purl) {
29
+ purlSet.add(component.purl);
30
+ }
31
+ }
32
+ }
33
+ /**
34
+ * Extract PURLs from dependencies
35
+ */
36
+ function extractPurlsFromDependencies(dependencies, purlSet) {
37
+ for (const dependency of dependencies) {
38
+ if (dependency.ref) {
39
+ purlSet.add(dependency.ref);
40
+ }
41
+ if (dependency.dependsOn) {
42
+ for (const dep of dependency.dependsOn) {
43
+ purlSet.add(dep);
44
+ }
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * Extract all PURLs from a CycloneDX SBOM, including components and dependencies
50
+ */
51
+ export function extractPurls(sbom) {
52
+ const purlSet = new Set();
53
+ // Extract from direct components
54
+ if (sbom.components) {
55
+ extractPurlsFromComponents(sbom.components, purlSet);
56
+ }
57
+ // Extract from dependencies
58
+ if (sbom.dependencies) {
59
+ extractPurlsFromDependencies(sbom.dependencies, purlSet);
60
+ }
61
+ return Array.from(purlSet);
29
62
  }
30
63
  /**
31
64
  * Parse a purls file in either JSON or text format, including the format of
32
- * nes.purls.json - { purls: [ 'pkg:npm/express@4.18.2', 'pkg:npm/react@18.3.1' ] }
65
+ * eol.purls.json - { purls: [ 'pkg:npm/express@4.18.2', 'pkg:npm/react@18.3.1' ] }
33
66
  * or a text file with one purl per line.
34
67
  */
35
68
  export function parsePurlsFile(purlsFileString) {
69
+ // Handle empty string
70
+ if (!purlsFileString.trim()) {
71
+ return [];
72
+ }
36
73
  try {
74
+ // Try parsing as JSON first
37
75
  const parsed = JSON.parse(purlsFileString);
38
76
  if (parsed && Array.isArray(parsed.purls)) {
39
77
  return parsed.purls;
@@ -43,10 +81,16 @@ export function parsePurlsFile(purlsFileString) {
43
81
  }
44
82
  }
45
83
  catch {
84
+ // If not JSON, try parsing as text file
46
85
  const lines = purlsFileString
47
86
  .split('\n')
48
87
  .map((line) => line.trim())
49
88
  .filter((line) => line.length > 0 && line.startsWith('pkg:'));
89
+ // Handle single purl case (no newlines)
90
+ if (lines.length === 0 && purlsFileString.trim().startsWith('pkg:')) {
91
+ return [purlsFileString.trim()];
92
+ }
93
+ // Return any valid purls found
50
94
  if (lines.length > 0) {
51
95
  return lines;
52
96
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herodevs/cli",
3
- "version": "1.5.0-beta.2",
3
+ "version": "1.6.0-beta.0",
4
4
  "author": "HeroDevs, Inc",
5
5
  "bin": {
6
6
  "hd": "./bin/run.js"
@@ -17,7 +17,7 @@
17
17
  "ci": "biome ci",
18
18
  "ci:fix": "biome check --write",
19
19
  "clean": "shx rm -rf dist && npm run clean:files && shx rm -rf node_modules",
20
- "clean:files": "shx rm -f nes.**.csv nes.**.json nes.**.text",
20
+ "clean:files": "shx rm -f eol.**.csv eol.**.json eol.**.text",
21
21
  "dev": "npm run build && ./bin/dev.js",
22
22
  "dev:debug": "npm run build && DEBUG=oclif:* ./bin/dev.js",
23
23
  "format": "biome format --write",
@@ -26,10 +26,6 @@
26
26
  "prepack": "oclif manifest && oclif readme",
27
27
  "pretest": "npm run lint && npm run typecheck",
28
28
  "readme": "npm run ci:fix && npm run build && npm exec oclif readme",
29
- "release": "./scripts/release.sh",
30
- "pre:release:publish": "npm run prepack && git add README.md",
31
- "release:publish:beta": "npm run release -- --publish",
32
- "release:publish:latest": "npm run release -- --latest --publish",
33
29
  "test": "globstar -- node --import tsx --test \"test/**/*.test.ts\"",
34
30
  "test:e2e": "globstar -- node --import tsx --test \"e2e/**/*.test.ts\"",
35
31
  "typecheck": "tsc --noEmit"
@@ -41,7 +37,7 @@
41
37
  ],
42
38
  "dependencies": {
43
39
  "@apollo/client": "^3.13.8",
44
- "@cyclonedx/cdxgen": "^11.2.4",
40
+ "@cyclonedx/cdxgen": "^11.2.5",
45
41
  "@oclif/core": "^4",
46
42
  "@oclif/plugin-help": "^6",
47
43
  "@oclif/plugin-update": "^4",
@@ -57,7 +53,6 @@
57
53
  "@types/node": "^22",
58
54
  "@types/sinon": "^17.0.4",
59
55
  "@types/update-notifier": "^6.0.8",
60
- "commit-and-tag-version": "^12.5.1",
61
56
  "globstar": "^1.0.0",
62
57
  "oclif": "^4",
63
58
  "shx": "^0.4.0",
@@ -86,7 +81,7 @@
86
81
  "@oclif/plugin-update"
87
82
  ],
88
83
  "hooks": {
89
- "init": "./dist/hooks/npm-update-notifier",
84
+ "init": "./dist/hooks/npm-update-notifier.js",
90
85
  "prerun": "./dist/hooks/prerun.js"
91
86
  },
92
87
  "topicSeparator": " ",