@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.
- package/README.md +282 -39
- 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 +120 -32
- 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 +40 -22
- 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,17 +10,45 @@ The HeroDevs CLI
|
|
|
10
10
|
* [@herodevs/cli](#herodevscli)
|
|
11
11
|
<!-- tocstop -->
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
### Terms and Data Security
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
18
|
+
### Prerequisites
|
|
22
19
|
|
|
23
|
-
|
|
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
|
|
71
|
+
$ npm install -g @herodevs/cli
|
|
44
72
|
$ hd COMMAND
|
|
45
73
|
running command...
|
|
46
74
|
$ hd (--version)
|
|
47
|
-
@herodevs/cli/2.0.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
|
-
|
|
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/
|
|
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] [--
|
|
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>
|
|
91
|
-
-f, --file=<value>
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
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.
|
|
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
|
-
|
|
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/
|
|
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
|
-
-
|
|
428
|
+
- name: Checkout repository
|
|
429
|
+
uses: actions/checkout@v5
|
|
192
430
|
|
|
193
|
-
- name: Run EOL Scan
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
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@
|
|
246
|
-
|
|
487
|
+
- uses: actions/checkout@v5
|
|
488
|
+
|
|
489
|
+
- uses: actions/setup-node@v6
|
|
247
490
|
with:
|
|
248
|
-
node-version: '
|
|
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
|
|
496
|
+
run: npx @herodevs/cli scan eol
|
|
254
497
|
|
|
255
|
-
- name: Upload
|
|
256
|
-
uses: actions/upload-artifact@
|
|
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
|
|
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
|
|
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;
|