@herodevs/cli 2.0.0-beta.8 → 2.0.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.
Files changed (76) hide show
  1. package/README.md +282 -39
  2. package/bin/main.js +2 -6
  3. package/dist/api/apollo.client.d.ts +3 -0
  4. package/dist/api/apollo.client.js +53 -0
  5. package/dist/api/ci-token.client.d.ts +27 -0
  6. package/dist/api/ci-token.client.js +95 -0
  7. package/dist/api/errors.d.ts +8 -0
  8. package/dist/api/errors.js +13 -0
  9. package/dist/api/gql-operations.d.ts +3 -0
  10. package/dist/api/gql-operations.js +36 -1
  11. package/dist/api/graphql-errors.d.ts +6 -0
  12. package/dist/api/graphql-errors.js +22 -0
  13. package/dist/api/nes.client.d.ts +1 -2
  14. package/dist/api/nes.client.js +40 -16
  15. package/dist/api/user-setup.client.d.ts +18 -0
  16. package/dist/api/user-setup.client.js +92 -0
  17. package/dist/commands/auth/login.d.ts +14 -0
  18. package/dist/commands/auth/login.js +225 -0
  19. package/dist/commands/auth/logout.d.ts +5 -0
  20. package/dist/commands/auth/logout.js +27 -0
  21. package/dist/commands/auth/provision-ci-token.d.ts +5 -0
  22. package/dist/commands/auth/provision-ci-token.js +72 -0
  23. package/dist/commands/report/committers.d.ts +27 -0
  24. package/dist/commands/report/committers.js +215 -0
  25. package/dist/commands/scan/eol.d.ts +7 -0
  26. package/dist/commands/scan/eol.js +120 -32
  27. package/dist/commands/tracker/init.d.ts +14 -0
  28. package/dist/commands/tracker/init.js +84 -0
  29. package/dist/commands/tracker/run.d.ts +15 -0
  30. package/dist/commands/tracker/run.js +183 -0
  31. package/dist/config/constants.d.ts +14 -0
  32. package/dist/config/constants.js +15 -0
  33. package/dist/config/tracker.config.d.ts +16 -0
  34. package/dist/config/tracker.config.js +16 -0
  35. package/dist/hooks/finally/finally.js +13 -7
  36. package/dist/hooks/init/01_initialize_amplitude.js +20 -9
  37. package/dist/service/analytics.svc.d.ts +10 -4
  38. package/dist/service/analytics.svc.js +180 -18
  39. package/dist/service/auth-config.svc.d.ts +2 -0
  40. package/dist/service/auth-config.svc.js +8 -0
  41. package/dist/service/auth-refresh.svc.d.ts +8 -0
  42. package/dist/service/auth-refresh.svc.js +45 -0
  43. package/dist/service/auth-token.svc.d.ts +11 -0
  44. package/dist/service/auth-token.svc.js +62 -0
  45. package/dist/service/auth.svc.d.ts +27 -0
  46. package/dist/service/auth.svc.js +91 -0
  47. package/dist/service/cdx.svc.d.ts +9 -1
  48. package/dist/service/cdx.svc.js +17 -12
  49. package/dist/service/ci-auth.svc.d.ts +6 -0
  50. package/dist/service/ci-auth.svc.js +32 -0
  51. package/dist/service/ci-token.svc.d.ts +6 -0
  52. package/dist/service/ci-token.svc.js +44 -0
  53. package/dist/service/committers.svc.d.ts +58 -0
  54. package/dist/service/committers.svc.js +78 -0
  55. package/dist/service/display.svc.d.ts +8 -0
  56. package/dist/service/display.svc.js +17 -2
  57. package/dist/service/encrypted-store.svc.d.ts +5 -0
  58. package/dist/service/encrypted-store.svc.js +43 -0
  59. package/dist/service/error.svc.d.ts +8 -0
  60. package/dist/service/error.svc.js +28 -0
  61. package/dist/service/file.svc.d.ts +17 -7
  62. package/dist/service/file.svc.js +80 -36
  63. package/dist/service/jwt.svc.d.ts +1 -0
  64. package/dist/service/jwt.svc.js +19 -0
  65. package/dist/service/tracker.svc.d.ts +58 -0
  66. package/dist/service/tracker.svc.js +101 -0
  67. package/dist/types/auth.d.ts +9 -0
  68. package/dist/utils/open-in-browser.d.ts +1 -0
  69. package/dist/utils/open-in-browser.js +21 -0
  70. package/dist/utils/retry.d.ts +11 -0
  71. package/dist/utils/retry.js +29 -0
  72. package/dist/utils/strip-typename.d.ts +1 -0
  73. package/dist/utils/strip-typename.js +16 -0
  74. package/package.json +40 -22
  75. package/dist/service/sbom.worker.js +0 -26
  76. /package/dist/{service/sbom.worker.d.ts → types/auth.js} +0 -0
