@herodevs/cli 1.0.0-beta.1 → 1.1.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.
package/README.md CHANGED
@@ -16,7 +16,7 @@ $ npm install -g @herodevs/cli
16
16
  $ hd COMMAND
17
17
  running command...
18
18
  $ hd (--version)
19
- @herodevs/cli/1.0.0-beta.1 linux-x64 node-v22.14.0
19
+ @herodevs/cli/1.1.0-beta.1 linux-x64 node-v22.14.0
20
20
  $ hd --help [COMMAND]
21
21
  USAGE
22
22
  $ hd COMMAND
@@ -25,24 +25,344 @@ USAGE
25
25
  <!-- usagestop -->
26
26
  ## Commands
27
27
  <!-- commands -->
28
+ * [`hd help [COMMAND]`](#hd-help-command)
29
+ * [`hd plugins`](#hd-plugins)
30
+ * [`hd plugins add PLUGIN`](#hd-plugins-add-plugin)
31
+ * [`hd plugins:inspect PLUGIN...`](#hd-pluginsinspect-plugin)
32
+ * [`hd plugins install PLUGIN`](#hd-plugins-install-plugin)
33
+ * [`hd plugins link PATH`](#hd-plugins-link-path)
34
+ * [`hd plugins remove [PLUGIN]`](#hd-plugins-remove-plugin)
35
+ * [`hd plugins reset`](#hd-plugins-reset)
36
+ * [`hd plugins uninstall [PLUGIN]`](#hd-plugins-uninstall-plugin)
37
+ * [`hd plugins unlink [PLUGIN]`](#hd-plugins-unlink-plugin)
38
+ * [`hd plugins update`](#hd-plugins-update)
28
39
  * [`hd report committers`](#hd-report-committers)
29
40
  * [`hd report purls`](#hd-report-purls)
30
41
  * [`hd scan eol`](#hd-scan-eol)
31
42
  * [`hd scan sbom`](#hd-scan-sbom)
32
43
 
44
+ ## `hd help [COMMAND]`
45
+
46
+ Display help for hd.
47
+
48
+ ```
49
+ USAGE
50
+ $ hd help [COMMAND...] [-n]
51
+
52
+ ARGUMENTS
53
+ COMMAND... Command to show help for.
54
+
55
+ FLAGS
56
+ -n, --nested-commands Include all nested commands in the output.
57
+
58
+ DESCRIPTION
59
+ Display help for hd.
60
+ ```
61
+
62
+ _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.27/src/commands/help.ts)_
63
+
64
+ ## `hd plugins`
65
+
66
+ List installed plugins.
67
+
68
+ ```
69
+ USAGE
70
+ $ hd plugins [--json] [--core]
71
+
72
+ FLAGS
73
+ --core Show core plugins.
74
+
75
+ GLOBAL FLAGS
76
+ --json Format output as json.
77
+
78
+ DESCRIPTION
79
+ List installed plugins.
80
+
81
+ EXAMPLES
82
+ $ hd plugins
83
+ ```
84
+
85
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.36/src/commands/plugins/index.ts)_
86
+
87
+ ## `hd plugins add PLUGIN`
88
+
89
+ Installs a plugin into hd.
90
+
91
+ ```
92
+ USAGE
93
+ $ hd plugins add PLUGIN... [--json] [-f] [-h] [-s | -v]
94
+
95
+ ARGUMENTS
96
+ PLUGIN... Plugin to install.
97
+
98
+ FLAGS
99
+ -f, --force Force npm to fetch remote resources even if a local copy exists on disk.
100
+ -h, --help Show CLI help.
101
+ -s, --silent Silences npm output.
102
+ -v, --verbose Show verbose npm output.
103
+
104
+ GLOBAL FLAGS
105
+ --json Format output as json.
106
+
107
+ DESCRIPTION
108
+ Installs a plugin into hd.
109
+
110
+ Uses npm to install plugins.
111
+
112
+ Installation of a user-installed plugin will override a core plugin.
113
+
114
+ Use the HD_NPM_LOG_LEVEL environment variable to set the npm loglevel.
115
+ Use the HD_NPM_REGISTRY environment variable to set the npm registry.
116
+
117
+ ALIASES
118
+ $ hd plugins add
119
+
120
+ EXAMPLES
121
+ Install a plugin from npm registry.
122
+
123
+ $ hd plugins add myplugin
124
+
125
+ Install a plugin from a github url.
126
+
127
+ $ hd plugins add https://github.com/someuser/someplugin
128
+
129
+ Install a plugin from a github slug.
130
+
131
+ $ hd plugins add someuser/someplugin
132
+ ```
133
+
134
+ ## `hd plugins:inspect PLUGIN...`
135
+
136
+ Displays installation properties of a plugin.
137
+
138
+ ```
139
+ USAGE
140
+ $ hd plugins inspect PLUGIN...
141
+
142
+ ARGUMENTS
143
+ PLUGIN... [default: .] Plugin to inspect.
144
+
145
+ FLAGS
146
+ -h, --help Show CLI help.
147
+ -v, --verbose
148
+
149
+ GLOBAL FLAGS
150
+ --json Format output as json.
151
+
152
+ DESCRIPTION
153
+ Displays installation properties of a plugin.
154
+
155
+ EXAMPLES
156
+ $ hd plugins inspect myplugin
157
+ ```
158
+
159
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.36/src/commands/plugins/inspect.ts)_
160
+
161
+ ## `hd plugins install PLUGIN`
162
+
163
+ Installs a plugin into hd.
164
+
165
+ ```
166
+ USAGE
167
+ $ hd plugins install PLUGIN... [--json] [-f] [-h] [-s | -v]
168
+
169
+ ARGUMENTS
170
+ PLUGIN... Plugin to install.
171
+
172
+ FLAGS
173
+ -f, --force Force npm to fetch remote resources even if a local copy exists on disk.
174
+ -h, --help Show CLI help.
175
+ -s, --silent Silences npm output.
176
+ -v, --verbose Show verbose npm output.
177
+
178
+ GLOBAL FLAGS
179
+ --json Format output as json.
180
+
181
+ DESCRIPTION
182
+ Installs a plugin into hd.
183
+
184
+ Uses npm to install plugins.
185
+
186
+ Installation of a user-installed plugin will override a core plugin.
187
+
188
+ Use the HD_NPM_LOG_LEVEL environment variable to set the npm loglevel.
189
+ Use the HD_NPM_REGISTRY environment variable to set the npm registry.
190
+
191
+ ALIASES
192
+ $ hd plugins add
193
+
194
+ EXAMPLES
195
+ Install a plugin from npm registry.
196
+
197
+ $ hd plugins install myplugin
198
+
199
+ Install a plugin from a github url.
200
+
201
+ $ hd plugins install https://github.com/someuser/someplugin
202
+
203
+ Install a plugin from a github slug.
204
+
205
+ $ hd plugins install someuser/someplugin
206
+ ```
207
+
208
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.36/src/commands/plugins/install.ts)_
209
+
210
+ ## `hd plugins link PATH`
211
+
212
+ Links a plugin into the CLI for development.
213
+
214
+ ```
215
+ USAGE
216
+ $ hd plugins link PATH [-h] [--install] [-v]
217
+
218
+ ARGUMENTS
219
+ PATH [default: .] path to plugin
220
+
221
+ FLAGS
222
+ -h, --help Show CLI help.
223
+ -v, --verbose
224
+ --[no-]install Install dependencies after linking the plugin.
225
+
226
+ DESCRIPTION
227
+ Links a plugin into the CLI for development.
228
+
229
+ Installation of a linked plugin will override a user-installed or core plugin.
230
+
231
+ e.g. If you have a user-installed or core plugin that has a 'hello' command, installing a linked plugin with a 'hello'
232
+ command will override the user-installed or core plugin implementation. This is useful for development work.
233
+
234
+
235
+ EXAMPLES
236
+ $ hd plugins link myplugin
237
+ ```
238
+
239
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.36/src/commands/plugins/link.ts)_
240
+
241
+ ## `hd plugins remove [PLUGIN]`
242
+
243
+ Removes a plugin from the CLI.
244
+
245
+ ```
246
+ USAGE
247
+ $ hd plugins remove [PLUGIN...] [-h] [-v]
248
+
249
+ ARGUMENTS
250
+ PLUGIN... plugin to uninstall
251
+
252
+ FLAGS
253
+ -h, --help Show CLI help.
254
+ -v, --verbose
255
+
256
+ DESCRIPTION
257
+ Removes a plugin from the CLI.
258
+
259
+ ALIASES
260
+ $ hd plugins unlink
261
+ $ hd plugins remove
262
+
263
+ EXAMPLES
264
+ $ hd plugins remove myplugin
265
+ ```
266
+
267
+ ## `hd plugins reset`
268
+
269
+ Remove all user-installed and linked plugins.
270
+
271
+ ```
272
+ USAGE
273
+ $ hd plugins reset [--hard] [--reinstall]
274
+
275
+ FLAGS
276
+ --hard Delete node_modules and package manager related files in addition to uninstalling plugins.
277
+ --reinstall Reinstall all plugins after uninstalling.
278
+ ```
279
+
280
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.36/src/commands/plugins/reset.ts)_
281
+
282
+ ## `hd plugins uninstall [PLUGIN]`
283
+
284
+ Removes a plugin from the CLI.
285
+
286
+ ```
287
+ USAGE
288
+ $ hd plugins uninstall [PLUGIN...] [-h] [-v]
289
+
290
+ ARGUMENTS
291
+ PLUGIN... plugin to uninstall
292
+
293
+ FLAGS
294
+ -h, --help Show CLI help.
295
+ -v, --verbose
296
+
297
+ DESCRIPTION
298
+ Removes a plugin from the CLI.
299
+
300
+ ALIASES
301
+ $ hd plugins unlink
302
+ $ hd plugins remove
303
+
304
+ EXAMPLES
305
+ $ hd plugins uninstall myplugin
306
+ ```
307
+
308
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.36/src/commands/plugins/uninstall.ts)_
309
+
310
+ ## `hd plugins unlink [PLUGIN]`
311
+
312
+ Removes a plugin from the CLI.
313
+
314
+ ```
315
+ USAGE
316
+ $ hd plugins unlink [PLUGIN...] [-h] [-v]
317
+
318
+ ARGUMENTS
319
+ PLUGIN... plugin to uninstall
320
+
321
+ FLAGS
322
+ -h, --help Show CLI help.
323
+ -v, --verbose
324
+
325
+ DESCRIPTION
326
+ Removes a plugin from the CLI.
327
+
328
+ ALIASES
329
+ $ hd plugins unlink
330
+ $ hd plugins remove
331
+
332
+ EXAMPLES
333
+ $ hd plugins unlink myplugin
334
+ ```
335
+
336
+ ## `hd plugins update`
337
+
338
+ Update installed plugins.
339
+
340
+ ```
341
+ USAGE
342
+ $ hd plugins update [-h] [-v]
343
+
344
+ FLAGS
345
+ -h, --help Show CLI help.
346
+ -v, --verbose
347
+
348
+ DESCRIPTION
349
+ Update installed plugins.
350
+ ```
351
+
352
+ _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.36/src/commands/plugins/update.ts)_
353
+
33
354
  ## `hd report committers`
34
355
 
35
356
  Generate report of committers to a git repository
36
357
 
37
358
  ```
38
359
  USAGE
39
- $ hd report committers [--json] [-m <value>] [-o text|json|csv] [-s]
360
+ $ hd report committers [--json] [-m <value>] [-c] [-s]
40
361
 
41
362
  FLAGS
42
- -m, --months=<value> [default: 12] The number of months of git history to review
43
- -o, --output=<option> [default: text] Output format: text, json, or csv
44
- <options: text|json|csv>
45
- -s, --save Save the committers report as nes.committers.<output>
363
+ -c, --csv Output in CSV format
364
+ -m, --months=<value> [default: 12] The number of months of git history to review
365
+ -s, --save Save the committers report as nes.committers.<output>
46
366
 
47
367
  GLOBAL FLAGS
48
368
  --json Format output as json.
@@ -53,14 +373,14 @@ DESCRIPTION
53
373
  EXAMPLES
54
374
  $ hd report committers
55
375
 
56
- $ hd report committers -o csv -s
376
+ $ hd report committers --csv -s
57
377
 
58
- $ hd report committers --output=json
378
+ $ hd report committers --json
59
379
 
60
- $ hd report committers --output=csv
380
+ $ hd report committers --csv
61
381
  ```
62
382
 
63
- _See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v1.0.0-beta.1/src/commands/report/committers.ts)_
383
+ _See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v1.1.0-beta.1/src/commands/report/committers.ts)_
64
384
 
65
385
  ## `hd report purls`
66
386
 
@@ -68,14 +388,13 @@ Generate a list of purls from a sbom
68
388
 
69
389
  ```
70
390
  USAGE
71
- $ hd report purls [--json] [-f <value>] [-d <value>] [-s] [-o json|csv]
391
+ $ hd report purls [--json] [-f <value>] [-d <value>] [-s] [-c]
72
392
 
73
393
  FLAGS
74
- -d, --dir=<value> The directory to scan in order to create a cyclonedx sbom
75
- -f, --file=<value> The file path of an existing cyclonedx sbom to scan for EOL
76
- -o, --output=<option> [default: json] The format of the saved file (when using --save)
77
- <options: json|csv>
78
- -s, --save Save the list of purls as nes.purls.<output>
394
+ -c, --csv Save output in CSV format (only applies when using --save)
395
+ -d, --dir=<value> The directory to scan in order to create a cyclonedx sbom
396
+ -f, --file=<value> The file path of an existing cyclonedx sbom to scan for EOL
397
+ -s, --save Save the list of purls as nes.purls.<output>
79
398
 
80
399
  GLOBAL FLAGS
81
400
  --json Format output as json.
@@ -84,16 +403,18 @@ DESCRIPTION
84
403
  Generate a list of purls from a sbom
85
404
 
86
405
  EXAMPLES
406
+ $ hd report purls --json -s
407
+
87
408
  $ hd report purls --dir=./my-project
88
409
 
89
410
  $ hd report purls --file=path/to/sbom.json
90
411
 
91
412
  $ hd report purls --dir=./my-project --save
92
413
 
93
- $ hd report purls --save --output=csv
414
+ $ hd report purls --save --csv
94
415
  ```
95
416
 
96
- _See code: [src/commands/report/purls.ts](https://github.com/herodevs/cli/blob/v1.0.0-beta.1/src/commands/report/purls.ts)_
417
+ _See code: [src/commands/report/purls.ts](https://github.com/herodevs/cli/blob/v1.1.0-beta.1/src/commands/report/purls.ts)_
97
418
 
98
419
  ## `hd scan eol`
99
420
 
@@ -120,7 +441,7 @@ EXAMPLES
120
441
  $ hd scan eol --file=path/to/sbom.json
121
442
  ```
122
443
 
123
- _See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v1.0.0-beta.1/src/commands/scan/eol.ts)_
444
+ _See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v1.1.0-beta.1/src/commands/scan/eol.ts)_
124
445
 
125
446
  ## `hd scan sbom`
126
447
 
@@ -128,9 +449,10 @@ Scan a SBOM for purls
128
449
 
129
450
  ```
130
451
  USAGE
131
- $ hd scan sbom [--json] [-f <value>] [-d <value>] [-s]
452
+ $ hd scan sbom [--json] [-f <value>] [-d <value>] [-s] [-b]
132
453
 
133
454
  FLAGS
455
+ -b, --background Run the scan in the background
134
456
  -d, --dir=<value> The directory to scan in order to create a cyclonedx sbom
135
457
  -f, --file=<value> The file path of an existing cyclonedx sbom to scan for EOL
136
458
  -s, --save Save the generated SBOM as nes.sbom.json in the scanned directory
@@ -147,5 +469,5 @@ EXAMPLES
147
469
  $ hd scan sbom --file=path/to/sbom.json
148
470
  ```
149
471
 
150
- _See code: [src/commands/scan/sbom.ts](https://github.com/herodevs/cli/blob/v1.0.0-beta.1/src/commands/scan/sbom.ts)_
472
+ _See code: [src/commands/scan/sbom.ts](https://github.com/herodevs/cli/blob/v1.1.0-beta.1/src/commands/scan/sbom.ts)_
151
473
  <!-- commandsstop -->
package/bin/dev.js CHANGED
@@ -5,7 +5,6 @@ process.env.GRAPHQL_HOST = 'http://localhost:3000';
5
5
  async function run() {
6
6
  const oclif = await import('@oclif/core');
7
7
  await oclif.execute({ development: true, dir: import.meta.dirname });
8
- console.log('\n\n\n=> OCLIF: Command complete.');
9
8
  }
10
9
 
11
10
  run();
@@ -1,14 +1,15 @@
1
1
  import { Command } from '@oclif/core';
2
+ import { type ReportData } from '../../service/committers.svc.ts';
2
3
  export default class Committers extends Command {
3
4
  static description: string;
4
5
  static enableJsonFlag: boolean;
5
6
  static examples: string[];
6
7
  static flags: {
7
8
  months: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
8
- output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
10
  save: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  };
11
- run(): Promise<void>;
12
+ run(): Promise<ReportData | string>;
12
13
  /**
13
14
  * Generates structured report data
14
15
  * @param entries - parsed git log output for commits
@@ -2,15 +2,15 @@ import { spawnSync } from 'node:child_process';
2
2
  import { Command, Flags } from '@oclif/core';
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
- import { calculateOverallStats, formatOutputBasedOnFlag, groupCommitsByMonth, parseGitLogOutput, } from "../../service/committers.svc.js";
5
+ import { calculateOverallStats, formatAsCsv, formatAsText, groupCommitsByMonth, parseGitLogOutput, } from "../../service/committers.svc.js";
6
6
  export default class Committers extends Command {
7
7
  static description = 'Generate report of committers to a git repository';
8
8
  static enableJsonFlag = true;
9
9
  static examples = [
10
10
  '<%= config.bin %> <%= command.id %>',
11
- '<%= config.bin %> <%= command.id %> -o csv -s',
12
- '<%= config.bin %> <%= command.id %> --output=json',
13
- '<%= config.bin %> <%= command.id %> --output=csv',
11
+ '<%= config.bin %> <%= command.id %> --csv -s',
12
+ '<%= config.bin %> <%= command.id %> --json',
13
+ '<%= config.bin %> <%= command.id %> --csv',
14
14
  ];
15
15
  static flags = {
16
16
  months: Flags.integer({
@@ -18,11 +18,10 @@ export default class Committers extends Command {
18
18
  description: 'The number of months of git history to review',
19
19
  default: 12,
20
20
  }),
21
- output: Flags.string({
22
- char: 'o',
23
- description: 'Output format: text, json, or csv',
24
- options: ['text', 'json', 'csv'],
25
- default: 'text',
21
+ csv: Flags.boolean({
22
+ char: 'c',
23
+ description: 'Output in CSV format',
24
+ default: false,
26
25
  }),
27
26
  save: Flags.boolean({
28
27
  char: 's',
@@ -32,24 +31,50 @@ export default class Committers extends Command {
32
31
  };
33
32
  async run() {
34
33
  const { flags } = await this.parse(Committers);
35
- const { months, output, save } = flags;
34
+ const { months, csv, save } = flags;
35
+ const isJson = this.jsonEnabled();
36
36
  const sinceDate = `${months} months ago`;
37
+ this.log('Starting committers report with flags: %O', flags);
37
38
  try {
38
39
  // Generate structured report data
39
40
  const entries = this.fetchGitCommitData(sinceDate);
41
+ this.log('Fetched %d commit entries', entries.length);
40
42
  const reportData = this.generateReportData(entries);
41
- const formattedOutput = formatOutputBasedOnFlag(output, reportData);
42
- // Output to file or stdout
43
+ // Handle different output scenarios
44
+ if (isJson) {
45
+ // JSON mode
46
+ if (save) {
47
+ fs.writeFileSync(path.resolve('nes.committers.json'), JSON.stringify(reportData, null, 2));
48
+ this.log('Report written to json');
49
+ }
50
+ return reportData;
51
+ }
52
+ if (csv) {
53
+ // CSV mode
54
+ const csvOutput = formatAsCsv(reportData);
55
+ if (save) {
56
+ fs.writeFileSync(path.resolve('nes.committers.csv'), csvOutput);
57
+ this.log('Report written to csv');
58
+ }
59
+ else {
60
+ this.log(csvOutput);
61
+ }
62
+ return csvOutput;
63
+ }
64
+ // Text mode
65
+ const textOutput = formatAsText(reportData);
43
66
  if (save) {
44
- fs.writeFileSync(path.resolve(`nes.committers.${output}`), formattedOutput);
45
- this.log(`Report written to ${output}`);
67
+ fs.writeFileSync(path.resolve('nes.committers.txt'), textOutput);
68
+ this.log('Report written to txt');
46
69
  }
47
70
  else {
48
- this.log(formattedOutput);
71
+ this.log(textOutput);
49
72
  }
73
+ return textOutput;
50
74
  }
51
75
  catch (error) {
52
76
  this.error(`Failed to generate report: ${error.message}`);
77
+ throw error;
53
78
  }
54
79
  }
55
80
  /**
@@ -7,7 +7,9 @@ export default class ReportPurls extends Command {
7
7
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
8
  dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
9
  save: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
- output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  };
12
- run(): Promise<string[]>;
12
+ run(): Promise<{
13
+ purls: string[];
14
+ }>;
13
15
  }
@@ -7,10 +7,11 @@ export default class ReportPurls extends Command {
7
7
  static description = 'Generate a list of purls from a sbom';
8
8
  static enableJsonFlag = true;
9
9
  static examples = [
10
+ '<%= config.bin %> <%= command.id %> --json -s',
10
11
  '<%= config.bin %> <%= command.id %> --dir=./my-project',
11
12
  '<%= config.bin %> <%= command.id %> --file=path/to/sbom.json',
12
13
  '<%= config.bin %> <%= command.id %> --dir=./my-project --save',
13
- '<%= config.bin %> <%= command.id %> --save --output=csv',
14
+ '<%= config.bin %> <%= command.id %> --save --csv',
14
15
  ];
15
16
  static flags = {
16
17
  file: Flags.string({
@@ -26,41 +27,45 @@ export default class ReportPurls extends Command {
26
27
  default: false,
27
28
  description: 'Save the list of purls as nes.purls.<output>',
28
29
  }),
29
- output: Flags.string({
30
- char: 'o',
31
- options: ['json', 'csv'],
32
- default: 'json',
33
- description: 'The format of the saved file (when using --save)',
30
+ csv: Flags.boolean({
31
+ char: 'c',
32
+ default: false,
33
+ description: 'Save output in CSV format (only applies when using --save)',
34
34
  }),
35
35
  };
36
36
  async run() {
37
37
  const { flags } = await this.parse(ReportPurls);
38
- const { dir: _dirFlag, file: _fileFlag, save, output } = flags;
39
- // Load the SBOM: Only pass the file, dir, and save flags to SbomScan
38
+ const { dir: _dirFlag, file: _fileFlag, save, csv } = flags;
40
39
  const sbomArgs = SbomScan.getSbomArgs(flags);
41
40
  const sbomCommand = new SbomScan(sbomArgs, this.config);
42
41
  const sbom = await sbomCommand.run();
43
- // Extract purls from SBOM
42
+ if (!sbom) {
43
+ throw new Error('SBOM not generated');
44
+ }
44
45
  const purls = await extractPurls(sbom);
46
+ this.log('Extracted %d purls from SBOM', purls.length);
45
47
  ux.action.stop('Scan completed');
46
48
  // Print the purls
47
- this.log('Found purls:');
48
49
  for (const purl of purls) {
49
50
  this.log(purl);
50
51
  }
51
52
  // Save if requested
52
53
  if (save) {
53
54
  try {
54
- const outputPath = path.join(_dirFlag || process.cwd(), `nes.purls.${output}`);
55
- const purlOutput = getPurlOutput(purls, output);
55
+ const outputFile = csv && !this.jsonEnabled() ? 'csv' : 'json';
56
+ const outputPath = path.join(_dirFlag || process.cwd(), `nes.purls.${outputFile}`);
57
+ const purlOutput = getPurlOutput(purls, outputFile);
56
58
  fs.writeFileSync(outputPath, purlOutput);
57
- this.log(`\nPurls saved to ${outputPath}`);
59
+ this.log('Purls saved to %s', outputPath);
58
60
  }
59
61
  catch (error) {
60
62
  const errorMessage = error && typeof error === 'object' && 'message' in error ? error.message : 'Unknown error';
61
63
  this.warn(`Failed to save purls: ${errorMessage}`);
62
64
  }
63
65
  }
64
- return purls;
66
+ // Return wrapped object with metadata
67
+ return {
68
+ purls,
69
+ };
65
70
  }
66
71
  }
@@ -32,6 +32,9 @@ export default class ScanEol extends Command {
32
32
  const sbomArgs = SbomScan.getSbomArgs(flags);
33
33
  const sbomCommand = new SbomScan(sbomArgs, this.config);
34
34
  const sbom = await sbomCommand.run();
35
+ if (!sbom) {
36
+ throw new Error('SBOM not generated');
37
+ }
35
38
  // Scan the SBOM for EOL information
36
39
  const { purls, scan } = await scanForEol(sbom);
37
40
  ux.action.stop('Scan completed');
@@ -8,11 +8,13 @@ export default class ScanSbom extends Command {
8
8
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
9
  dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  save: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ background: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
12
  };
12
13
  static getSbomArgs(flags: Record<string, string>): string[];
13
14
  getScanOptions(): {};
14
- run(): Promise<Sbom>;
15
+ run(): Promise<Sbom | undefined>;
15
16
  private _getSbomFromScan;
17
+ private _getSbomInBackground;
16
18
  private _getSbomFromFile;
17
19
  private _saveSbom;
18
20
  }
@@ -1,6 +1,7 @@
1
- import path from 'node:path';
2
- import { Command, Flags, ux } from '@oclif/core';
1
+ import { spawn } from 'node:child_process';
3
2
  import fs from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import { Command, Flags, ux } from '@oclif/core';
4
5
  import { createSbom, validateIsCycloneDxSbom } from "../../service/eol/eol.svc.js";
5
6
  export default class ScanSbom extends Command {
6
7
  static description = 'Scan a SBOM for purls';
@@ -23,9 +24,14 @@ export default class ScanSbom extends Command {
23
24
  default: false,
24
25
  description: 'Save the generated SBOM as nes.sbom.json in the scanned directory',
25
26
  }),
27
+ background: Flags.boolean({
28
+ char: 'b',
29
+ default: false,
30
+ description: 'Run the scan in the background',
31
+ }),
26
32
  };
27
33
  static getSbomArgs(flags) {
28
- const { dir, file, save } = flags ?? {};
34
+ const { dir, file, save, json, background } = flags ?? {};
29
35
  const sbomArgs = [];
30
36
  if (file)
31
37
  sbomArgs.push('--file', file);
@@ -33,6 +39,10 @@ export default class ScanSbom extends Command {
33
39
  sbomArgs.push('--dir', dir);
34
40
  if (save)
35
41
  sbomArgs.push('--save');
42
+ if (json)
43
+ sbomArgs.push('--json');
44
+ if (background)
45
+ sbomArgs.push('--background');
36
46
  return sbomArgs;
37
47
  }
38
48
  getScanOptions() {
@@ -41,26 +51,31 @@ export default class ScanSbom extends Command {
41
51
  }
42
52
  async run() {
43
53
  const { flags } = await this.parse(ScanSbom);
44
- const { dir: _dirFlag, save, file: _fileFlag } = flags;
54
+ const { dir, save, file, background } = flags;
45
55
  // Validate that exactly one of --file or --dir is provided
46
- if (_fileFlag && _dirFlag) {
56
+ if (file && dir) {
47
57
  throw new Error('Cannot specify both --file and --dir flags. Please use one or the other.');
48
58
  }
49
59
  let sbom;
50
- if (_fileFlag) {
51
- sbom = this._getSbomFromFile(_fileFlag);
60
+ const path = dir || process.cwd();
61
+ if (file) {
62
+ sbom = this._getSbomFromFile(file);
63
+ }
64
+ else if (background) {
65
+ this._getSbomInBackground(path);
66
+ this.log(`The scan is running in the background. The file will be saved at ${path}/nes.sbom.json`);
67
+ return;
52
68
  }
53
69
  else {
54
- const _dir = _dirFlag || process.cwd();
55
- sbom = await this._getSbomFromScan(_dir);
70
+ sbom = await this._getSbomFromScan(path);
56
71
  if (save) {
57
- this._saveSbom(_dir, sbom);
72
+ this._saveSbom(path, sbom);
58
73
  }
59
74
  }
60
75
  return sbom;
61
76
  }
62
77
  async _getSbomFromScan(_dirFlag) {
63
- const dir = path.resolve(_dirFlag);
78
+ const dir = resolve(_dirFlag);
64
79
  if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
65
80
  throw new Error(`Directory not found or not a directory: ${dir}`);
66
81
  }
@@ -72,8 +87,23 @@ export default class ScanSbom extends Command {
72
87
  }
73
88
  return sbom;
74
89
  }
90
+ _getSbomInBackground(path) {
91
+ const opts = this.getScanOptions();
92
+ const args = [
93
+ JSON.stringify({
94
+ opts,
95
+ path,
96
+ }),
97
+ ];
98
+ const workerProcess = spawn('node', [join(import.meta.dirname, '../../service/eol/sbom.worker.js'), ...args], {
99
+ stdio: 'ignore',
100
+ detached: true,
101
+ env: { ...process.env },
102
+ });
103
+ workerProcess.unref();
104
+ }
75
105
  _getSbomFromFile(_fileFlag) {
76
- const file = path.resolve(_fileFlag);
106
+ const file = resolve(_fileFlag);
77
107
  if (!fs.existsSync(file)) {
78
108
  throw new Error(`SBOM file not found: ${file}`);
79
109
  }
@@ -94,9 +124,11 @@ export default class ScanSbom extends Command {
94
124
  }
95
125
  _saveSbom(dir, sbom) {
96
126
  try {
97
- const outputPath = path.join(dir, 'nes.sbom.json');
127
+ const outputPath = join(dir, 'nes.sbom.json');
98
128
  fs.writeFileSync(outputPath, JSON.stringify(sbom, null, 2));
99
- this.log(`SBOM saved to ${outputPath}`);
129
+ if (!this.jsonEnabled()) {
130
+ this.log(`SBOM saved to ${outputPath}`);
131
+ }
100
132
  }
101
133
  catch (error) {
102
134
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
@@ -0,0 +1,8 @@
1
+ import debug from 'debug';
2
+ const hook = async (opts) => {
3
+ // If JSON flag is enabled, silence debug logging
4
+ if (opts.Command.prototype.jsonEnabled()) {
5
+ debug.disable();
6
+ }
7
+ };
8
+ export default hook;
@@ -9,6 +9,58 @@ export interface Sbom {
9
9
  components: SbomEntry[];
10
10
  dependencies: SbomEntry[];
11
11
  }
12
+ export declare const SBOM_DEFAULT__OPTIONS: {
13
+ $0: string;
14
+ _: never[];
15
+ 'auto-compositions': boolean;
16
+ autoCompositions: boolean;
17
+ 'data-flow-slices-file': string;
18
+ dataFlowSlicesFile: string;
19
+ deep: boolean;
20
+ 'deps-slices-file': string;
21
+ depsSlicesFile: string;
22
+ evidence: boolean;
23
+ 'export-proto': boolean;
24
+ exportProto: boolean;
25
+ 'fail-on-error': boolean;
26
+ failOnError: boolean;
27
+ false: boolean;
28
+ 'include-crypto': boolean;
29
+ 'include-formulation': boolean;
30
+ includeCrypto: boolean;
31
+ includeFormulation: boolean;
32
+ 'install-deps': boolean;
33
+ installDeps: boolean;
34
+ 'min-confidence': number;
35
+ minConfidence: number;
36
+ multiProject: boolean;
37
+ 'no-banner': boolean;
38
+ noBabel: boolean;
39
+ noBanner: boolean;
40
+ o: string;
41
+ output: string;
42
+ outputFormat: string;
43
+ profile: string;
44
+ project: undefined;
45
+ 'project-version': string;
46
+ projectVersion: string;
47
+ 'proto-bin-file': string;
48
+ protoBinFile: string;
49
+ r: boolean;
50
+ 'reachables-slices-file': string;
51
+ reachablesSlicesFile: string;
52
+ recurse: boolean;
53
+ requiredOnly: boolean;
54
+ 'semantics-slices-file': string;
55
+ semanticsSlicesFile: string;
56
+ 'skip-dt-tls-check': boolean;
57
+ skipDtTlsCheck: boolean;
58
+ 'spec-version': number;
59
+ specVersion: number;
60
+ 'usages-slices-file': string;
61
+ usagesSlicesFile: string;
62
+ validate: boolean;
63
+ };
12
64
  /**
13
65
  * Lazy loads cdxgen (for ESM purposes), scans
14
66
  * `directory`, and returns the `bomJson` property.
@@ -1,71 +1,66 @@
1
- import { log } from "../../service/log.svc.js";
1
+ import { debugLogger } from "../../service/log.svc.js";
2
+ export const SBOM_DEFAULT__OPTIONS = {
3
+ $0: 'cdxgen',
4
+ _: [],
5
+ 'auto-compositions': true,
6
+ autoCompositions: true,
7
+ 'data-flow-slices-file': 'data-flow.slices.json',
8
+ dataFlowSlicesFile: 'data-flow.slices.json',
9
+ deep: false, // TODO: you def want to check this out
10
+ 'deps-slices-file': 'deps.slices.json',
11
+ depsSlicesFile: 'deps.slices.json',
12
+ evidence: false,
13
+ 'export-proto': false,
14
+ exportProto: false,
15
+ // DON'T FAIL ON ERROR; you won't get hlepful logs
16
+ 'fail-on-error': false,
17
+ failOnError: false,
18
+ false: true,
19
+ 'include-crypto': false,
20
+ 'include-formulation': false,
21
+ includeCrypto: false,
22
+ includeFormulation: false,
23
+ 'install-deps': true,
24
+ installDeps: true,
25
+ 'min-confidence': 0,
26
+ minConfidence: 0,
27
+ multiProject: true,
28
+ 'no-banner': false,
29
+ noBabel: false,
30
+ noBanner: false,
31
+ o: 'bom.json',
32
+ output: 'bom.json',
33
+ outputFormat: 'json', // or "xml"
34
+ // author: ['OWASP Foundation'],
35
+ profile: 'generic',
36
+ project: undefined,
37
+ 'project-version': '',
38
+ projectVersion: '',
39
+ 'proto-bin-file': 'bom.cdx',
40
+ protoBinFile: 'bom.cdx',
41
+ r: false,
42
+ 'reachables-slices-file': 'reachables.slices.json',
43
+ reachablesSlicesFile: 'reachables.slices.json',
44
+ recurse: false,
45
+ requiredOnly: false,
46
+ 'semantics-slices-file': 'semantics.slices.json',
47
+ semanticsSlicesFile: 'semantics.slices.json',
48
+ 'skip-dt-tls-check': true,
49
+ skipDtTlsCheck: true,
50
+ 'spec-version': 1.6,
51
+ specVersion: 1.6,
52
+ 'usages-slices-file': 'usages.slices.json',
53
+ usagesSlicesFile: 'usages.slices.json',
54
+ validate: true,
55
+ };
2
56
  /**
3
57
  * Lazy loads cdxgen (for ESM purposes), scans
4
58
  * `directory`, and returns the `bomJson` property.
5
59
  */
6
60
  export async function createBomFromDir(directory, opts = {}) {
7
- const options = {
8
- $0: 'cdxgen',
9
- _: [],
10
- 'auto-compositions': true,
11
- autoCompositions: true,
12
- 'data-flow-slices-file': 'data-flow.slices.json',
13
- dataFlowSlicesFile: 'data-flow.slices.json',
14
- deep: false, // TODO: you def want to check this out
15
- 'deps-slices-file': 'deps.slices.json',
16
- depsSlicesFile: 'deps.slices.json',
17
- evidence: false,
18
- 'export-proto': false,
19
- exportProto: false,
20
- // DON'T FAIL ON ERROR; you won't get hlepful logs
21
- 'fail-on-error': false,
22
- failOnError: false,
23
- false: true,
24
- 'include-crypto': false,
25
- 'include-formulation': false,
26
- includeCrypto: false,
27
- includeFormulation: false,
28
- // 'server-host': '127.0.0.1',
29
- // serverHost: '127.0.0.1',
30
- // 'server-port': '9090',
31
- // serverPort: '9090',
32
- 'install-deps': true,
33
- installDeps: true,
34
- 'min-confidence': 0,
35
- minConfidence: 0,
36
- multiProject: true,
37
- 'no-banner': false,
38
- noBabel: false,
39
- noBanner: false,
40
- o: 'bom.json',
41
- output: 'bom.json',
42
- outputFormat: 'json', // or "xml"
43
- // author: ['OWASP Foundation'],
44
- profile: 'generic',
45
- project: undefined,
46
- 'project-version': '',
47
- projectVersion: '',
48
- 'proto-bin-file': 'bom.cdx',
49
- protoBinFile: 'bom.cdx',
50
- r: false,
51
- 'reachables-slices-file': 'reachables.slices.json',
52
- reachablesSlicesFile: 'reachables.slices.json',
53
- recurse: false,
54
- requiredOnly: false,
55
- 'semantics-slices-file': 'semantics.slices.json',
56
- semanticsSlicesFile: 'semantics.slices.json',
57
- 'skip-dt-tls-check': true,
58
- skipDtTlsCheck: true,
59
- 'spec-version': 1.6,
60
- specVersion: 1.6,
61
- 'usages-slices-file': 'usages.slices.json',
62
- usagesSlicesFile: 'usages.slices.json',
63
- validate: true,
64
- ...opts,
65
- };
66
61
  const { createBom } = await getCdxGen();
67
- const sbom = await createBom?.(directory, options);
68
- log.info('Successfully generated SBOM');
62
+ const sbom = await createBom?.(directory, { ...SBOM_DEFAULT__OPTIONS, ...opts });
63
+ debugLogger('Successfully generated SBOM');
69
64
  return sbom?.bomJson;
70
65
  }
71
66
  // use a value holder, for easier mocking
@@ -1,5 +1,5 @@
1
1
  import { NesApolloClient } from "../../api/nes/nes.client.js";
2
- import { log } from "../../service/log.svc.js";
2
+ import { debugLogger } from "../../service/log.svc.js";
3
3
  import { getDaysEolFromEolAt, getStatusFromComponent } from "../line.svc.js";
4
4
  import { extractPurls } from "../purls.svc.js";
5
5
  import { createBomFromDir } from "./cdx.svc.js";
@@ -7,7 +7,7 @@ export async function createSbom(directory, opts = {}) {
7
7
  const sbom = await createBomFromDir(directory, opts.cdxgen || {});
8
8
  if (!sbom)
9
9
  throw new Error('SBOM not generated');
10
- log.info('SBOM generated');
10
+ debugLogger('SBOM generated');
11
11
  return sbom;
12
12
  }
13
13
  export function validateIsCycloneDxSbom(sbom) {
@@ -60,7 +60,7 @@ export async function prepareRows(purls, scan) {
60
60
  if (!details) {
61
61
  // In this case, the purl string is in the generated sbom, but the NES/XEOL api has no data
62
62
  // TODO: add UNKNOWN Component Status, create new line, and create flag to show/hide unknown results
63
- log.debug(`Unknown status: ${purl}.`);
63
+ debugLogger(`Unknown status: ${purl}.`);
64
64
  continue;
65
65
  }
66
66
  const { info } = details;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { createBom } from '@cyclonedx/cdxgen';
4
+ import { SBOM_DEFAULT__OPTIONS } from "./cdx.svc.js";
5
+ process.on('uncaughtException', (err) => {
6
+ console.error('Uncaught exception:', err.message);
7
+ process.exit(1);
8
+ });
9
+ process.on('unhandledRejection', (reason) => {
10
+ console.error('Unhandled rejection:', reason);
11
+ process.exit(1);
12
+ });
13
+ try {
14
+ console.log('Sbom worker started');
15
+ const options = JSON.parse(process.argv[2]);
16
+ const { path, opts } = options;
17
+ const { bomJson } = await createBom(path, { ...SBOM_DEFAULT__OPTIONS, ...opts });
18
+ const outputPath = join(path, 'nes.sbom.json');
19
+ writeFileSync(outputPath, JSON.stringify(bomJson, null, 2));
20
+ process.exit(0);
21
+ }
22
+ catch (error) {
23
+ console.error('Error creating SBOM', error.message);
24
+ process.exit(1);
25
+ }
@@ -1,10 +1,7 @@
1
+ import debug from 'debug';
1
2
  /**
2
- * A simple logging construct when you
3
- * don't have the command instance handy
3
+ * A simple debug logger for services.
4
+ * Services should only use debug logging for development/troubleshooting.
5
+ * All user-facing output should be handled by commands.
4
6
  */
5
- export declare const log: {
6
- info: (_message?: unknown, ...args: unknown[]) => void;
7
- warn: (_message?: unknown, ...args: unknown[]) => void;
8
- debug: (_message?: unknown, ...args: unknown[]) => void;
9
- };
10
- export declare const initOclifLog: (info: (message?: unknown, ...args: unknown[]) => void, warn: (message?: unknown, ...args: unknown[]) => void, debug: (message?: unknown, ...args: unknown[]) => void) => void;
7
+ export declare const debugLogger: debug.Debugger;
@@ -1,20 +1,7 @@
1
+ import debug from 'debug';
1
2
  /**
2
- * A simple logging construct when you
3
- * don't have the command instance handy
3
+ * A simple debug logger for services.
4
+ * Services should only use debug logging for development/troubleshooting.
5
+ * All user-facing output should be handled by commands.
4
6
  */
5
- export const log = {
6
- info: (_message, ...args) => {
7
- console.log('[default_log]', ...args);
8
- },
9
- warn: (_message, ...args) => {
10
- console.warn('[default_warn]', ...args);
11
- },
12
- debug: (_message, ...args) => {
13
- console.debug('[default_debug]', ...args);
14
- },
15
- };
16
- export const initOclifLog = (info, warn, debug) => {
17
- log.info = info;
18
- log.warn = warn;
19
- log.debug = debug;
20
- };
7
+ export const debugLogger = debug('oclif:herodevs-debug');
@@ -1,5 +1,5 @@
1
1
  import { M_SCAN } from "../../api/queries/nes/sbom.js";
2
- import { log } from "../log.svc.js";
2
+ import { debugLogger } from "../log.svc.js";
3
3
  export const buildScanResult = (scan) => {
4
4
  const components = new Map();
5
5
  for (const c of scan.components) {
@@ -16,8 +16,8 @@ export const SbomScanner = (client) => async (purls) => {
16
16
  const res = await client.mutate(M_SCAN.gql, { input });
17
17
  const scan = res.data?.insights?.scan?.eol;
18
18
  if (!scan?.success) {
19
- log.info('failed scan %o', scan || {});
20
- log.warn('scan failed');
19
+ debugLogger('failed scan %o', scan || {});
20
+ debugLogger('scan failed');
21
21
  throw new Error('Failed to provide scan: ');
22
22
  }
23
23
  const result = buildScanResult(scan);
@@ -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
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herodevs/cli",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.1.0-beta.1",
4
4
  "author": "HeroDevs, Inc",
5
5
  "bin": {
6
6
  "hd": "./bin/run.js"
@@ -19,6 +19,7 @@
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",
@@ -36,9 +37,12 @@
36
37
  ],
37
38
  "dependencies": {
38
39
  "@apollo/client": "^3.13.1",
40
+ "@cyclonedx/cdxgen": "^11.2.2",
41
+ "@oclif/core": "^4",
42
+ "@oclif/plugin-help": "^6",
43
+ "@oclif/plugin-plugins": "^5",
39
44
  "graphql": "^16.8.1",
40
- "inquirer": "^12.5.0",
41
- "@oclif/core": "^4"
45
+ "inquirer": "^12.5.0"
42
46
  },
43
47
  "devDependencies": {
44
48
  "@biomejs/biome": "^1.8.3",
@@ -49,6 +53,7 @@
49
53
  "oclif": "^4",
50
54
  "shx": "^0.3.3",
51
55
  "sinon": "^19.0.2",
56
+ "ts-node": "^10",
52
57
  "typescript": "^5.8.0"
53
58
  },
54
59
  "engines": {
@@ -67,15 +72,12 @@
67
72
  "commands": "./dist/commands",
68
73
  "plugins": [
69
74
  "@oclif/plugin-help",
70
- "@oclif/plugin-plugins",
71
- "@oclif/plugin-update"
75
+ "@oclif/plugin-plugins"
72
76
  ],
73
- "topicSeparator": " ",
74
- "update": {
75
- "node": {
76
- "version": ">=22.0.0"
77
- }
78
- }
77
+ "hooks": {
78
+ "prerun": "./dist/hooks/prerun.js"
79
+ },
80
+ "topicSeparator": " "
79
81
  },
80
82
  "types": "dist/index.d.ts"
81
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;