@herodevs/cli 2.0.0-beta.9 → 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.
- package/README.md +253 -38
- package/bin/main.js +2 -6
- 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 +27 -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 +40 -16
- package/dist/api/user-setup.client.d.ts +18 -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 +72 -0
- package/dist/commands/report/committers.d.ts +27 -0
- package/dist/commands/report/committers.js +215 -0
- package/dist/commands/scan/eol.d.ts +7 -0
- package/dist/commands/scan/eol.js +113 -25
- 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 +13 -7
- package/dist/hooks/init/01_initialize_amplitude.js +20 -9
- package/dist/service/analytics.svc.d.ts +10 -4
- package/dist/service/analytics.svc.js +180 -18
- package/dist/service/auth-config.svc.d.ts +2 -0
- package/dist/service/auth-config.svc.js +8 -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 +62 -0
- package/dist/service/auth.svc.d.ts +27 -0
- package/dist/service/auth.svc.js +91 -0
- package/dist/service/cdx.svc.d.ts +9 -1
- package/dist/service/cdx.svc.js +17 -12
- 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 +44 -0
- package/dist/service/committers.svc.d.ts +58 -0
- package/dist/service/committers.svc.js +78 -0
- package/dist/service/display.svc.d.ts +8 -0
- package/dist/service/display.svc.js +17 -2
- package/dist/service/encrypted-store.svc.d.ts +5 -0
- package/dist/service/encrypted-store.svc.js +43 -0
- package/dist/service/error.svc.d.ts +8 -0
- package/dist/service/error.svc.js +28 -0
- package/dist/service/file.svc.d.ts +17 -7
- package/dist/service/file.svc.js +80 -36
- 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/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.d.ts +1 -0
- package/dist/utils/strip-typename.js +16 -0
- package/package.json +38 -21
- package/dist/service/sbom.worker.js +0 -26
- /package/dist/{service/sbom.worker.d.ts → types/auth.js} +0 -0
package/README.md
CHANGED
|
@@ -10,6 +10,11 @@ The HeroDevs CLI
|
|
|
10
10
|
* [@herodevs/cli](#herodevscli)
|
|
11
11
|
<!-- tocstop -->
|
|
12
12
|
|
|
13
|
+
### Terms and Data Security
|
|
14
|
+
|
|
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)
|
|
17
|
+
|
|
13
18
|
### Prerequisites
|
|
14
19
|
|
|
15
20
|
- Install node v20 or higher: [Download Node](https://nodejs.org/en/download)
|
|
@@ -24,13 +29,13 @@ The HeroDevs CLI
|
|
|
24
29
|
With Node installed, you can run the CLI directly from the npm registry without installing it globally or locally on your system
|
|
25
30
|
|
|
26
31
|
```sh
|
|
27
|
-
npx @herodevs/cli
|
|
32
|
+
npx @herodevs/cli
|
|
28
33
|
```
|
|
29
34
|
|
|
30
35
|
#### Global NPM Installation
|
|
31
36
|
|
|
32
37
|
```sh
|
|
33
|
-
npm install -g @herodevs/cli
|
|
38
|
+
npm install -g @herodevs/cli
|
|
34
39
|
```
|
|
35
40
|
|
|
36
41
|
#### Binary Installation
|
|
@@ -38,17 +43,13 @@ npm install -g @herodevs/cli@beta
|
|
|
38
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:
|
|
39
44
|
|
|
40
45
|
```sh
|
|
41
|
-
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.18/scripts/install.sh | bash
|
|
42
47
|
```
|
|
43
48
|
|
|
44
49
|
```sh
|
|
45
|
-
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.18/scripts/install.sh | bash
|
|
46
51
|
```
|
|
47
52
|
|
|
48
|
-
## TERMS
|
|
49
|
-
|
|
50
|
-
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).
|
|
51
|
-
|
|
52
53
|
## Scanning Behavior
|
|
53
54
|
|
|
54
55
|
The CLI is designed to be non-invasive:
|
|
@@ -67,11 +68,11 @@ Maven and Gradle projects should run an install and build before scanning
|
|
|
67
68
|
## Usage
|
|
68
69
|
<!-- usage -->
|
|
69
70
|
```sh-session
|
|
70
|
-
$ npm install -g @herodevs/cli
|
|
71
|
+
$ npm install -g @herodevs/cli
|
|
71
72
|
$ hd COMMAND
|
|
72
73
|
running command...
|
|
73
74
|
$ hd (--version)
|
|
74
|
-
@herodevs/cli/2.0.0
|
|
75
|
+
@herodevs/cli/2.0.0 darwin-arm64 node-v24.10.0
|
|
75
76
|
$ hd --help [COMMAND]
|
|
76
77
|
USAGE
|
|
77
78
|
$ hd COMMAND
|
|
@@ -80,10 +81,58 @@ USAGE
|
|
|
80
81
|
<!-- usagestop -->
|
|
81
82
|
## Commands
|
|
82
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)
|
|
83
87
|
* [`hd help [COMMAND]`](#hd-help-command)
|
|
88
|
+
* [`hd report committers`](#hd-report-committers)
|
|
84
89
|
* [`hd scan eol`](#hd-scan-eol)
|
|
90
|
+
* [`hd tracker init`](#hd-tracker-init)
|
|
91
|
+
* [`hd tracker run`](#hd-tracker-run)
|
|
85
92
|
* [`hd update [CHANNEL]`](#hd-update-channel)
|
|
86
|
-
|
|
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)_
|
|
87
136
|
|
|
88
137
|
## `hd help [COMMAND]`
|
|
89
138
|
|
|
@@ -94,7 +143,7 @@ USAGE
|
|
|
94
143
|
$ hd help [COMMAND...] [-n]
|
|
95
144
|
|
|
96
145
|
ARGUMENTS
|
|
97
|
-
COMMAND... Command to show help for.
|
|
146
|
+
[COMMAND...] Command to show help for.
|
|
98
147
|
|
|
99
148
|
FLAGS
|
|
100
149
|
-n, --nested-commands Include all nested commands in the output.
|
|
@@ -103,7 +152,43 @@ DESCRIPTION
|
|
|
103
152
|
Display help for hd.
|
|
104
153
|
```
|
|
105
154
|
|
|
106
|
-
_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)_
|
|
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)_
|
|
107
192
|
|
|
108
193
|
## `hd scan eol`
|
|
109
194
|
|
|
@@ -111,14 +196,21 @@ Scan a given SBOM for EOL data
|
|
|
111
196
|
|
|
112
197
|
```
|
|
113
198
|
USAGE
|
|
114
|
-
$ hd scan eol [--json] [-f <value> | -d <value>] [-s] [--saveSbom] [--
|
|
199
|
+
$ hd scan eol [--json] [-f <value> | -d <value>] [-s] [-o <value>] [--saveSbom] [--sbomOutput <value>]
|
|
200
|
+
[--saveTrimmedSbom] [--hideReportUrl] [--automated] [--version]
|
|
115
201
|
|
|
116
202
|
FLAGS
|
|
117
|
-
-d, --dir=<value>
|
|
118
|
-
-f, --file=<value>
|
|
119
|
-
-
|
|
120
|
-
|
|
121
|
-
|
|
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.
|
|
122
214
|
|
|
123
215
|
GLOBAL FLAGS
|
|
124
216
|
--json Format output as json.
|
|
@@ -148,7 +240,66 @@ EXAMPLES
|
|
|
148
240
|
$ hd scan eol --json
|
|
149
241
|
```
|
|
150
242
|
|
|
151
|
-
_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.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)_
|
|
152
303
|
|
|
153
304
|
## `hd update [CHANNEL]`
|
|
154
305
|
|
|
@@ -188,18 +339,75 @@ EXAMPLES
|
|
|
188
339
|
$ hd update --available
|
|
189
340
|
```
|
|
190
341
|
|
|
191
|
-
_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)_
|
|
192
343
|
<!-- commandsstop -->
|
|
193
344
|
|
|
194
345
|
## CI/CD Usage
|
|
195
346
|
|
|
196
347
|
You can use `@herodevs/cli` in your CI/CD pipelines to automate EOL scanning.
|
|
197
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
|
+
|
|
198
404
|
### Using the Docker Image (Recommended)
|
|
199
405
|
|
|
200
406
|
We provide a Docker image that's pre-configured to run EOL scans. Based on [`cdxgen`](https://github.com/CycloneDX/cdxgen),
|
|
201
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.
|
|
202
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
|
+
|
|
203
411
|
#### GitHub Actions
|
|
204
412
|
|
|
205
413
|
```yaml
|
|
@@ -215,19 +423,25 @@ on:
|
|
|
215
423
|
jobs:
|
|
216
424
|
scan:
|
|
217
425
|
runs-on: ubuntu-latest
|
|
426
|
+
environment: demo
|
|
218
427
|
steps:
|
|
219
|
-
-
|
|
428
|
+
- name: Checkout repository
|
|
429
|
+
uses: actions/checkout@v5
|
|
220
430
|
|
|
221
|
-
- name: Run EOL Scan
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
228
442
|
with:
|
|
229
443
|
name: my-eol-report
|
|
230
|
-
path: herodevs.report.json
|
|
444
|
+
path: ${{ runner.temp }}/herodevs.report.json
|
|
231
445
|
```
|
|
232
446
|
|
|
233
447
|
#### GitLab CI/CD
|
|
@@ -239,7 +453,7 @@ eol-scan:
|
|
|
239
453
|
# Entrypoint or base command must be disabled due
|
|
240
454
|
# to GitLab's execution mechanism and run manually
|
|
241
455
|
entrypoint: [""]
|
|
242
|
-
script: "npx @herodevs/cli
|
|
456
|
+
script: "npx @herodevs/cli scan eol -s"
|
|
243
457
|
artifacts:
|
|
244
458
|
paths:
|
|
245
459
|
- herodevs.report.json
|
|
@@ -270,18 +484,19 @@ jobs:
|
|
|
270
484
|
scan:
|
|
271
485
|
runs-on: ubuntu-latest
|
|
272
486
|
steps:
|
|
273
|
-
- uses: actions/checkout@
|
|
274
|
-
|
|
487
|
+
- uses: actions/checkout@v5
|
|
488
|
+
|
|
489
|
+
- uses: actions/setup-node@v6
|
|
275
490
|
with:
|
|
276
|
-
node-version: '
|
|
491
|
+
node-version: '24'
|
|
277
492
|
|
|
278
493
|
- run: echo # Prepare environment, install tooling, perform setup, etc.
|
|
279
494
|
|
|
280
495
|
- name: Run EOL Scan
|
|
281
|
-
run: npx @herodevs/cli
|
|
496
|
+
run: npx @herodevs/cli scan eol
|
|
282
497
|
|
|
283
|
-
- name: Upload
|
|
284
|
-
uses: actions/upload-artifact@
|
|
498
|
+
- name: Upload artifact
|
|
499
|
+
uses: actions/upload-artifact@v5
|
|
285
500
|
with:
|
|
286
501
|
name: my-eol-report
|
|
287
502
|
path: herodevs.report.json
|
|
@@ -295,7 +510,7 @@ image: alpine
|
|
|
295
510
|
eol-scan:
|
|
296
511
|
script:
|
|
297
512
|
- echo # Prepare environment, install tooling, perform setup, etc.
|
|
298
|
-
- npx @herodevs/cli
|
|
513
|
+
- npx @herodevs/cli scan eol -s
|
|
299
514
|
artifacts:
|
|
300
515
|
paths:
|
|
301
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
|
|
10
|
+
// If no arguments at all, default to help
|
|
11
11
|
if (positionals.length === 0) {
|
|
12
|
-
process.argv.splice(2, 0, '
|
|
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,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;
|