@herodevs/cli 2.0.0-beta.12 → 2.0.0-beta.14
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 +91 -17
- package/dist/api/gql-operations.js +2 -1
- package/dist/api/nes.client.d.ts +2 -2
- package/dist/api/nes.client.js +10 -7
- package/dist/commands/report/committers.d.ts +27 -0
- package/dist/commands/report/committers.js +215 -0
- package/dist/commands/tracker/init.d.ts +14 -0
- package/dist/commands/tracker/init.js +84 -0
- package/dist/config/constants.d.ts +4 -0
- package/dist/config/constants.js +5 -0
- package/dist/config/tracker.config.d.ts +16 -0
- package/dist/config/tracker.config.js +18 -0
- package/dist/service/committers.svc.d.ts +58 -0
- package/dist/service/committers.svc.js +78 -0
- package/dist/service/error.svc.d.ts +8 -0
- package/dist/service/error.svc.js +28 -0
- package/dist/service/tracker.svc.d.ts +3 -0
- package/dist/service/tracker.svc.js +26 -0
- package/package.json +14 -9
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.14/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.14/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.14 darwin-arm64 node-v24.10.0
|
|
76
76
|
$ hd --help [COMMAND]
|
|
77
77
|
USAGE
|
|
78
78
|
$ hd COMMAND
|
|
@@ -82,7 +82,9 @@ USAGE
|
|
|
82
82
|
## Commands
|
|
83
83
|
<!-- commands -->
|
|
84
84
|
* [`hd help [COMMAND]`](#hd-help-command)
|
|
85
|
+
* [`hd report committers`](#hd-report-committers)
|
|
85
86
|
* [`hd scan eol`](#hd-scan-eol)
|
|
87
|
+
* [`hd tracker init`](#hd-tracker-init)
|
|
86
88
|
* [`hd update [CHANNEL]`](#hd-update-channel)
|
|
87
89
|
* **NOTE:** Only applies to [binary installation method](#binary-installation). NPM users should use [`npm install`](#global-npm-installation) to update to the latest version.
|
|
88
90
|
|
|
@@ -95,7 +97,7 @@ USAGE
|
|
|
95
97
|
$ hd help [COMMAND...] [-n]
|
|
96
98
|
|
|
97
99
|
ARGUMENTS
|
|
98
|
-
COMMAND... Command to show help for.
|
|
100
|
+
[COMMAND...] Command to show help for.
|
|
99
101
|
|
|
100
102
|
FLAGS
|
|
101
103
|
-n, --nested-commands Include all nested commands in the output.
|
|
@@ -104,7 +106,43 @@ DESCRIPTION
|
|
|
104
106
|
Display help for hd.
|
|
105
107
|
```
|
|
106
108
|
|
|
107
|
-
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.
|
|
109
|
+
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.34/src/commands/help.ts)_
|
|
110
|
+
|
|
111
|
+
## `hd report committers`
|
|
112
|
+
|
|
113
|
+
Generate report of committers to a git repository
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
USAGE
|
|
117
|
+
$ hd report committers [--json] [-x <value>...] [-d <value>] [--monthly] [-m <value> | -s <value> | -e <value> | | ]
|
|
118
|
+
[-c] [-s]
|
|
119
|
+
|
|
120
|
+
FLAGS
|
|
121
|
+
-c, --csv Output in CSV format
|
|
122
|
+
-d, --directory=<value> Directory to search
|
|
123
|
+
-e, --afterDate=<value> [default: 2024-11-19] Start date (format: yyyy-MM-dd)
|
|
124
|
+
-m, --months=<value> [default: 12] The number of months of git history to review. Cannot be used along beforeDate
|
|
125
|
+
and afterDate
|
|
126
|
+
-s, --beforeDate=<value> [default: 2025-11-19] End date (format: yyyy-MM-dd)
|
|
127
|
+
-s, --save Save the committers report as herodevs.committers.<output>
|
|
128
|
+
-x, --exclude=<value>... Path Exclusions (eg -x="./src/bin" -x="./dist")
|
|
129
|
+
--json Output to JSON format
|
|
130
|
+
--monthly Break down by calendar month.
|
|
131
|
+
|
|
132
|
+
DESCRIPTION
|
|
133
|
+
Generate report of committers to a git repository
|
|
134
|
+
|
|
135
|
+
EXAMPLES
|
|
136
|
+
$ hd report committers
|
|
137
|
+
|
|
138
|
+
$ hd report committers --csv -s
|
|
139
|
+
|
|
140
|
+
$ hd report committers --json
|
|
141
|
+
|
|
142
|
+
$ hd report committers --csv
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
_See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.14/src/commands/report/committers.ts)_
|
|
108
146
|
|
|
109
147
|
## `hd scan eol`
|
|
110
148
|
|
|
@@ -112,18 +150,20 @@ Scan a given SBOM for EOL data
|
|
|
112
150
|
|
|
113
151
|
```
|
|
114
152
|
USAGE
|
|
115
|
-
$ hd scan eol [--json] [-f <value> | -d <value>] [-s] [-o <value>] [--saveSbom] [--sbomOutput <value>]
|
|
153
|
+
$ hd scan eol [--json] [-f <value> | -d <value>] [-s] [-o <value>] [--saveSbom] [--sbomOutput <value>]
|
|
154
|
+
[--saveTrimmedSbom] [--hideReportUrl] [--version]
|
|
116
155
|
|
|
117
156
|
FLAGS
|
|
118
|
-
-d, --dir=<value>
|
|
119
|
-
-f, --file=<value>
|
|
120
|
-
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
--
|
|
124
|
-
--
|
|
125
|
-
--saveTrimmedSbom
|
|
126
|
-
--
|
|
157
|
+
-d, --dir=<value> [default: <current directory>] The directory to scan in order to create a cyclonedx SBOM
|
|
158
|
+
-f, --file=<value> The file path of an existing SBOM to scan for EOL (supports CycloneDX and SPDX 2.3 formats)
|
|
159
|
+
-o, --output=<value> Save the generated report to a custom path (defaults to herodevs.report.json when not
|
|
160
|
+
provided)
|
|
161
|
+
-s, --save Save the generated report as herodevs.report.json in the scanned directory
|
|
162
|
+
--hideReportUrl Hide the generated web report URL for this scan
|
|
163
|
+
--saveSbom Save the generated SBOM as herodevs.sbom.json in the scanned directory
|
|
164
|
+
--saveTrimmedSbom Save the trimmed SBOM as herodevs.sbom-trimmed.json in the scanned directory
|
|
165
|
+
--sbomOutput=<value> Save the generated SBOM to a custom path (defaults to herodevs.sbom.json when not provided)
|
|
166
|
+
--version Show CLI version.
|
|
127
167
|
|
|
128
168
|
GLOBAL FLAGS
|
|
129
169
|
--json Format output as json.
|
|
@@ -157,7 +197,41 @@ EXAMPLES
|
|
|
157
197
|
$ hd scan eol --json
|
|
158
198
|
```
|
|
159
199
|
|
|
160
|
-
_See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.
|
|
200
|
+
_See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.14/src/commands/scan/eol.ts)_
|
|
201
|
+
|
|
202
|
+
## `hd tracker init`
|
|
203
|
+
|
|
204
|
+
Initialize the tracker configuration
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
USAGE
|
|
208
|
+
$ hd tracker init [--force -o] [-d <value>] [-f <value>] [-i <value>...]
|
|
209
|
+
|
|
210
|
+
FLAGS
|
|
211
|
+
-d, --outputDir=<value> [default: hd-tracker] Output directory for the tracker configuration file
|
|
212
|
+
-f, --configFile=<value> [default: config.json] Filename for the tracker configuration file
|
|
213
|
+
-i, --ignorePatterns=<value>... [default: node_modules] Ignore patterns to use for the tracker configuration file
|
|
214
|
+
-o, --overwrite Overwrites the tracker configuration file if it exists
|
|
215
|
+
--force Force tracker configuration file creation. Use with --overwrite flag
|
|
216
|
+
|
|
217
|
+
DESCRIPTION
|
|
218
|
+
Initialize the tracker configuration
|
|
219
|
+
|
|
220
|
+
EXAMPLES
|
|
221
|
+
$ hd tracker init
|
|
222
|
+
|
|
223
|
+
$ hd tracker init -d trackerDir
|
|
224
|
+
|
|
225
|
+
$ hd tracker init -d trackerDir -f configFileName
|
|
226
|
+
|
|
227
|
+
$ hd tracker init -i node_modules
|
|
228
|
+
|
|
229
|
+
$ hd tracker init -i node_modules -i custom_modules
|
|
230
|
+
|
|
231
|
+
$ hd tracker init -o
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
_See code: [src/commands/tracker/init.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.14/src/commands/tracker/init.ts)_
|
|
161
235
|
|
|
162
236
|
## `hd update [CHANNEL]`
|
|
163
237
|
|
|
@@ -197,7 +271,7 @@ EXAMPLES
|
|
|
197
271
|
$ hd update --available
|
|
198
272
|
```
|
|
199
273
|
|
|
200
|
-
_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.7.
|
|
274
|
+
_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.7.13/src/commands/update.ts)_
|
|
201
275
|
<!-- commandsstop -->
|
|
202
276
|
|
|
203
277
|
## CI/CD Usage
|
|
@@ -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 {
|
package/dist/api/nes.client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ApolloClient } from '@apollo/client/core
|
|
1
|
+
import { ApolloClient } from '@apollo/client/core';
|
|
2
2
|
import type { CreateEolReportInput, EolReport } from '@herodevs/eol-shared';
|
|
3
|
-
export declare const createApollo: (uri: string) => ApolloClient
|
|
3
|
+
export declare const createApollo: (uri: string) => ApolloClient;
|
|
4
4
|
export declare const SbomScanner: (client: ReturnType<typeof createApollo>) => (input: CreateEolReportInput) => Promise<EolReport>;
|
|
5
5
|
export declare class NesClient {
|
|
6
6
|
startScan: ReturnType<typeof SbomScanner>;
|
package/dist/api/nes.client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client/core
|
|
1
|
+
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client/core';
|
|
2
2
|
import { config } from "../config/constants.js";
|
|
3
3
|
import { debugLogger } from "../service/log.svc.js";
|
|
4
4
|
import { stripTypename } from "../utils/strip-typename.js";
|
|
@@ -18,12 +18,13 @@ export const createApollo = (uri) => new ApolloClient({
|
|
|
18
18
|
});
|
|
19
19
|
export const SbomScanner = (client) => {
|
|
20
20
|
return async (input) => {
|
|
21
|
-
|
|
21
|
+
let res;
|
|
22
|
+
res = await client.mutate({
|
|
22
23
|
mutation: createReportMutation,
|
|
23
24
|
variables: { input },
|
|
24
25
|
});
|
|
25
|
-
if (res?.errors
|
|
26
|
-
debugLogger('
|
|
26
|
+
if (res?.error || res?.errors) {
|
|
27
|
+
debugLogger('Error returned from createReport mutation: %o', res.error || res?.errors);
|
|
27
28
|
throw new Error('Failed to create EOL report');
|
|
28
29
|
}
|
|
29
30
|
const result = res.data?.eol?.createReport;
|
|
@@ -47,10 +48,12 @@ export const SbomScanner = (client) => {
|
|
|
47
48
|
let reportMetadata = null;
|
|
48
49
|
for (let i = 0; i < pages.length; i += config.concurrentPageRequests) {
|
|
49
50
|
const batch = pages.slice(i, i + config.concurrentPageRequests);
|
|
50
|
-
|
|
51
|
+
let batchResponses;
|
|
52
|
+
batchResponses = await Promise.all(batch);
|
|
51
53
|
for (const response of batchResponses) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
const queryErrors = response?.errors;
|
|
55
|
+
if (response?.error || queryErrors?.length || !response.data?.eol) {
|
|
56
|
+
debugLogger('Error in getReport query response: %o', response?.error ?? queryErrors ?? response);
|
|
54
57
|
throw new Error('Failed to fetch EOL report');
|
|
55
58
|
}
|
|
56
59
|
const report = response.data.eol.report;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { type CommittersReport } from '../../service/committers.svc.ts';
|
|
3
|
+
export default class Committers extends Command {
|
|
4
|
+
static description: string;
|
|
5
|
+
static enableJsonFlag: boolean;
|
|
6
|
+
static examples: string[];
|
|
7
|
+
static flags: {
|
|
8
|
+
beforeDate: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
afterDate: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
exclude: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
directory: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
monthly: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
months: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
save: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
};
|
|
18
|
+
run(): Promise<CommittersReport | string>;
|
|
19
|
+
/**
|
|
20
|
+
* Fetches git commit data with month and author information
|
|
21
|
+
* @param sinceDate - Date range for git log
|
|
22
|
+
* @param beforeDateEndOfDay - End date for git log
|
|
23
|
+
* @param ignores - indicate elements to exclude for git log
|
|
24
|
+
* @param cwd - directory to use for git log
|
|
25
|
+
*/
|
|
26
|
+
private fetchGitCommitData;
|
|
27
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { Command, Flags } from '@oclif/core';
|
|
4
|
+
import { makeTable } from '@oclif/table';
|
|
5
|
+
import { endOfDay, formatDate, formatISO, parse, subMonths } from 'date-fns';
|
|
6
|
+
import { DEFAULT_DATE_COMMIT_FORMAT, DEFAULT_DATE_FORMAT, filenamePrefix, GIT_OUTPUT_FORMAT, } from "../../config/constants.js";
|
|
7
|
+
import { generateCommittersReport, generateMonthlyReport, parseGitLogOutput, } from "../../service/committers.svc.js";
|
|
8
|
+
import { getErrorMessage, isErrnoException } from "../../service/error.svc.js";
|
|
9
|
+
export default class Committers extends Command {
|
|
10
|
+
static description = 'Generate report of committers to a git repository';
|
|
11
|
+
static enableJsonFlag = true;
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> <%= command.id %>',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> --csv -s',
|
|
15
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> --csv',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
beforeDate: Flags.string({
|
|
20
|
+
char: 's',
|
|
21
|
+
default: formatDate(new Date(), DEFAULT_DATE_FORMAT),
|
|
22
|
+
description: `End date (format: ${DEFAULT_DATE_FORMAT})`,
|
|
23
|
+
}),
|
|
24
|
+
afterDate: Flags.string({
|
|
25
|
+
char: 'e',
|
|
26
|
+
default: formatDate(subMonths(new Date(), 12), DEFAULT_DATE_FORMAT),
|
|
27
|
+
description: `Start date (format: ${DEFAULT_DATE_FORMAT})`,
|
|
28
|
+
}),
|
|
29
|
+
exclude: Flags.string({
|
|
30
|
+
char: 'x',
|
|
31
|
+
description: 'Path Exclusions (eg -x="./src/bin" -x="./dist")',
|
|
32
|
+
multiple: true,
|
|
33
|
+
multipleNonGreedy: true,
|
|
34
|
+
}),
|
|
35
|
+
json: Flags.boolean({
|
|
36
|
+
description: 'Output to JSON format',
|
|
37
|
+
default: false,
|
|
38
|
+
}),
|
|
39
|
+
directory: Flags.string({
|
|
40
|
+
char: 'd',
|
|
41
|
+
description: 'Directory to search',
|
|
42
|
+
}),
|
|
43
|
+
monthly: Flags.boolean({
|
|
44
|
+
description: 'Break down by calendar month.',
|
|
45
|
+
default: false,
|
|
46
|
+
}),
|
|
47
|
+
months: Flags.integer({
|
|
48
|
+
char: 'm',
|
|
49
|
+
description: 'The number of months of git history to review. Cannot be used along beforeDate and afterDate',
|
|
50
|
+
default: 12,
|
|
51
|
+
exclusive: ['beforeDate', 'afterDate', 's', 'e'],
|
|
52
|
+
}),
|
|
53
|
+
csv: Flags.boolean({
|
|
54
|
+
char: 'c',
|
|
55
|
+
description: 'Output in CSV format',
|
|
56
|
+
default: false,
|
|
57
|
+
}),
|
|
58
|
+
save: Flags.boolean({
|
|
59
|
+
char: 's',
|
|
60
|
+
description: `Save the committers report as ${filenamePrefix}.committers.<output>`,
|
|
61
|
+
default: false,
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
async run() {
|
|
65
|
+
const { flags } = await this.parse(Committers);
|
|
66
|
+
const { afterDate, beforeDate, exclude, directory: cwd, monthly, months, csv, save } = flags;
|
|
67
|
+
const isJson = this.jsonEnabled();
|
|
68
|
+
const reportFormat = isJson ? 'json' : csv ? 'csv' : 'txt';
|
|
69
|
+
const afterDateStartOfDay = months
|
|
70
|
+
? `${subMonths(new Date(), months)}`
|
|
71
|
+
: `${parse(afterDate, DEFAULT_DATE_FORMAT, new Date())}`;
|
|
72
|
+
const beforeDateEndOfDay = formatISO(endOfDay(parse(beforeDate, DEFAULT_DATE_FORMAT, new Date())));
|
|
73
|
+
const ignores = exclude && exclude.length > 0 ? `. "!(${exclude.join('|')})"` : undefined;
|
|
74
|
+
try {
|
|
75
|
+
const entries = this.fetchGitCommitData(afterDateStartOfDay, beforeDateEndOfDay, ignores, cwd);
|
|
76
|
+
if (entries.length === 0) {
|
|
77
|
+
return `No commits found between ${afterDate} and ${beforeDate}`;
|
|
78
|
+
}
|
|
79
|
+
this.log('\nFetched %d commit entries\n', entries.length);
|
|
80
|
+
const reportData = monthly ? generateMonthlyReport(entries) : generateCommittersReport(entries);
|
|
81
|
+
let finalReport;
|
|
82
|
+
switch (reportFormat) {
|
|
83
|
+
case 'json':
|
|
84
|
+
finalReport = JSON.stringify(reportData.map((row) => 'month' in row
|
|
85
|
+
? {
|
|
86
|
+
month: row.month,
|
|
87
|
+
start: row.start,
|
|
88
|
+
end: row.end,
|
|
89
|
+
committers: row.committers,
|
|
90
|
+
}
|
|
91
|
+
: {
|
|
92
|
+
name: row.author,
|
|
93
|
+
count: row.commits.length,
|
|
94
|
+
lastCommitDate: formatDate(row.lastCommitOn, DEFAULT_DATE_COMMIT_FORMAT),
|
|
95
|
+
}), null, 2);
|
|
96
|
+
break;
|
|
97
|
+
case 'csv':
|
|
98
|
+
finalReport = reportData
|
|
99
|
+
.map((row, index) => 'month' in row
|
|
100
|
+
? `${index},${row.month},${row.start},${row.end},${row.totalCommits}`
|
|
101
|
+
: `${index},${row.author},${row.commits.length},${formatDate(row.lastCommitOn, DEFAULT_DATE_COMMIT_FORMAT).replace(',', '')}`)
|
|
102
|
+
.join('\n')
|
|
103
|
+
.replace(/^/, monthly ? `(index),month,start,end,totalCommits\n` : `(index),Committer,Commits,Last Commit Date\n`);
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
if (monthly) {
|
|
107
|
+
finalReport = makeTable({
|
|
108
|
+
title: 'Monthly Report',
|
|
109
|
+
data: reportData
|
|
110
|
+
.filter((row) => 'month' in row)
|
|
111
|
+
.map((row, index) => ({
|
|
112
|
+
index,
|
|
113
|
+
month: row.month,
|
|
114
|
+
start: row.start,
|
|
115
|
+
end: row.end,
|
|
116
|
+
totalCommits: row.totalCommits,
|
|
117
|
+
})),
|
|
118
|
+
headerOptions: {
|
|
119
|
+
color: undefined,
|
|
120
|
+
bold: false,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
finalReport = makeTable({
|
|
126
|
+
title: 'Committers Report',
|
|
127
|
+
data: reportData
|
|
128
|
+
.filter((row) => 'author' in row)
|
|
129
|
+
.map((row, index) => ({
|
|
130
|
+
index,
|
|
131
|
+
author: row.author,
|
|
132
|
+
commits: row.commits.length,
|
|
133
|
+
lastCommitOn: formatDate(row.lastCommitOn, DEFAULT_DATE_COMMIT_FORMAT),
|
|
134
|
+
})),
|
|
135
|
+
columns: [
|
|
136
|
+
{
|
|
137
|
+
key: 'index',
|
|
138
|
+
name: '(index)',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
key: 'author',
|
|
142
|
+
name: 'Committer',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
key: 'commits',
|
|
146
|
+
name: 'Commits',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
key: 'lastCommitOn',
|
|
150
|
+
name: 'Last Commit Date',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
headerOptions: {
|
|
154
|
+
color: undefined,
|
|
155
|
+
bold: false,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
if (save) {
|
|
162
|
+
try {
|
|
163
|
+
fs.writeFileSync(`${filenamePrefix}.${monthly ? 'monthly' : 'committers'}.${reportFormat}`, finalReport, {
|
|
164
|
+
encoding: 'utf-8',
|
|
165
|
+
});
|
|
166
|
+
this.log(`Report written to ${reportFormat.toUpperCase()}`);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
this.error(`Failed to save ${reportFormat.toUpperCase()} report: ${getErrorMessage(err)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
this.log(finalReport);
|
|
173
|
+
return finalReport;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
this.error(`Failed to generate report: ${getErrorMessage(error)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Fetches git commit data with month and author information
|
|
181
|
+
* @param sinceDate - Date range for git log
|
|
182
|
+
* @param beforeDateEndOfDay - End date for git log
|
|
183
|
+
* @param ignores - indicate elements to exclude for git log
|
|
184
|
+
* @param cwd - directory to use for git log
|
|
185
|
+
*/
|
|
186
|
+
fetchGitCommitData(sinceDate, beforeDateEndOfDay, ignores, cwd) {
|
|
187
|
+
const logParameters = [
|
|
188
|
+
'log',
|
|
189
|
+
`--since="${sinceDate}"`,
|
|
190
|
+
`--until="${beforeDateEndOfDay}"`,
|
|
191
|
+
`--format=${GIT_OUTPUT_FORMAT}`,
|
|
192
|
+
...(cwd ? ['--', cwd] : []),
|
|
193
|
+
...(ignores ? ['--', ignores] : []),
|
|
194
|
+
];
|
|
195
|
+
const logProcess = spawnSync('git', logParameters, {
|
|
196
|
+
encoding: 'utf-8',
|
|
197
|
+
});
|
|
198
|
+
if (logProcess.error) {
|
|
199
|
+
if (isErrnoException(logProcess.error)) {
|
|
200
|
+
if (logProcess.error.code === 'ENOENT') {
|
|
201
|
+
this.error('Git command not found. Please ensure git is installed and available in your PATH.');
|
|
202
|
+
}
|
|
203
|
+
this.error(`Git command failed: ${getErrorMessage(logProcess.error)}`);
|
|
204
|
+
}
|
|
205
|
+
this.error(`Git command failed: ${getErrorMessage(logProcess.error)}`);
|
|
206
|
+
}
|
|
207
|
+
if (logProcess.status !== 0) {
|
|
208
|
+
this.error(`Git command failed with status ${logProcess.status}: ${logProcess.stderr}`);
|
|
209
|
+
}
|
|
210
|
+
if (!logProcess.stdout) {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
return parseGitLogOutput(logProcess.stdout);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Init extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static enableJsonFlag: boolean;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
overwrite: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
outputDir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
configFile: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
ignorePatterns: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { confirm } from '@inquirer/prompts';
|
|
2
|
+
import { Command, Flags } from '@oclif/core';
|
|
3
|
+
import { TRACKER_DEFAULT_CONFIG } from '../../config/tracker.config.js';
|
|
4
|
+
import { createTrackerConfig, getRootDir } from '../../service/tracker.svc.js';
|
|
5
|
+
export default class Init extends Command {
|
|
6
|
+
static description = 'Initialize the tracker configuration';
|
|
7
|
+
static enableJsonFlag = false;
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %>',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> -d trackerDir',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> -d trackerDir -f configFileName',
|
|
12
|
+
'<%= config.bin %> <%= command.id %> -i node_modules',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> -i node_modules -i custom_modules',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> -o',
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
overwrite: Flags.boolean({
|
|
18
|
+
char: 'o',
|
|
19
|
+
description: 'Overwrites the tracker configuration file if it exists',
|
|
20
|
+
}),
|
|
21
|
+
force: Flags.boolean({
|
|
22
|
+
description: 'Force tracker configuration file creation. Use with --overwrite flag',
|
|
23
|
+
dependsOn: ['overwrite'],
|
|
24
|
+
}),
|
|
25
|
+
outputDir: Flags.string({
|
|
26
|
+
char: 'd',
|
|
27
|
+
description: 'Output directory for the tracker configuration file',
|
|
28
|
+
default: 'hd-tracker',
|
|
29
|
+
}),
|
|
30
|
+
configFile: Flags.string({
|
|
31
|
+
char: 'f',
|
|
32
|
+
description: 'Filename for the tracker configuration file',
|
|
33
|
+
default: 'config.json',
|
|
34
|
+
}),
|
|
35
|
+
ignorePatterns: Flags.string({
|
|
36
|
+
char: 'i',
|
|
37
|
+
description: 'Ignore patterns to use for the tracker configuration file',
|
|
38
|
+
multiple: true,
|
|
39
|
+
multipleNonGreedy: true,
|
|
40
|
+
default: ['node_modules'],
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
async run() {
|
|
44
|
+
const { flags } = await this.parse(Init);
|
|
45
|
+
const { overwrite, outputDir, configFile, ignorePatterns, force } = flags;
|
|
46
|
+
this.log('Starting tracker init command');
|
|
47
|
+
if (overwrite) {
|
|
48
|
+
if (force) {
|
|
49
|
+
this.warn(`You're using the --force flag along the --overwrite flag.`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const response = await confirm({
|
|
53
|
+
message: `You're using the overwrite flag. If a previous configuration file exists, it will be replaced. Do you want to continue?`,
|
|
54
|
+
default: false,
|
|
55
|
+
});
|
|
56
|
+
this.log(response ? 'Yes' : 'No');
|
|
57
|
+
if (!response) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const rootDir = getRootDir(global.process.cwd());
|
|
64
|
+
const outputConfig = {
|
|
65
|
+
...TRACKER_DEFAULT_CONFIG,
|
|
66
|
+
outputDir,
|
|
67
|
+
configFile,
|
|
68
|
+
ignorePatterns,
|
|
69
|
+
};
|
|
70
|
+
await createTrackerConfig(rootDir, outputConfig, overwrite);
|
|
71
|
+
this.log(`Tracker init command completed successfully.`);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (err instanceof Error) {
|
|
75
|
+
this.error(err, {
|
|
76
|
+
message: err.message,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.error('An unknown error occurred while running the tracker init command');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -4,6 +4,10 @@ export declare const GRAPHQL_PATH = "/graphql";
|
|
|
4
4
|
export declare const ANALYTICS_URL = "https://apps.herodevs.com/api/eol/track";
|
|
5
5
|
export declare const CONCURRENT_PAGE_REQUESTS = 3;
|
|
6
6
|
export declare const PAGE_SIZE = 500;
|
|
7
|
+
export declare const GIT_OUTPUT_FORMAT: string;
|
|
8
|
+
export declare const DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
|
|
9
|
+
export declare const DEFAULT_DATE_COMMIT_FORMAT = "MM/dd/yyyy, h:mm:ss a";
|
|
10
|
+
export declare const DEFAULT_DATE_COMMIT_MONTH_FORMAT = "MMMM yyyy";
|
|
7
11
|
export declare const config: {
|
|
8
12
|
eolReportUrl: string;
|
|
9
13
|
graphqlHost: string;
|
package/dist/config/constants.js
CHANGED
|
@@ -4,6 +4,11 @@ export const GRAPHQL_PATH = '/graphql';
|
|
|
4
4
|
export const ANALYTICS_URL = 'https://apps.herodevs.com/api/eol/track';
|
|
5
5
|
export const CONCURRENT_PAGE_REQUESTS = 3;
|
|
6
6
|
export const PAGE_SIZE = 500;
|
|
7
|
+
export const GIT_OUTPUT_FORMAT = `"${['%h', '%an', '%ad'].join('|')}"`;
|
|
8
|
+
// Committers Report - Date Constants
|
|
9
|
+
export const DEFAULT_DATE_FORMAT = 'yyyy-MM-dd';
|
|
10
|
+
export const DEFAULT_DATE_COMMIT_FORMAT = 'MM/dd/yyyy, h:mm:ss a';
|
|
11
|
+
export const DEFAULT_DATE_COMMIT_MONTH_FORMAT = 'MMMM yyyy';
|
|
7
12
|
let concurrentPageRequests = CONCURRENT_PAGE_REQUESTS;
|
|
8
13
|
const parsed = Number.parseInt(process.env.CONCURRENT_PAGE_REQUESTS ?? '0', 10);
|
|
9
14
|
if (parsed > 0) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TrackerCategoryDefinition {
|
|
2
|
+
fileTypes: string[];
|
|
3
|
+
includes: string[];
|
|
4
|
+
excludes?: string[];
|
|
5
|
+
jsTsPairs?: 'js' | 'ts' | 'ignore';
|
|
6
|
+
}
|
|
7
|
+
export type TrackerConfig = {
|
|
8
|
+
categories: {
|
|
9
|
+
[key: string]: TrackerCategoryDefinition;
|
|
10
|
+
};
|
|
11
|
+
ignorePatterns?: string[];
|
|
12
|
+
outputDir: string;
|
|
13
|
+
configFile: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const TRACKER_ROOT_FILE = "package.json";
|
|
16
|
+
export declare const TRACKER_DEFAULT_CONFIG: TrackerConfig;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const TRACKER_ROOT_FILE = 'package.json';
|
|
2
|
+
export const TRACKER_DEFAULT_CONFIG = {
|
|
3
|
+
categories: {
|
|
4
|
+
legacy: {
|
|
5
|
+
fileTypes: ['js', 'ts', 'html', 'css', 'scss', 'less'],
|
|
6
|
+
includes: ['./legacy'],
|
|
7
|
+
jsTsPairs: 'js',
|
|
8
|
+
},
|
|
9
|
+
modern: {
|
|
10
|
+
fileTypes: ['ts', 'html', 'css', 'scss', 'less'],
|
|
11
|
+
includes: ['./modern'],
|
|
12
|
+
jsTsPairs: 'ts',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
ignorePatterns: ['node_modules'],
|
|
16
|
+
outputDir: 'hd-tracker',
|
|
17
|
+
configFile: 'config.json',
|
|
18
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type ReportFormat = 'txt' | 'csv' | 'json';
|
|
2
|
+
export type CommitEntry = {
|
|
3
|
+
commitHash: string;
|
|
4
|
+
author: string;
|
|
5
|
+
date: Date;
|
|
6
|
+
monthGroup: string;
|
|
7
|
+
};
|
|
8
|
+
export type CommitAuthorData = {
|
|
9
|
+
commits: CommitEntry[];
|
|
10
|
+
lastCommitOn: Date;
|
|
11
|
+
};
|
|
12
|
+
export type CommitMonthData = {
|
|
13
|
+
start: Date | string;
|
|
14
|
+
end: Date | string;
|
|
15
|
+
totalCommits: number;
|
|
16
|
+
committers: AuthorCommitCount;
|
|
17
|
+
};
|
|
18
|
+
export type AuthorCommitCount = {
|
|
19
|
+
[author: string]: number;
|
|
20
|
+
};
|
|
21
|
+
export type AuthorReportTableRow = {
|
|
22
|
+
index: number;
|
|
23
|
+
author: string;
|
|
24
|
+
commits: number;
|
|
25
|
+
lastCommitOn: string;
|
|
26
|
+
};
|
|
27
|
+
export type MonthlyReportTableRow = {
|
|
28
|
+
index: number;
|
|
29
|
+
month: number;
|
|
30
|
+
start: string;
|
|
31
|
+
end: string;
|
|
32
|
+
totalCommits: number;
|
|
33
|
+
};
|
|
34
|
+
export type MonthlyReportRow = {
|
|
35
|
+
month: string;
|
|
36
|
+
} & CommitMonthData;
|
|
37
|
+
export type AuthorReportRow = {
|
|
38
|
+
author: string;
|
|
39
|
+
} & CommitAuthorData;
|
|
40
|
+
export type CommittersReport = AuthorReportRow[] | MonthlyReportRow[];
|
|
41
|
+
/**
|
|
42
|
+
* Parses git log output into structured data
|
|
43
|
+
* @param output - Git log command output
|
|
44
|
+
* @returns Parsed commit entries
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseGitLogOutput(output: string): CommitEntry[];
|
|
47
|
+
/**
|
|
48
|
+
* Generates commits author report
|
|
49
|
+
* @param entries - commit entries from git log
|
|
50
|
+
* @returns Commits Author Report
|
|
51
|
+
*/
|
|
52
|
+
export declare function generateCommittersReport(entries: CommitEntry[]): AuthorReportRow[];
|
|
53
|
+
/**
|
|
54
|
+
* Generates commits monthly report
|
|
55
|
+
* @param entries - commit entries from git log
|
|
56
|
+
* @returns Monthly Report
|
|
57
|
+
*/
|
|
58
|
+
export declare function generateMonthlyReport(entries: CommitEntry[]): MonthlyReportRow[];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { endOfMonth, formatDate, parse } from 'date-fns';
|
|
2
|
+
import { DEFAULT_DATE_COMMIT_MONTH_FORMAT, DEFAULT_DATE_FORMAT } from '../config/constants.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parses git log output into structured data
|
|
5
|
+
* @param output - Git log command output
|
|
6
|
+
* @returns Parsed commit entries
|
|
7
|
+
*/
|
|
8
|
+
export function parseGitLogOutput(output) {
|
|
9
|
+
return output
|
|
10
|
+
.split('\n')
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.map((line) => {
|
|
13
|
+
// Remove surrounding double quotes if present (e.g. "March|John Doe" → March|John Doe)
|
|
14
|
+
const [commitHash, author, date] = line.replace(/^"(.*)"$/, '$1').split('|');
|
|
15
|
+
return {
|
|
16
|
+
commitHash,
|
|
17
|
+
author,
|
|
18
|
+
date: parse(formatDate(new Date(date), DEFAULT_DATE_FORMAT), DEFAULT_DATE_FORMAT, new Date()),
|
|
19
|
+
monthGroup: formatDate(new Date(date), DEFAULT_DATE_COMMIT_MONTH_FORMAT),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Generates commits author report
|
|
25
|
+
* @param entries - commit entries from git log
|
|
26
|
+
* @returns Commits Author Report
|
|
27
|
+
*/
|
|
28
|
+
export function generateCommittersReport(entries) {
|
|
29
|
+
return Array.from(entries
|
|
30
|
+
.sort((a, b) => b.date.valueOf() - a.date.valueOf())
|
|
31
|
+
.reduce((acc, curr, _index, array) => {
|
|
32
|
+
if (!acc.has(curr.author)) {
|
|
33
|
+
const byAuthor = array.filter((c) => c.author === curr.author);
|
|
34
|
+
acc.set(curr.author, {
|
|
35
|
+
commits: byAuthor,
|
|
36
|
+
lastCommitOn: byAuthor[0].date,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return acc;
|
|
40
|
+
}, new Map()))
|
|
41
|
+
.map(([key, value]) => ({
|
|
42
|
+
author: key,
|
|
43
|
+
commits: value.commits,
|
|
44
|
+
lastCommitOn: value.lastCommitOn,
|
|
45
|
+
}))
|
|
46
|
+
.sort((a, b) => b.commits.length - a.commits.length);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Generates commits monthly report
|
|
50
|
+
* @param entries - commit entries from git log
|
|
51
|
+
* @returns Monthly Report
|
|
52
|
+
*/
|
|
53
|
+
export function generateMonthlyReport(entries) {
|
|
54
|
+
return Array.from(entries
|
|
55
|
+
.sort((a, b) => b.date.valueOf() - a.date.valueOf())
|
|
56
|
+
.reduce((acc, curr, _index, array) => {
|
|
57
|
+
if (!acc.has(curr.monthGroup)) {
|
|
58
|
+
const monthlyCommits = array.filter((e) => e.monthGroup === curr.monthGroup);
|
|
59
|
+
acc.set(curr.monthGroup, {
|
|
60
|
+
start: formatDate(monthlyCommits[0].date, DEFAULT_DATE_FORMAT),
|
|
61
|
+
end: formatDate(endOfMonth(monthlyCommits[0].date), DEFAULT_DATE_FORMAT),
|
|
62
|
+
totalCommits: monthlyCommits.length,
|
|
63
|
+
committers: monthlyCommits.reduce((acc, curr) => {
|
|
64
|
+
if (!acc[curr.author]) {
|
|
65
|
+
acc[curr.author] = monthlyCommits.filter((c) => c.author === curr.author).length;
|
|
66
|
+
}
|
|
67
|
+
return acc;
|
|
68
|
+
}, {}),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return acc;
|
|
72
|
+
}, new Map()))
|
|
73
|
+
.map(([key, value]) => ({
|
|
74
|
+
month: key,
|
|
75
|
+
...value,
|
|
76
|
+
}))
|
|
77
|
+
.sort((a, b) => new Date(a.end).valueOf() - new Date(b.end).valueOf());
|
|
78
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const isError: (error: unknown) => error is Error;
|
|
2
|
+
export declare const isErrnoException: (error: unknown) => error is NodeJS.ErrnoException;
|
|
3
|
+
export declare const isApolloError: (error: unknown) => error is ApolloError;
|
|
4
|
+
export declare const getErrorMessage: (error: unknown) => string;
|
|
5
|
+
export declare class ApolloError extends Error {
|
|
6
|
+
readonly originalError?: unknown;
|
|
7
|
+
constructor(message: string, original?: unknown);
|
|
8
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const isError = (error) => {
|
|
2
|
+
return error instanceof Error;
|
|
3
|
+
};
|
|
4
|
+
export const isErrnoException = (error) => {
|
|
5
|
+
return isError(error) && 'code' in error;
|
|
6
|
+
};
|
|
7
|
+
export const isApolloError = (error) => {
|
|
8
|
+
return error instanceof ApolloError;
|
|
9
|
+
};
|
|
10
|
+
export const getErrorMessage = (error) => {
|
|
11
|
+
if (isError(error)) {
|
|
12
|
+
return error.message;
|
|
13
|
+
}
|
|
14
|
+
return 'Unknown error';
|
|
15
|
+
};
|
|
16
|
+
export class ApolloError extends Error {
|
|
17
|
+
originalError;
|
|
18
|
+
constructor(message, original) {
|
|
19
|
+
if (isError(original)) {
|
|
20
|
+
super(`${message}: ${original.message}`);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
super(`${message}: ${String(original)}`);
|
|
24
|
+
}
|
|
25
|
+
this.name = 'ApolloError';
|
|
26
|
+
this.originalError = original;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { TRACKER_ROOT_FILE } from '../config/tracker.config.js';
|
|
5
|
+
export const getRootDir = (path) => {
|
|
6
|
+
if (existsSync(join(path, TRACKER_ROOT_FILE))) {
|
|
7
|
+
return path;
|
|
8
|
+
}
|
|
9
|
+
else if (path === join(path, '..')) {
|
|
10
|
+
throw new Error(`Couldn't find root directory for the project`);
|
|
11
|
+
}
|
|
12
|
+
return getRootDir(resolve(join(path, '..')));
|
|
13
|
+
};
|
|
14
|
+
export const createTrackerConfig = async (rootPath, config, overwrite = false) => {
|
|
15
|
+
const { outputDir } = config;
|
|
16
|
+
const configDir = join(rootPath, outputDir);
|
|
17
|
+
const configFile = join(configDir, config.configFile);
|
|
18
|
+
const doesConfigFileExists = existsSync(configFile);
|
|
19
|
+
if (!existsSync(configDir)) {
|
|
20
|
+
mkdirSync(configDir);
|
|
21
|
+
}
|
|
22
|
+
if (doesConfigFileExists && !overwrite) {
|
|
23
|
+
throw new Error(`Configuration file already exists for this repo. If you want to overwrite it, run the command again with the --overwrite flag`);
|
|
24
|
+
}
|
|
25
|
+
await writeFile(join(configDir, config.configFile), JSON.stringify(config, null, 2));
|
|
26
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herodevs/cli",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.14",
|
|
4
4
|
"author": "HeroDevs, Inc",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hd": "./bin/run.js"
|
|
@@ -39,13 +39,16 @@
|
|
|
39
39
|
"herodevs cli"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@amplitude/analytics-node": "^1.5.
|
|
43
|
-
"@apollo/client": "^
|
|
42
|
+
"@amplitude/analytics-node": "^1.5.22",
|
|
43
|
+
"@apollo/client": "^4.0.9",
|
|
44
44
|
"@cyclonedx/cdxgen": "^11.11.0",
|
|
45
|
-
"@herodevs/eol-shared": "github:herodevs/eol-shared#v0.1.
|
|
46
|
-
"@
|
|
45
|
+
"@herodevs/eol-shared": "github:herodevs/eol-shared#v0.1.12",
|
|
46
|
+
"@inquirer/prompts": "^8.0.1",
|
|
47
|
+
"@oclif/core": "^4.8.0",
|
|
47
48
|
"@oclif/plugin-help": "^6.2.32",
|
|
48
|
-
"@oclif/plugin-update": "^4.7.
|
|
49
|
+
"@oclif/plugin-update": "^4.7.14",
|
|
50
|
+
"@oclif/table": "^0.5.1",
|
|
51
|
+
"date-fns": "^4.1.0",
|
|
49
52
|
"node-machine-id": "^1.1.12",
|
|
50
53
|
"ora": "^9.0.0",
|
|
51
54
|
"packageurl-js": "^2.0.1",
|
|
@@ -53,14 +56,16 @@
|
|
|
53
56
|
"update-notifier": "^7.3.1"
|
|
54
57
|
},
|
|
55
58
|
"devDependencies": {
|
|
56
|
-
"@biomejs/biome": "^2.
|
|
59
|
+
"@biomejs/biome": "^2.3.4",
|
|
57
60
|
"@oclif/test": "^4.1.13",
|
|
58
61
|
"@types/inquirer": "^9.0.9",
|
|
59
|
-
"@types/
|
|
62
|
+
"@types/mock-fs": "^4.13.4",
|
|
63
|
+
"@types/node": "^24.10.0",
|
|
60
64
|
"@types/sinon": "^17.0.4",
|
|
61
65
|
"@types/update-notifier": "^6.0.8",
|
|
62
66
|
"globstar": "^1.0.0",
|
|
63
|
-
"
|
|
67
|
+
"mock-fs": "^5.5.0",
|
|
68
|
+
"oclif": "^4.22.47",
|
|
64
69
|
"shx": "^0.4.0",
|
|
65
70
|
"sinon": "^21.0.0",
|
|
66
71
|
"ts-node": "^10.9.2",
|