package/README.md CHANGED
@@ -10,17 +10,45 @@ The HeroDevs CLI
10
10
  * [@herodevs/cli](#herodevscli)
11
11
  <!-- tocstop -->
12
12
 
13
- ## Installation Instructions
13
+ ### Terms and Data Security
14
14
 
15
- 1. Install node v20 or higher: [Download Node](https://nodejs.org/en/download)
16
- 1. Install the CLI using one of the following methods:
17
- * Globally: Refer to the [Usage](#usage) instructions on installing the CLI globally
18
- * npx: `npx @herodevs/cli@beta`
19
- 1. Refer to the [Commands](#commands) section for a list of commands
15
+ - [HeroDevs End of Life Dataset Terms of Service and Data Policy](https://docs.herodevs.com/legal/end-of-life-dataset-terms)
16
+ - [HeroDevs End of Life Dataset Data Privacy and Security](https://docs.herodevs.com/eol-ds/data-privacy-and-security)
20
17
 
21
- ## TERMS
18
+ ### Prerequisites
22
19
 
23
- Use of this CLI is governed by the [HeroDevs End of Life Dataset Terms of Service and Data Policy](https://docs.herodevs.com/legal/end-of-life-dataset-terms).
20
+ - Install node v20 or higher: [Download Node](https://nodejs.org/en/download)
21
+ - The HeroDevs CLI expects that you have all required technology installed for the project that you are running the CLI against
22
+ - For example, if you are running the CLI against a Gradle project, the CLI expects you to have Java installed.
23
+
24
+
25
+ ### Installation methods
26
+
27
+ #### Node Package Execute (NPX)
28
+
29
+ With Node installed, you can run the CLI directly from the npm registry without installing it globally or locally on your system
30
+
31
+ ```sh
32
+ npx @herodevs/cli
33
+ ```
34
+
35
+ #### Global NPM Installation
36
+
37
+ ```sh
38
+ npm install -g @herodevs/cli
39
+ ```
40
+
41
+ #### Binary Installation
42
+
43
+ HeroDevs CLI is available as a binary installation, without requiring `npm`. To do that, you may either download and run the script manually, or use the following cURL or Wget command:
44
+
45
+ ```sh
46
+ curl -o- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.18/scripts/install.sh | bash
47
+ ```
48
+
49
+ ```sh
50
+ wget -qO- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.18/scripts/install.sh | bash
51
+ ```
24
52
 
25
53
  ## Scanning Behavior
26
54
 
@@ -40,11 +68,11 @@ Maven and Gradle projects should run an install and build before scanning
40
68
  ## Usage
41
69
  <!-- usage -->
42
70
  ```sh-session
43
- $ npm install -g @herodevs/cli@beta
71
+ $ npm install -g @herodevs/cli
44
72
  $ hd COMMAND
45
73
  running command...
46
74
  $ hd (--version)
47
- @herodevs/cli/2.0.0-beta.8 darwin-arm64 node-v22.18.0
75
+ @herodevs/cli/2.0.0 darwin-arm64 node-v24.10.0
48
76
  $ hd --help [COMMAND]
49
77
  USAGE
50
78
  $ hd COMMAND
@@ -53,10 +81,58 @@ USAGE
53
81
  <!-- usagestop -->
54
82
  ## Commands
55
83
  <!-- commands -->
84
+ * [`hd auth login`](#hd-auth-login)
85
+ * [`hd auth logout`](#hd-auth-logout)
86
+ * [`hd auth provision-ci-token`](#hd-auth-provision-ci-token)
56
87
  * [`hd help [COMMAND]`](#hd-help-command)
88
+ * [`hd report committers`](#hd-report-committers)
57
89
  * [`hd scan eol`](#hd-scan-eol)
90
+ * [`hd tracker init`](#hd-tracker-init)
91
+ * [`hd tracker run`](#hd-tracker-run)
58
92
  * [`hd update [CHANNEL]`](#hd-update-channel)
59
- * Only applies to tarball installation. For NPM users, please update using `npm install`
93
+ * **NOTE:** Only applies to [binary installation method](#binary-installation). NPM users should use [`npm install`](#global-npm-installation) to update to the latest version.
94
+
95
+ ## `hd auth login`
96
+
97
+ OAuth CLI login
98
+
99
+ ```
100
+ USAGE
101
+ $ hd auth login
102
+
103
+ DESCRIPTION
104
+ OAuth CLI login
105
+ ```
106
+
107
+ _See code: [src/commands/auth/login.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.16/src/commands/auth/login.ts)_
108
+
109
+ ## `hd auth logout`
110
+
111
+ Logs out of HeroDevs OAuth and clears stored tokens
112
+
113
+ ```
114
+ USAGE
115
+ $ hd auth logout
116
+
117
+ DESCRIPTION
118
+ Logs out of HeroDevs OAuth and clears stored tokens
119
+ ```
120
+
121
+ _See code: [src/commands/auth/logout.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.16/src/commands/auth/logout.ts)_
122
+
123
+ ## `hd auth provision-ci-token`
124
+
125
+ Provision a CI/CD long-lived refresh token for headless auth
126
+
127
+ ```
128
+ USAGE
129
+ $ hd auth provision-ci-token
130
+
131
+ DESCRIPTION
132
+ Provision a CI/CD long-lived refresh token for headless auth
133
+ ```
134
+
135
+ _See code: [src/commands/auth/provision-ci-token.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.16/src/commands/auth/provision-ci-token.ts)_
60
136
 
61
137
  ## `hd help [COMMAND]`
62
138
 
@@ -67,7 +143,7 @@ USAGE
67
143
  $ hd help [COMMAND...] [-n]
68
144
 
69
145
  ARGUMENTS
70
- COMMAND... Command to show help for.
146
+ [COMMAND...] Command to show help for.
71
147
 
72
148
  FLAGS
73
149
  -n, --nested-commands Include all nested commands in the output.
@@ -76,7 +152,43 @@ DESCRIPTION
76
152
  Display help for hd.
77
153
  ```
78
154
 
79
- _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.32/src/commands/help.ts)_
155
+ _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/6.2.37/src/commands/help.ts)_
156
+
157
+ ## `hd report committers`
158
+
159
+ Generate report of committers to a git repository
160
+
161
+ ```
162
+ USAGE
163
+ $ hd report committers [--json] [-x <value>...] [-d <value>] [--monthly] [-m <value> | -s <value> | -e <value> | | ]
164
+ [-c] [-s]
165
+
166
+ FLAGS
167
+ -c, --csv Output in CSV format
168
+ -d, --directory=<value> Directory to search
169
+ -e, --afterDate=<value> [default: 2025-02-26] Start date (format: yyyy-MM-dd)
170
+ -m, --months=<value> [default: 12] The number of months of git history to review. Cannot be used along beforeDate
171
+ and afterDate
172
+ -s, --beforeDate=<value> [default: 2026-02-26] End date (format: yyyy-MM-dd)
173
+ -s, --save Save the committers report as herodevs.committers.<output>
174
+ -x, --exclude=<value>... Path Exclusions (eg -x="./src/bin" -x="./dist")
175
+ --json Output to JSON format
176
+ --monthly Break down by calendar month.
177
+
178
+ DESCRIPTION
179
+ Generate report of committers to a git repository
180
+
181
+ EXAMPLES
182
+ $ hd report committers
183
+
184
+ $ hd report committers --csv -s
185
+
186
+ $ hd report committers --json
187
+
188
+ $ hd report committers --csv
189
+ ```
190
+
191
+ _See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.16/src/commands/report/committers.ts)_
80
192
 
81
193
  ## `hd scan eol`
82
194
 
@@ -84,14 +196,21 @@ Scan a given SBOM for EOL data
84
196
 
85
197
  ```
86
198
  USAGE
87
- $ hd scan eol [--json] [-f <value> | -d <value>] [-s] [--saveSbom] [--version]
199
+ $ hd scan eol [--json] [-f <value> | -d <value>] [-s] [-o <value>] [--saveSbom] [--sbomOutput <value>]
200
+ [--saveTrimmedSbom] [--hideReportUrl] [--automated] [--version]
88
201
 
89
202
  FLAGS
90
- -d, --dir=<value> [default: <current directory>] The directory to scan in order to create a cyclonedx SBOM
91
- -f, --file=<value> The file path of an existing cyclonedx SBOM to scan for EOL
92
- -s, --save Save the generated report as herodevs.report.json in the scanned directory
93
- --saveSbom Save the generated SBOM as herodevs.sbom.json in the scanned directory
94
- --version Show CLI version.
203
+ -d, --dir=<value> [default: <current directory>] The directory to scan in order to create a cyclonedx SBOM
204
+ -f, --file=<value> The file path of an existing SBOM to scan for EOL (supports CycloneDX and SPDX 2.3 formats)
205
+ -o, --output=<value> Save the generated report to a custom path (defaults to herodevs.report.json when not
206
+ provided)
207
+ -s, --save Save the generated report as herodevs.report.json in the scanned directory
208
+ --automated Mark scan as automated (for CI/CD pipelines)
209
+ --hideReportUrl Hide the generated web report URL for this scan
210
+ --saveSbom Save the generated SBOM as herodevs.sbom.json in the scanned directory
211
+ --saveTrimmedSbom Save the trimmed SBOM as herodevs.sbom-trimmed.json in the scanned directory
212
+ --sbomOutput=<value> Save the generated SBOM to a custom path (defaults to herodevs.sbom.json when not provided)
213
+ --version Show CLI version.
95
214
 
96
215
  GLOBAL FLAGS
97
216
  --json Format output as json.
@@ -121,12 +240,72 @@ EXAMPLES
121
240
  $ hd scan eol --json
122
241
  ```
123
242
 
124
- _See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.8/src/commands/scan/eol.ts)_
243
+ _See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.16/src/commands/scan/eol.ts)_
244
+
245
+ ## `hd tracker init`
246
+
247
+ Initialize the tracker configuration
248
+
249
+ ```
250
+ USAGE
251
+ $ hd tracker init [--force -o] [-d <value>] [-f <value>] [-i <value>...]
252
+
253
+ FLAGS
254
+ -d, --outputDir=<value> [default: hd-tracker] Output directory for the tracker configuration file
255
+ -f, --configFile=<value> [default: config.json] Filename for the tracker configuration file
256
+ -i, --ignorePatterns=<value>... [default: node_modules] Ignore patterns to use for the tracker configuration file
257
+ -o, --overwrite Overwrites the tracker configuration file if it exists
258
+ --force Force tracker configuration file creation. Use with --overwrite flag
259
+
260
+ DESCRIPTION
261
+ Initialize the tracker configuration
262
+
263
+ EXAMPLES
264
+ $ hd tracker init
265
+
266
+ $ hd tracker init -d trackerDir
267
+
268
+ $ hd tracker init -d trackerDir -f configFileName
269
+
270
+ $ hd tracker init -i node_modules
271
+
272
+ $ hd tracker init -i node_modules -i custom_modules
273
+
274
+ $ hd tracker init -o
275
+ ```
276
+
277
+ _See code: [src/commands/tracker/init.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.16/src/commands/tracker/init.ts)_
278
+
279
+ ## `hd tracker run`
280
+
281
+ Run the tracker
282
+
283
+ ```
284
+ USAGE
285
+ $ hd tracker run [-d <value>] [-f <value>]
286
+
287
+ FLAGS
288
+ -d, --configDir=<value> [default: hd-tracker] Directory where the tracker configuration file resides
289
+ -f, --configFile=<value> [default: config.json] Filename for the tracker configuration file
290
+
291
+ DESCRIPTION
292
+ Run the tracker
293
+
294
+ EXAMPLES
295
+ $ hd tracker run
296
+
297
+ $ hd tracker run -d tracker-configuration
298
+
299
+ $ hd tracker run -d tracker -f settings.json
300
+ ```
301
+
302
+ _See code: [src/commands/tracker/run.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.16/src/commands/tracker/run.ts)_
125
303
 
126
304
  ## `hd update [CHANNEL]`
127
305
 
128
306
  update the hd CLI
129
- **NOTE:** Only applies to binary installation method. NPM users should use `npm install` to update to the latest version.
307
+
308
+ * **NOTE:** Only applies to [binary installation method](#binary-installation). NPM users should use [`npm install`](#global-npm-installation) to update to the latest version.
130
309
 
131
310
  ```
132
311
  USAGE
@@ -160,18 +339,75 @@ EXAMPLES
160
339
  $ hd update --available
161
340
  ```
162
341
 
163
- _See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.7.4/src/commands/update.ts)_
342
+ _See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/4.7.18/src/commands/update.ts)_
164
343
  <!-- commandsstop -->
165
344
 
166
345
  ## CI/CD Usage
167
346
 
168
347
  You can use `@herodevs/cli` in your CI/CD pipelines to automate EOL scanning.
169
348
 
349
+ ### CI/CD authentication
350
+
351
+ For headless use in CI/CD (e.g. GitHub Actions, GitLab CI), the CLI supports long-lived organization-scoped refresh tokens. You do not need to run an interactive login in the pipeline.
352
+
353
+ **One-time setup (interactive):**
354
+
355
+ ```bash
356
+ hd auth login
357
+ hd auth provision-ci-token
358
+ ```
359
+
360
+ Copy the token output, add as an environment variable: `HD_CI_CREDENTIAL`
361
+
362
+ **CI pipeline (headless):** Run `hd scan eol` directly with `HD_CI_CREDENTIAL` set. The CLI exchanges the token for an access token automatically. An explicit `auth login` command is not required when using the CI token.
363
+
364
+ ```bash
365
+ export HD_CI_CREDENTIAL="<token>"
366
+ hd scan eol
367
+ ```
368
+
369
+ | Secret / Env Var | Purpose |
370
+ |------------------|---------|
371
+ | `HD_CI_CREDENTIAL` | Refresh token from provision; exchanged for access token |
372
+
373
+ #### GitHub Actions (authenticated scan)
374
+
375
+ Add secret `HD_CI_CREDENTIAL` in your repository or organization, then:
376
+
377
+ ```yaml
378
+ - uses: actions/checkout@v5
379
+ - uses: actions/setup-node@v6
380
+ with:
381
+ node-version: '24'
382
+ - name: Run EOL Scan
383
+ env:
384
+ HD_CI_CREDENTIAL: ${{ secrets.HD_CI_CREDENTIAL }}
385
+ run: npx @herodevs/cli scan eol -s
386
+ ```
387
+
388
+ #### GitLab CI (authenticated scan)
389
+
390
+ Add CI/CD variable `HD_CI_CREDENTIAL` (masked) in your project:
391
+
392
+ ```yaml
393
+ eol-scan:
394
+ image: node:24
395
+ variables:
396
+ HD_CI_CREDENTIAL: $HD_CI_CREDENTIAL
397
+ script:
398
+ - npx @herodevs/cli scan eol -s
399
+ artifacts:
400
+ paths:
401
+ - herodevs.report.json
402
+ ```
403
+
170
404
  ### Using the Docker Image (Recommended)
171
405
 
172
406
  We provide a Docker image that's pre-configured to run EOL scans. Based on [`cdxgen`](https://github.com/CycloneDX/cdxgen),
173
407
  it contains build tools for most project types and will provide best results when generating an SBOM. Use these templates to generate a report and save it to your CI job artifact for analysis and processing after your scan runs.
174
408
 
409
+ **Note:** There is a potential to run into permission issues writing out the report to your CI runner. Please ensure that your CI runner is setup to have proper read/write permissions for wherever your output files are being written to.
410
+
175
411
  #### GitHub Actions
176
412
 
177
413
  ```yaml
@@ -187,19 +423,25 @@ on:
187
423
  jobs:
188
424
  scan:
189
425
  runs-on: ubuntu-latest
426
+ environment: demo
190
427
  steps:
191
- - uses: actions/checkout@v4
428
+ - name: Checkout repository
429
+ uses: actions/checkout@v5
192
430
 
193
- - name: Run EOL Scan with Docker
194
- uses: docker://ghcr.io/herodevs/eol-scan
195
- with:
196
- args: "-s"
197
-
198
- - name: Upload artifact
199
- uses: actions/upload-artifact@v4
431
+ - name: Run EOL Scan
432
+ run: |
433
+ docker run --name eol-scanner \
434
+ -v $GITHUB_WORKSPACE:/app \
435
+ -w /app \
436
+ ghcr.io/herodevs/eol-scan --save --output /tmp/herodevs.report.json
437
+ docker cp eol-scanner:/tmp/herodevs.report.json ${{ runner.temp }}/herodevs.report.json
438
+ docker rm eol-scanner
439
+
440
+ - name: Upload artifact
441
+ uses: actions/upload-artifact@v5
200
442
  with:
201
443
  name: my-eol-report
202
- path: herodevs.report.json
444
+ path: ${{ runner.temp }}/herodevs.report.json
203
445
  ```
204
446
 
205
447
  #### GitLab CI/CD
@@ -211,7 +453,7 @@ eol-scan:
211
453
  # Entrypoint or base command must be disabled due
212
454
  # to GitLab's execution mechanism and run manually
213
455
  entrypoint: [""]
214
- script: "npx @herodevs/cli@beta scan eol -s"
456
+ script: "npx @herodevs/cli scan eol -s"
215
457
  artifacts:
216
458
  paths:
217
459
  - herodevs.report.json
@@ -242,18 +484,19 @@ jobs:
242
484
  scan:
243
485
  runs-on: ubuntu-latest
244
486
  steps:
245
- - uses: actions/checkout@v4
246
- - uses: actions/setup-node@v4
487
+ - uses: actions/checkout@v5
488
+
489
+ - uses: actions/setup-node@v6
247
490
  with:
248
- node-version: '20'
491
+ node-version: '24'
249
492
 
250
493
  - run: echo # Prepare environment, install tooling, perform setup, etc.
251
494
 
252
495
  - name: Run EOL Scan
253
- run: npx @herodevs/cli@beta
496
+ run: npx @herodevs/cli scan eol
254
497
 
255
- - name: Upload artifact
256
- uses: actions/upload-artifact@v4
498
+ - name: Upload artifact
499
+ uses: actions/upload-artifact@v5
257
500
  with:
258
501
  name: my-eol-report
259
502
  path: herodevs.report.json
@@ -267,7 +510,7 @@ image: alpine
267
510
  eol-scan:
268
511
  script:
269
512
  - echo # Prepare environment, install tooling, perform setup, etc.
270
- - npx @herodevs/cli@beta scan eol -s
513
+ - npx @herodevs/cli scan eol -s
271
514
  artifacts:
272
515
  paths:
273
516
  - herodevs.report.json
package/bin/main.js CHANGED
@@ -7,13 +7,9 @@ async function main(isProduction = false) {
7
7
  strict: false, // Don't validate flags
8
8
  });
9
9
 
10
- // If no arguments at all, default to scan:eol
10
+ // If no arguments at all, default to help
11
11
  if (positionals.length === 0) {
12
- process.argv.splice(2, 0, 'scan:eol');
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');
12
+ process.argv.splice(2, 0, 'help');
17
13
  }
18
14
 
19
15
  try {
@@ -0,0 +1,3 @@
1
+ import { ApolloClient } from '@apollo/client/core';
2
+ export type TokenProvider = (forceRefresh?: boolean) => Promise<string>;
3
+ export declare const createApollo: (uri: string, tokenProvider?: TokenProvider) => ApolloClient;
@@ -0,0 +1,53 @@
1
+ import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client/core';
2
+ import { requireAccessTokenForScan } from "../service/auth.svc.js";
3
+ function isTokenEndpoint(input) {
4
+ let urlString;
5
+ if (typeof input === 'string') {
6
+ urlString = input;
7
+ }
8
+ else if (input instanceof Request) {
9
+ urlString = input.url;
10
+ }
11
+ else {
12
+ urlString = input.toString();
13
+ }
14
+ try {
15
+ const url = new URL(urlString);
16
+ return url.pathname.endsWith('/token');
17
+ }
18
+ catch {
19
+ const pathOnly = urlString.split('?')[0].split('#')[0];
20
+ return pathOnly.endsWith('/token');
21
+ }
22
+ }
23
+ const createAuthorizedFetch = (tokenProvider) => async (input, init) => {
24
+ const headers = new Headers(init?.headers);
25
+ const token = await tokenProvider();
26
+ if (token) {
27
+ headers.set('Authorization', `Bearer ${token}`);
28
+ }
29
+ const response = await fetch(input, { ...init, headers });
30
+ if (response.status === 401 &&
31
+ !isTokenEndpoint(input) &&
32
+ (init?.method === 'GET' || init?.method === undefined || init?.method === 'POST')) {
33
+ const refreshed = await tokenProvider(true);
34
+ const retryHeaders = new Headers(init?.headers);
35
+ retryHeaders.set('Authorization', `Bearer ${refreshed}`);
36
+ return fetch(input, { ...init, headers: retryHeaders });
37
+ }
38
+ return response;
39
+ };
40
+ export const createApollo = (uri, tokenProvider = requireAccessTokenForScan) => new ApolloClient({
41
+ cache: new InMemoryCache(),
42
+ defaultOptions: {
43
+ query: { fetchPolicy: 'no-cache', errorPolicy: 'all' },
44
+ mutate: { errorPolicy: 'all' },
45
+ },
46
+ link: new HttpLink({
47
+ uri,
48
+ fetch: createAuthorizedFetch(tokenProvider),
49
+ headers: {
50
+ 'User-Agent': `hdcli/${process.env.npm_package_version ?? 'unknown'}`,
51
+ },
52
+ }),
53
+ });
@@ -0,0 +1,27 @@
1
+ export type IamAccessOrgTokensInput = {
2
+ orgId: number;
3
+ previousToken?: never;
4
+ } | {
5
+ orgId?: never;
6
+ previousToken: string;
7
+ };
8
+ export interface ProvisionCITokenResponse {
9
+ refresh_token: string;
10
+ access_token: string;
11
+ }
12
+ export interface ProvisionCITokenOptions {
13
+ orgId?: number;
14
+ previousToken?: string | null;
15
+ }
16
+ export declare function getOrgAccessTokensUnauthenticated(input: IamAccessOrgTokensInput): Promise<{
17
+ accessToken: string;
18
+ refreshToken: string;
19
+ }>;
20
+ export interface ExchangeCITokenOptions {
21
+ refreshToken: string;
22
+ }
23
+ export declare function exchangeCITokenForAccess(options: ExchangeCITokenOptions): Promise<{
24
+ accessToken: string;
25
+ refreshToken: string;
26
+ }>;
27
+ export declare function provisionCIToken(options?: ProvisionCITokenOptions): Promise<ProvisionCITokenResponse>;
@@ -0,0 +1,95 @@
1
+ import { config } from "../config/constants.js";
2
+ import { requireAccessToken } from "../service/auth.svc.js";
3
+ import { debugLogger } from "../service/log.svc.js";
4
+ import { createApollo } from "./apollo.client.js";
5
+ import { ApiError, isApiErrorCode } from "./errors.js";
6
+ import { getOrgAccessTokensMutation } from "./gql-operations.js";
7
+ import { getGraphQLErrors } from "./graphql-errors.js";
8
+ const graphqlUrl = `${config.graphqlHost}${config.graphqlPath}`;
9
+ const noAuthTokenProvider = async () => '';
10
+ function extractErrorCode(errors) {
11
+ const code = errors[0]?.extensions?.code;
12
+ if (!code || !isApiErrorCode(code))
13
+ return;
14
+ return code;
15
+ }
16
+ async function getOrgAccessTokens(input) {
17
+ const client = createApollo(graphqlUrl, requireAccessToken);
18
+ const res = await client.mutate({
19
+ mutation: getOrgAccessTokensMutation,
20
+ variables: {
21
+ input,
22
+ },
23
+ });
24
+ const errors = getGraphQLErrors(res);
25
+ if (res?.error || errors?.length) {
26
+ debugLogger('Error returned from getOrgAccessTokens mutation: %o', res.error ?? errors);
27
+ if (errors?.length) {
28
+ const code = extractErrorCode(errors);
29
+ if (code) {
30
+ throw new ApiError(errors[0].message ?? 'CI token provisioning failed', code);
31
+ }
32
+ throw new Error(errors[0].message ?? 'CI token provisioning failed');
33
+ }
34
+ const msg = res?.error instanceof Error ? res.error.message : res?.error ? String(res.error) : '';
35
+ throw new Error(msg || 'CI token provisioning failed');
36
+ }
37
+ const tokens = res.data?.iamV2?.access?.getOrgAccessTokens;
38
+ if (!tokens?.refreshToken || tokens.refreshToken.trim() === '') {
39
+ throw new Error('CI token provisioning response missing refreshToken');
40
+ }
41
+ return {
42
+ accessToken: tokens.accessToken ?? '',
43
+ refreshToken: tokens.refreshToken,
44
+ };
45
+ }
46
+ export async function getOrgAccessTokensUnauthenticated(input) {
47
+ return callGetOrgAccessTokensInternal(input, noAuthTokenProvider);
48
+ }
49
+ async function callGetOrgAccessTokensInternal(input, tokenProvider) {
50
+ const client = createApollo(graphqlUrl, tokenProvider);
51
+ const res = await client.mutate({
52
+ mutation: getOrgAccessTokensMutation,
53
+ variables: { input },
54
+ });
55
+ const errors = getGraphQLErrors(res);
56
+ if (res?.error || errors?.length) {
57
+ debugLogger('Error returned from getOrgAccessTokens mutation: %o', res.error ?? errors);
58
+ if (errors?.length) {
59
+ const code = extractErrorCode(errors);
60
+ if (code) {
61
+ throw new ApiError(errors[0].message ?? 'CI token refresh failed', code);
62
+ }
63
+ throw new Error(errors[0].message ?? 'CI token refresh failed');
64
+ }
65
+ const msg = res?.error instanceof Error ? res.error.message : res?.error ? String(res.error) : '';
66
+ throw new Error(msg || 'CI token refresh failed');
67
+ }
68
+ const tokens = res.data?.iamV2?.access?.getOrgAccessTokens;
69
+ if (!tokens?.accessToken) {
70
+ throw new Error('getOrgAccessTokens response missing accessToken');
71
+ }
72
+ return {
73
+ accessToken: tokens.accessToken,
74
+ refreshToken: tokens.refreshToken ?? '',
75
+ };
76
+ }
77
+ export async function exchangeCITokenForAccess(options) {
78
+ const { refreshToken } = options;
79
+ return callGetOrgAccessTokensInternal({ previousToken: refreshToken }, noAuthTokenProvider);
80
+ }
81
+ export async function provisionCIToken(options = {}) {
82
+ const { orgId, previousToken } = options;
83
+ let input;
84
+ if (previousToken != null && previousToken !== '') {
85
+ input = { previousToken };
86
+ }
87
+ else if (orgId != null) {
88
+ input = { orgId };
89
+ }
90
+ else {
91
+ throw new Error('Either orgId or previousToken is required to provision a CI token');
92
+ }
93
+ const result = await getOrgAccessTokens(input);
94
+ return { access_token: result.accessToken, refresh_token: result.refreshToken };
95
+ }
@@ -0,0 +1,8 @@
1
+ declare const API_ERROR_CODES: readonly ["SESSION_EXPIRED", "INVALID_TOKEN", "UNAUTHENTICATED", "FORBIDDEN"];
2
+ export type ApiErrorCode = (typeof API_ERROR_CODES)[number];
3
+ export declare class ApiError extends Error {
4
+ readonly code: ApiErrorCode;
5
+ constructor(message: string, code: ApiErrorCode);
6
+ }
7
+ export declare function isApiErrorCode(code: string): code is ApiErrorCode;
8
+ export {};
@@ -0,0 +1,13 @@
1
+ const API_ERROR_CODES = ['SESSION_EXPIRED', 'INVALID_TOKEN', 'UNAUTHENTICATED', 'FORBIDDEN'];
2
+ const VALID_API_ERROR_CODES = new Set(API_ERROR_CODES);
3
+ export class ApiError extends Error {
4
+ code;
5
+ constructor(message, code) {
6
+ super(message);
7
+ this.name = 'ApiError';
8
+ this.code = code;
9
+ }
10
+ }
11
+ export function isApiErrorCode(code) {
12
+ return VALID_API_ERROR_CODES.has(code);
13
+ }
@@ -1,2 +1,5 @@
1
1
  export declare const createReportMutation: import("graphql/language/ast.js").DocumentNode;
2
2
  export declare const getEolReportQuery: import("graphql/language/ast.js").DocumentNode;
3
+ export declare const userSetupStatusQuery: import("graphql/language/ast.js").DocumentNode;
4
+ export declare const completeUserSetupMutation: import("graphql/language/ast.js").DocumentNode;
5
+ export declare const getOrgAccessTokensMutation: import("graphql/language/ast.js").DocumentNode;