@herodevs/cli 2.0.0-beta.13 → 2.0.0-beta.15
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 +192 -20
- package/dist/api/apollo.client.d.ts +3 -0
- package/dist/api/apollo.client.js +53 -0
- package/dist/api/ci-token.client.d.ts +26 -0
- package/dist/api/ci-token.client.js +95 -0
- package/dist/api/errors.d.ts +8 -0
- package/dist/api/errors.js +13 -0
- package/dist/api/gql-operations.d.ts +3 -0
- package/dist/api/gql-operations.js +36 -1
- package/dist/api/graphql-errors.d.ts +6 -0
- package/dist/api/graphql-errors.js +22 -0
- package/dist/api/nes.client.d.ts +1 -2
- package/dist/api/nes.client.js +31 -20
- package/dist/api/user-setup.client.d.ts +15 -0
- package/dist/api/user-setup.client.js +92 -0
- package/dist/commands/auth/login.d.ts +14 -0
- package/dist/commands/auth/login.js +225 -0
- package/dist/commands/auth/logout.d.ts +5 -0
- package/dist/commands/auth/logout.js +27 -0
- package/dist/commands/auth/provision-ci-token.d.ts +5 -0
- package/dist/commands/auth/provision-ci-token.js +62 -0
- package/dist/commands/report/committers.d.ts +11 -7
- package/dist/commands/report/committers.js +144 -76
- package/dist/commands/scan/eol.d.ts +2 -0
- package/dist/commands/scan/eol.js +34 -4
- package/dist/commands/tracker/init.d.ts +14 -0
- package/dist/commands/tracker/init.js +84 -0
- package/dist/commands/tracker/run.d.ts +15 -0
- package/dist/commands/tracker/run.js +183 -0
- package/dist/config/constants.d.ts +14 -0
- package/dist/config/constants.js +15 -0
- package/dist/config/tracker.config.d.ts +16 -0
- package/dist/config/tracker.config.js +16 -0
- package/dist/hooks/finally/finally.js +10 -4
- package/dist/hooks/init/01_initialize_amplitude.js +20 -9
- package/dist/service/analytics.svc.d.ts +10 -3
- package/dist/service/analytics.svc.js +180 -18
- package/dist/service/auth-config.svc.d.ts +5 -0
- package/dist/service/auth-config.svc.js +20 -0
- package/dist/service/auth-refresh.svc.d.ts +8 -0
- package/dist/service/auth-refresh.svc.js +45 -0
- package/dist/service/auth-token.svc.d.ts +11 -0
- package/dist/service/auth-token.svc.js +48 -0
- package/dist/service/auth.svc.d.ts +27 -0
- package/dist/service/auth.svc.js +88 -0
- package/dist/service/ci-auth.svc.d.ts +6 -0
- package/dist/service/ci-auth.svc.js +32 -0
- package/dist/service/ci-token.svc.d.ts +6 -0
- package/dist/service/ci-token.svc.js +75 -0
- package/dist/service/committers.svc.d.ts +46 -58
- package/dist/service/committers.svc.js +55 -173
- package/dist/service/jwt.svc.d.ts +1 -0
- package/dist/service/jwt.svc.js +19 -0
- package/dist/service/tracker.svc.d.ts +58 -0
- package/dist/service/tracker.svc.js +101 -0
- package/dist/types/auth.d.ts +9 -0
- package/dist/types/auth.js +1 -0
- package/dist/utils/open-in-browser.d.ts +1 -0
- package/dist/utils/open-in-browser.js +21 -0
- package/dist/utils/retry.d.ts +11 -0
- package/dist/utils/retry.js +29 -0
- package/dist/utils/strip-typename.js +2 -1
- package/package.json +38 -19
package/README.md
CHANGED
|
@@ -43,11 +43,11 @@ npm install -g @herodevs/cli@beta
|
|
|
43
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
44
|
|
|
45
45
|
```sh
|
|
46
|
-
curl -o- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.
|
|
46
|
+
curl -o- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.15/scripts/install.sh | bash
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
```sh
|
|
50
|
-
wget -qO- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.
|
|
50
|
+
wget -qO- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.15/scripts/install.sh | bash
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
## Scanning Behavior
|
|
@@ -72,7 +72,7 @@ $ npm install -g @herodevs/cli@beta
|
|
|
72
72
|
$ hd COMMAND
|
|
73
73
|
running command...
|
|
74
74
|
$ hd (--version)
|
|
75
|
-
@herodevs/cli/2.0.0-beta.
|
|
75
|
+
@herodevs/cli/2.0.0-beta.15 darwin-arm64 node-v24.10.0
|
|
76
76
|
$ hd --help [COMMAND]
|
|
77
77
|
USAGE
|
|
78
78
|
$ hd COMMAND
|
|
@@ -81,11 +81,58 @@ USAGE
|
|
|
81
81
|
<!-- usagestop -->
|
|
82
82
|
## Commands
|
|
83
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)
|
|
84
87
|
* [`hd help [COMMAND]`](#hd-help-command)
|
|
85
88
|
* [`hd report committers`](#hd-report-committers)
|
|
86
89
|
* [`hd scan eol`](#hd-scan-eol)
|
|
90
|
+
* [`hd tracker init`](#hd-tracker-init)
|
|
91
|
+
* [`hd tracker run`](#hd-tracker-run)
|
|
87
92
|
* [`hd update [CHANNEL]`](#hd-update-channel)
|
|
88
|
-
|
|
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.15/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.15/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.15/src/commands/auth/provision-ci-token.ts)_
|
|
89
136
|
|
|
90
137
|
## `hd help [COMMAND]`
|
|
91
138
|
|
|
@@ -105,7 +152,7 @@ DESCRIPTION
|
|
|
105
152
|
Display help for hd.
|
|
106
153
|
```
|
|
107
154
|
|
|
108
|
-
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/
|
|
155
|
+
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/6.2.37/src/commands/help.ts)_
|
|
109
156
|
|
|
110
157
|
## `hd report committers`
|
|
111
158
|
|
|
@@ -113,15 +160,20 @@ Generate report of committers to a git repository
|
|
|
113
160
|
|
|
114
161
|
```
|
|
115
162
|
USAGE
|
|
116
|
-
$ hd report committers [--json] [-
|
|
163
|
+
$ hd report committers [--json] [-x <value>...] [-d <value>] [--monthly] [-m <value> | -s <value> | -e <value> | | ]
|
|
164
|
+
[-c] [-s]
|
|
117
165
|
|
|
118
166
|
FLAGS
|
|
119
|
-
-c, --csv
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
--
|
|
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.
|
|
125
177
|
|
|
126
178
|
DESCRIPTION
|
|
127
179
|
Generate report of committers to a git repository
|
|
@@ -136,7 +188,7 @@ EXAMPLES
|
|
|
136
188
|
$ hd report committers --csv
|
|
137
189
|
```
|
|
138
190
|
|
|
139
|
-
_See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.
|
|
191
|
+
_See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.15/src/commands/report/committers.ts)_
|
|
140
192
|
|
|
141
193
|
## `hd scan eol`
|
|
142
194
|
|
|
@@ -145,7 +197,7 @@ Scan a given SBOM for EOL data
|
|
|
145
197
|
```
|
|
146
198
|
USAGE
|
|
147
199
|
$ hd scan eol [--json] [-f <value> | -d <value>] [-s] [-o <value>] [--saveSbom] [--sbomOutput <value>]
|
|
148
|
-
[--saveTrimmedSbom] [--hideReportUrl] [--version]
|
|
200
|
+
[--saveTrimmedSbom] [--hideReportUrl] [--automated] [--version]
|
|
149
201
|
|
|
150
202
|
FLAGS
|
|
151
203
|
-d, --dir=<value> [default: <current directory>] The directory to scan in order to create a cyclonedx SBOM
|
|
@@ -153,6 +205,7 @@ FLAGS
|
|
|
153
205
|
-o, --output=<value> Save the generated report to a custom path (defaults to herodevs.report.json when not
|
|
154
206
|
provided)
|
|
155
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)
|
|
156
209
|
--hideReportUrl Hide the generated web report URL for this scan
|
|
157
210
|
--saveSbom Save the generated SBOM as herodevs.sbom.json in the scanned directory
|
|
158
211
|
--saveTrimmedSbom Save the trimmed SBOM as herodevs.sbom-trimmed.json in the scanned directory
|
|
@@ -182,16 +235,71 @@ EXAMPLES
|
|
|
182
235
|
|
|
183
236
|
$ hd scan eol --save --saveSbom
|
|
184
237
|
|
|
185
|
-
Save the report and SBOM to custom paths
|
|
186
|
-
|
|
187
|
-
$ hd scan eol --dir . --save --saveSbom --output ./reports/my-report.json --sbomOutput ./reports/my-sbom.json
|
|
188
|
-
|
|
189
238
|
Output the report in JSON format (for APIs, CI, etc.)
|
|
190
239
|
|
|
191
240
|
$ hd scan eol --json
|
|
192
241
|
```
|
|
193
242
|
|
|
194
|
-
_See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.
|
|
243
|
+
_See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.15/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.15/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.15/src/commands/tracker/run.ts)_
|
|
195
303
|
|
|
196
304
|
## `hd update [CHANNEL]`
|
|
197
305
|
|
|
@@ -231,13 +339,77 @@ EXAMPLES
|
|
|
231
339
|
$ hd update --available
|
|
232
340
|
```
|
|
233
341
|
|
|
234
|
-
_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/
|
|
342
|
+
_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/4.7.18/src/commands/update.ts)_
|
|
235
343
|
<!-- commandsstop -->
|
|
236
344
|
|
|
237
345
|
## CI/CD Usage
|
|
238
346
|
|
|
239
347
|
You can use `@herodevs/cli` in your CI/CD pipelines to automate EOL scanning.
|
|
240
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 CI secret: `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:
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
export HD_CI_CREDENTIAL="<token>"
|
|
366
|
+
hd scan eol --dir .
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
| Secret / Env Var | Purpose |
|
|
370
|
+
|------------------|---------|
|
|
371
|
+
| `HD_CI_CREDENTIAL` | Refresh token from provision; exchanged for access token |
|
|
372
|
+
|
|
373
|
+
#### Local testing
|
|
374
|
+
|
|
375
|
+
Reproduce the CI flow locally:
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
export HD_CI_CREDENTIAL="<token-from-provision>"
|
|
379
|
+
hd scan eol --dir /path/to/project
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### GitHub Actions (authenticated scan)
|
|
383
|
+
|
|
384
|
+
Add secret `HD_CI_CREDENTIAL` in your repository or organization, then:
|
|
385
|
+
|
|
386
|
+
```yaml
|
|
387
|
+
- uses: actions/checkout@v5
|
|
388
|
+
- uses: actions/setup-node@v6
|
|
389
|
+
with:
|
|
390
|
+
node-version: '24'
|
|
391
|
+
- name: Run EOL Scan
|
|
392
|
+
env:
|
|
393
|
+
HD_CI_CREDENTIAL: ${{ secrets.HD_CI_CREDENTIAL }}
|
|
394
|
+
run: npx @herodevs/cli@beta scan eol -s
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### GitLab CI (authenticated scan)
|
|
398
|
+
|
|
399
|
+
Add CI/CD variable `HD_CI_CREDENTIAL` (masked) in your project:
|
|
400
|
+
|
|
401
|
+
```yaml
|
|
402
|
+
eol-scan:
|
|
403
|
+
image: node:24
|
|
404
|
+
variables:
|
|
405
|
+
HD_CI_CREDENTIAL: $HD_CI_CREDENTIAL
|
|
406
|
+
script:
|
|
407
|
+
- npx @herodevs/cli@beta scan eol -s
|
|
408
|
+
artifacts:
|
|
409
|
+
paths:
|
|
410
|
+
- herodevs.report.json
|
|
411
|
+
```
|
|
412
|
+
|
|
241
413
|
### Using the Docker Image (Recommended)
|
|
242
414
|
|
|
243
415
|
We provide a Docker image that's pre-configured to run EOL scans. Based on [`cdxgen`](https://github.com/CycloneDX/cdxgen),
|
|
@@ -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,26 @@
|
|
|
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
|
+
}
|
|
11
|
+
export interface ProvisionCITokenOptions {
|
|
12
|
+
orgId?: number;
|
|
13
|
+
previousToken?: string | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function getOrgAccessTokensUnauthenticated(input: IamAccessOrgTokensInput): Promise<{
|
|
16
|
+
accessToken: string;
|
|
17
|
+
refreshToken: string;
|
|
18
|
+
}>;
|
|
19
|
+
export interface ExchangeCITokenOptions {
|
|
20
|
+
refreshToken: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function exchangeCITokenForAccess(options: ExchangeCITokenOptions): Promise<{
|
|
23
|
+
accessToken: string;
|
|
24
|
+
refreshToken: string;
|
|
25
|
+
}>;
|
|
26
|
+
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 { 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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { gql } from '@apollo/client/core
|
|
1
|
+
import { gql } from '@apollo/client/core';
|
|
2
2
|
export const createReportMutation = gql `
|
|
3
3
|
mutation createReport($input: CreateEolReportInput) {
|
|
4
4
|
eol {
|
|
@@ -20,6 +20,7 @@ query GetEolReport($input: GetEolReportInput) {
|
|
|
20
20
|
components {
|
|
21
21
|
purl
|
|
22
22
|
metadata
|
|
23
|
+
dependencySummary
|
|
23
24
|
nesRemediation {
|
|
24
25
|
remediations {
|
|
25
26
|
urls {
|
|
@@ -34,3 +35,37 @@ query GetEolReport($input: GetEolReportInput) {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
`;
|
|
38
|
+
export const userSetupStatusQuery = gql `
|
|
39
|
+
query Eol {
|
|
40
|
+
eol {
|
|
41
|
+
userSetupStatus {
|
|
42
|
+
isComplete
|
|
43
|
+
orgId
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
export const completeUserSetupMutation = gql `
|
|
49
|
+
mutation Eol {
|
|
50
|
+
eol {
|
|
51
|
+
completeUserSetup {
|
|
52
|
+
isComplete
|
|
53
|
+
orgId
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
export const getOrgAccessTokensMutation = gql `
|
|
59
|
+
mutation GetOrgAccessTokens(
|
|
60
|
+
$input: IamAccessOrgTokensInput!
|
|
61
|
+
) {
|
|
62
|
+
iamV2 {
|
|
63
|
+
access {
|
|
64
|
+
getOrgAccessTokens(input: $input) {
|
|
65
|
+
accessToken
|
|
66
|
+
refreshToken
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
`;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { GraphQLFormattedError } from 'graphql';
|
|
2
|
+
export type GraphQLErrorResult = {
|
|
3
|
+
error?: unknown;
|
|
4
|
+
errors?: ReadonlyArray<GraphQLFormattedError>;
|
|
5
|
+
};
|
|
6
|
+
export declare function getGraphQLErrors(result: GraphQLErrorResult): ReadonlyArray<GraphQLFormattedError> | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function getGraphQLErrors(result) {
|
|
2
|
+
if (result.errors?.length) {
|
|
3
|
+
return result.errors;
|
|
4
|
+
}
|
|
5
|
+
const error = result.error;
|
|
6
|
+
if (!error || typeof error !== 'object') {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if ('errors' in error) {
|
|
10
|
+
const errors = error.errors;
|
|
11
|
+
if (errors?.length) {
|
|
12
|
+
return errors;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if ('graphQLErrors' in error) {
|
|
16
|
+
const errors = error.graphQLErrors;
|
|
17
|
+
if (errors?.length) {
|
|
18
|
+
return errors;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return;
|
|
22
|
+
}
|
package/dist/api/nes.client.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ApolloClient } from '@apollo/client/core/index.js';
|
|
2
1
|
import type { CreateEolReportInput, EolReport } from '@herodevs/eol-shared';
|
|
3
|
-
|
|
2
|
+
import { createApollo } from './apollo.client.ts';
|
|
4
3
|
export declare const SbomScanner: (client: ReturnType<typeof createApollo>) => (input: CreateEolReportInput) => Promise<EolReport>;
|
|
5
4
|
export declare class NesClient {
|
|
6
5
|
startScan: ReturnType<typeof SbomScanner>;
|
package/dist/api/nes.client.js
CHANGED
|
@@ -1,29 +1,32 @@
|
|
|
1
|
-
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client/core/index.js';
|
|
2
1
|
import { config } from "../config/constants.js";
|
|
3
2
|
import { debugLogger } from "../service/log.svc.js";
|
|
4
3
|
import { stripTypename } from "../utils/strip-typename.js";
|
|
4
|
+
import { createApollo } from "./apollo.client.js";
|
|
5
|
+
import { ApiError, isApiErrorCode } from "./errors.js";
|
|
5
6
|
import { createReportMutation, getEolReportQuery } from "./gql-operations.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
uri,
|
|
14
|
-
headers: {
|
|
15
|
-
'User-Agent': `hdcli/${process.env.npm_package_version ?? 'unknown'}`,
|
|
16
|
-
},
|
|
17
|
-
}),
|
|
18
|
-
});
|
|
7
|
+
import { getGraphQLErrors } from "./graphql-errors.js";
|
|
8
|
+
function extractErrorCode(errors) {
|
|
9
|
+
const code = errors[0]?.extensions?.code;
|
|
10
|
+
if (!code || !isApiErrorCode(code))
|
|
11
|
+
return;
|
|
12
|
+
return code;
|
|
13
|
+
}
|
|
19
14
|
export const SbomScanner = (client) => {
|
|
20
15
|
return async (input) => {
|
|
21
|
-
|
|
16
|
+
let res;
|
|
17
|
+
res = await client.mutate({
|
|
22
18
|
mutation: createReportMutation,
|
|
23
19
|
variables: { input },
|
|
24
20
|
});
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
const errors = getGraphQLErrors(res);
|
|
22
|
+
if (res?.error || errors?.length) {
|
|
23
|
+
debugLogger('Error returned from createReport mutation: %o', res.error || errors);
|
|
24
|
+
if (errors?.length) {
|
|
25
|
+
const code = extractErrorCode(errors);
|
|
26
|
+
if (code) {
|
|
27
|
+
throw new ApiError(errors[0].message, code);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
27
30
|
throw new Error('Failed to create EOL report');
|
|
28
31
|
}
|
|
29
32
|
const result = res.data?.eol?.createReport;
|
|
@@ -47,10 +50,18 @@ export const SbomScanner = (client) => {
|
|
|
47
50
|
let reportMetadata = null;
|
|
48
51
|
for (let i = 0; i < pages.length; i += config.concurrentPageRequests) {
|
|
49
52
|
const batch = pages.slice(i, i + config.concurrentPageRequests);
|
|
50
|
-
|
|
53
|
+
let batchResponses;
|
|
54
|
+
batchResponses = await Promise.all(batch);
|
|
51
55
|
for (const response of batchResponses) {
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
const queryErrors = getGraphQLErrors(response);
|
|
57
|
+
if (response?.error || queryErrors?.length || !response.data?.eol) {
|
|
58
|
+
debugLogger('Error in getReport query response: %o', response?.error ?? queryErrors ?? response);
|
|
59
|
+
if (queryErrors?.length) {
|
|
60
|
+
const code = extractErrorCode(queryErrors);
|
|
61
|
+
if (code) {
|
|
62
|
+
throw new ApiError(queryErrors[0].message, code);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
54
65
|
throw new Error('Failed to fetch EOL report');
|
|
55
66
|
}
|
|
56
67
|
const report = response.data.eol.report;
|