@rio-cloud/rio-license-checker 1.1.7 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +65 -30
  2. package/dist/index.mjs +136 -83
  3. package/package.json +8 -7
package/README.md CHANGED
@@ -1,58 +1,93 @@
1
- # rio-license-checker
1
+ # [@rio-cloud/rio-license-checker](https://www.npmjs.com/package/@rio-cloud/rio-license-checker)
2
2
 
3
- Easy-to-use tool to run a license check according to the RIO guidelines.
3
+ Helps checking third-party libraries' licenses according to RIO guidelines.
4
4
 
5
- It does the following:
5
+ ## Usage
6
6
 
7
- * download the latest version of the RIO license whitelist for the given project type from the central RIO license S3 bucket
8
- * run a 3rd party license checker tool to crawl the licenses of the project in the given directory (or `.`, if not specified)
9
- * compare the licenses found with the RIO whitelist
10
- * if a license is found that is not in the whitelist: fail with nonzero exit code
11
- * if all licenses are compliant: upload a license report to the central RIO license S3 bucket (only if the `--upload` flag is set)
7
+ ```shell
8
+ # general usage information
9
+ npx @rio-cloud/rio-license-checker help
12
10
 
13
- Nothing of that is really new - this is supposed to be a testable, maintainable drop-in replacement for the code snippets & scripts we currently copy-paste from one repo to another.
11
+ # information on the default "run-check" command
12
+ npx @rio-cloud/rio-license-checker help run-check
14
13
 
15
- Currently supports:
14
+ # information on the "download-whitelist" command
15
+ npx @rio-cloud/rio-license-checker help download-whitelist
16
16
 
17
- * npm frontend projects
18
- * npm backend projects
19
- * gradle projects
17
+ # information on the "upload-report" command
18
+ npx @rio-cloud/rio-license-checker help upload-report
19
+ ```
20
20
 
21
- ## License Check Implementation
21
+ The `run-check` command is the default command. The command name is optional:
22
22
 
23
- ### npm
23
+ ```shell
24
+ npx @rio-cloud/rio-license-checker run-check -a rio-example -s example-service -t npm-frontend
25
+ # is the same as
26
+ npx @rio-cloud/rio-license-checker -a rio-example -s example-service -t npm-frontend
27
+ ```
24
28
 
25
- * The underlying license checker tool is [license-checker-rseidelsohn](https://www.npmjs.com/package/license-checker-rseidelsohn).
26
- * The license checker generates a report and compares the licenses to the passed whitelist.
27
- * The application's own package is automatically excluded from the report, as it (usually) does not have a license.
28
- * For testability reasons, we cannot use the programmatic interface of the tool. Instead, we call it as a subprocess via `zx`.
29
- * You can exclude dependencies by creating a `oss-licenses-ignore-packages.txt` file in the project directory.
29
+ ## Automatic mode (a.k.a. `run-check`)
30
+
31
+ 1. Downloads the appropriate license whitelist that's curated by the RIO Security Guild from a central location.
32
+ 2. Orchestrates a 3rd-party license checking tool to inspect your project's dependencies.
33
+ * If any dependency is found with non-compliant license information, this tool will exit with a non-zero code.
34
+ * If all dependencies are okay: generate a report file containing the list of dependencies, their versions, and their
35
+ respective license information.
36
+ 3. Uploads the report to a central location (optional; this is only done when the `--upload` flag is set).
30
37
 
31
- ### gradle
38
+ ### `gradle`
32
39
 
33
40
  * The underlying license report tool is [hierynomus/license-gradle-plugin](https://github.com/hierynomus/license-gradle-plugin).
34
41
  * You need to *include & configure the plugin in your `build.gradle.kts`*.
35
42
  * The output of the plugin is compared with the whitelist programmatically (as we used to do in the `build.gradle.kts`).
36
43
  * For subprojects, you need a separate invocation of the license checker, where the directory points to the subproject.
37
- * Currently, only subprojects directly below the root project are supported (limited by where the checker looks for the gradle wrapper).
38
- * When doing so, take care to specify a different service name to prevent overwriting the license report of the root project.
39
- * Please see below for an example.
44
+ * Currently, only subprojects directly below the root project are supported (limited by where the checker looks for
45
+ the gradle wrapper).
46
+ * When doing so, take care to specify a different service name to prevent overwriting the license report of the root
47
+ project.
48
+ * Please see below for examples.
40
49
 
41
- ```
50
+ ```shell
42
51
  npx @rio-cloud/rio-license-checker -a rio-example -s example-service -t gradle
43
52
  npx @rio-cloud/rio-license-checker -a rio-example -s example-service_sub-project -t gradle -d ./sub-project
44
53
  ```
45
54
 
46
- ## Usage
55
+ ### `npm-frontend` and `npm-backend`
47
56
 
48
- Show usage
57
+ * The underlying license checker tool is [license-checker-rseidelsohn](https://www.npmjs.com/package/license-checker-rseidelsohn).
58
+ * The license checker generates a report and compares the licenses to the passed whitelist.
59
+ * The application's own package is automatically excluded from the report, as it (usually) does not have a license.
60
+ * For testability reasons, we cannot use the programmatic interface of the tool. Instead, we call it as a subprocess via
61
+ `zx`.
62
+ * You can exclude dependencies by creating a `oss-licenses-ignore-packages.txt` file in the project directory.
63
+ * Note that this will _statically_ inspect the dependencies as defined in your `package.json`. Especially for frontends,
64
+ this might not be accurate enough. Check out the "manual" type for `npm-frontend-bundled` below.
65
+
66
+ ## `download-whitelist`
67
+
68
+ To just download the appropriate whitelist file as-is without any processing or running the check, you can use the
69
+ `download-whitelist` command:
49
70
 
50
71
  ```shell
51
- npx @rio-cloud/rio-license-checker -h
72
+ npx @rio-cloud/rio-license-checker download-whitelist -t npm-frontend-bundled
52
73
  ```
53
74
 
54
- Run the license check for a project. Requires AWS credentials (RIO developer role), e.g. via `AWS_PROFILE=...`.
75
+ Note that this will only work when the current session has access to AWS.
76
+
77
+ Also note that this only supports the "npm-frontend-bundled" type at the moment, which will save the whitelist as
78
+ `frontend-license-whitelist.json` in the project directory.
79
+
80
+ This command supports a `directory` and `verbose` option, as well. Check out the usage docs for more info.
81
+
82
+ ## `upload-report`
83
+
84
+ If your license check is handled by another tool during the build, you can use the `upload-report` command to upload the
85
+ report output from a given file.
55
86
 
56
87
  ```shell
57
- npx @rio-cloud/rio-license-checker -a rio-example -s example-service -t npm-frontend --verbose
88
+ npx @rio-cloud/rio-license-checker upload-report -a rio-example -s example-service -t npm-frontend-bundled -r ./build/libraries.json
58
89
  ```
90
+
91
+ Note that this only supports the "npm-frontend-bundled" type at the moment.
92
+
93
+ This command supports a `verbose` option, as well. Check out the usage docs for more info.
package/dist/index.mjs CHANGED
@@ -1,35 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
- import * as fs$1 from "node:fs";
4
- import * as path$1 from "node:path";
5
- import { Command, Option } from "commander";
3
+ import { Command, CommanderError, Option } from "commander";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
6
  import logger from "loglevel";
7
7
  import { GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
8
8
  import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
9
- import { $, fs, path } from "zx";
9
+ import { $, fs as fs$1, path as path$1 } from "zx";
10
10
 
11
- //#region src/types.ts
12
- var LicenseCheckError = class extends Error {
13
- constructor(message) {
14
- super(message);
15
- this.name = "LicenseCheckError";
16
- }
17
- };
18
- var CliArgsError = class extends Error {
19
- constructor(message) {
20
- super(message);
21
- this.name = "CliArgsError";
22
- }
23
- };
24
- let LicenseCheckType = /* @__PURE__ */ function(LicenseCheckType$1) {
25
- LicenseCheckType$1["NPM_BACKEND"] = "npm-backend";
26
- LicenseCheckType$1["NPM_FRONTEND"] = "npm-frontend";
27
- LicenseCheckType$1["GRADLE"] = "gradle";
28
- return LicenseCheckType$1;
29
- }({});
30
-
31
- //#endregion
32
- //#region src/license-bucket.ts
11
+ //#region src/s3/license-bucket.ts
33
12
  let ossLicensesBucketName;
34
13
  const OSS_LICENSES_BUCKET_NAME_SSM_PARAMETER = "/config/oss-licenses/bucket-name";
35
14
  const getOssLicensesBucketName = async () => {
@@ -43,27 +22,29 @@ const getOssLicensesBucketName = async () => {
43
22
  }
44
23
  return ossLicensesBucketName;
45
24
  };
46
- const getWhitelistKey = (checkType) => {
47
- switch (checkType) {
48
- case LicenseCheckType.NPM_FRONTEND: return "whitelist-npm-frontend.txt";
49
- case LicenseCheckType.NPM_BACKEND: return "whitelist-npm-backend.txt";
50
- case LicenseCheckType.GRADLE: return "whitelist-gradle.txt";
51
- default: throw new Error(`Unknown whitelist type: ${checkType}`);
52
- }
25
+ const s3Keys = {
26
+ "npm-frontend": "whitelist-npm-frontend.txt",
27
+ "npm-backend": "whitelist-npm-backend.txt",
28
+ gradle: "whitelist-gradle.txt",
29
+ "npm-frontend-bundled": "whitelist-npm-frontend-bundled.json"
53
30
  };
54
- const downloadWhitelist = async (checkType) => {
31
+ const downloadWhitelistFile = async (checkType) => {
55
32
  const getWhitelistObject = new GetObjectCommand({
56
33
  Bucket: await getOssLicensesBucketName(),
57
- Key: getWhitelistKey(checkType)
34
+ Key: s3Keys[checkType]
58
35
  });
59
36
  const whitelistResponse = await new S3Client().send(getWhitelistObject);
60
37
  if (!whitelistResponse.Body) throw new Error("No whitelist found");
61
- return (await whitelistResponse.Body.transformToString()).split("\n").map((line) => stripQuotes(line)).filter((line) => line.trim().length > 0);
38
+ return whitelistResponse.Body.transformToString();
39
+ };
40
+ const downloadAutomaticWhitelist = async (checkType) => {
41
+ return (await downloadWhitelistFile(checkType)).split("\n").map(stripQuotes).filter(removeEmptyLines);
62
42
  };
63
43
  const stripQuotes = (line) => {
64
44
  if (line.startsWith("\"") && line.endsWith("\"")) return line.slice(1, -1);
65
45
  return line;
66
46
  };
47
+ const removeEmptyLines = (line) => line.trim().length > 0;
67
48
  const uploadLicenseReport = async (accountName, serviceName, type, report) => {
68
49
  const s3Client = new S3Client();
69
50
  const putObjectCommand = new PutObjectCommand({
@@ -75,7 +56,45 @@ const uploadLicenseReport = async (accountName, serviceName, type, report) => {
75
56
  };
76
57
 
77
58
  //#endregion
78
- //#region src/license-checker-gradle.ts
59
+ //#region src/cli/downloadWhitelistFileCommand.ts
60
+ const downloadWhitelistFileCommand = new Command("download-whitelist").description("Download the license whitelist (experimental)").addOption(new Option("-t, --type <type>", "Type of the license check. (only supports \"npm-frontend-bundled\" for now)").makeOptionMandatory(true).choices(["npm-frontend-bundled"])).option("-d, --directory <directory>", "Directory to save the downloaded whitelist file to", process.cwd()).option("-v, --verbose", "Enable debug logging", false).action(async ({ type, directory, verbose }) => {
61
+ logger.setLevel(verbose ? "debug" : "info");
62
+ const licenseWhitelistFileContent = await downloadWhitelistFile(type);
63
+ const prettyJson = JSON.stringify(JSON.parse(licenseWhitelistFileContent), null, 2);
64
+ logger.debug(`Licenses whitelist:\n----------------\n${prettyJson}\n----------------`);
65
+ const whitelistFilePath = await saveWhitelistFile(directory, type, licenseWhitelistFileContent);
66
+ logger.info(`Successfully downloaded whitelist file into ${whitelistFilePath}`);
67
+ });
68
+ const whitelistFilenames = { "npm-frontend-bundled": "frontend-license-whitelist.json" };
69
+ const saveWhitelistFile = async (directory, checkType, content) => {
70
+ const filename = whitelistFilenames[checkType];
71
+ const filePath = path.join(directory, filename);
72
+ await fs.promises.writeFile(filePath, content, "utf8");
73
+ return filePath;
74
+ };
75
+
76
+ //#endregion
77
+ //#region src/types.ts
78
+ var LicenseCheckError = class extends Error {
79
+ constructor(message) {
80
+ super(message);
81
+ this.name = "LicenseCheckError";
82
+ }
83
+ };
84
+ var CliArgsError = class extends Error {
85
+ constructor(message) {
86
+ super(message);
87
+ this.name = "CliArgsError";
88
+ }
89
+ };
90
+ const automaticWhitelists = [
91
+ "npm-backend",
92
+ "npm-frontend",
93
+ "gradle"
94
+ ];
95
+
96
+ //#endregion
97
+ //#region src/integrations/license-checker-gradle.ts
79
98
  const normalizeLicenseReport = (report) => {
80
99
  const sortedLicences = report.licences.map((license) => ({
81
100
  ...license,
@@ -110,8 +129,8 @@ const callGradleLicenseChecker = async ({ directory, licenseWhitelist }) => {
110
129
  quiet: true
111
130
  })`./${gradleWrapper} downloadLicenses`;
112
131
  if (result.exitCode !== 0) throw new LicenseCheckError(`NOTE: This script requires the license-gradle-plugin to be configured, so that the script ./gradlew downloadLicenses works.\nSee https://github.com/hierynomus/license-gradle-plugin for details.\n${result.stderr.trimEnd()}`);
113
- const licensesByLicenseNameFile = path.join(directory, "build", "reports", "license", "license-dependency.json");
114
- const licensesByLicenseName = normalizeLicenseReport(JSON.parse(await fs.readFile(licensesByLicenseNameFile, "utf8")));
132
+ const licensesByLicenseNameFile = path$1.join(directory, "build", "reports", "license", "license-dependency.json");
133
+ const licensesByLicenseName = normalizeLicenseReport(JSON.parse(await fs$1.readFile(licensesByLicenseNameFile, "utf8")));
115
134
  if (licensesByLicenseName.licences.length === 0) throw new LicenseCheckError("List of licenses is empty. It is highly unlikely your project has no dependencies. Check the configuration of the license-gradle-plugin.");
116
135
  const allowedLicenses = new Set(licenseWhitelist);
117
136
  const notAllowedLicenses = /* @__PURE__ */ new Set();
@@ -123,14 +142,14 @@ const callGradleLicenseChecker = async ({ directory, licenseWhitelist }) => {
123
142
  return JSON.stringify(licensesByLicenseName, null, 2);
124
143
  };
125
144
  const discoverGradleWrapper = async (directory) => {
126
- const gradleWrapperFile = path.join(directory, "gradlew");
127
- if (fs.existsSync(gradleWrapperFile)) return "gradlew";
128
- if (fs.existsSync(path.join(directory, "..", "gradlew"))) return "../gradlew";
145
+ const gradleWrapperFile = path$1.join(directory, "gradlew");
146
+ if (fs$1.existsSync(gradleWrapperFile)) return "gradlew";
147
+ if (fs$1.existsSync(path$1.join(directory, "..", "gradlew"))) return "../gradlew";
129
148
  throw new LicenseCheckError(`Could not find gradle wrapper in ${directory} or the parent directory. Please ensure that the gradle wrapper is present.`);
130
149
  };
131
150
 
132
151
  //#endregion
133
- //#region src/license-checker-npm.ts
152
+ //#region src/integrations/license-checker-npm.ts
134
153
  const resolveLicenseCheckerPath = () => {
135
154
  return createRequire(import.meta.url).resolve("license-checker-rseidelsohn/bin/license-checker-rseidelsohn");
136
155
  };
@@ -157,60 +176,93 @@ const callNpmLicenseChecker = async ({ directory, licenseWhitelist, excludePacka
157
176
  };
158
177
 
159
178
  //#endregion
160
- //#region src/cli.ts
161
- const cli = async (args) => {
162
- const program = new Command();
163
- program.name("check-licenses").option("-a, --account-name <account>", "Account / context name").option("-s, --service-name <service>", "Service name").addOption(new Option("-t, --type <type>", "Type of the license check").makeOptionMandatory(true).choices(Object.values(LicenseCheckType))).addOption(new Option("-d, --directory <directory>", "Directory to check licenses for (defaults to current directory)").default(process.cwd())).addOption(new Option("-u, --upload", "Upload license report to S3").default(false)).addOption(new Option("-v, --verbose", "Enable debug logging").default(false)).parse(args);
164
- logger.setLevel(program.opts().verbose ? "debug" : "info");
165
- const contextName = program.opts().accountName;
166
- const serviceName = program.opts().serviceName;
167
- const checkType = parseType(program.opts().type);
168
- const upload = program.opts().upload;
169
- if (!contextName) {
170
- if (upload) throw new CliArgsError("Account name must not be empty when --upload is specified");
171
- } else if (!contextName.match(/^[a-zA-Z0-9-_]+$/)) throw new CliArgsError("Account name must only contain alphanumeric characters, hyphens and underscores");
172
- if (!serviceName) {
173
- if (upload) throw new CliArgsError("Service name must not be empty when --upload is specified");
174
- } else if (!serviceName.match(/^[a-zA-Z0-9-_]+$/)) throw new CliArgsError("Service name must only contain alphanumeric characters, hyphens and underscores");
175
- const licenseWhitelist = await downloadWhitelist(checkType);
179
+ //#region src/cli/validations.ts
180
+ const validateAccountName = (accountName) => {
181
+ if (!accountName.match(/^[a-zA-Z0-9-_]+$/)) throw new CliArgsError("Account name must only contain alphanumeric characters, hyphens and underscores");
182
+ };
183
+ const validateServiceName = (serviceName) => {
184
+ if (!serviceName.match(/^[a-zA-Z0-9-_]+$/)) throw new CliArgsError("Service name must only contain alphanumeric characters, hyphens and underscores");
185
+ };
186
+
187
+ //#endregion
188
+ //#region src/cli/runCheckCommand.ts
189
+ const runCheckCommand = new Command("run-check").summary("(default) Run an automated license check and optionally upload the report to S3").description("This is the default command to be used for backend and \"traditional\" frontend projects.\n\nWill download the whitelist, then perform the check and upload the report in one single step.").addOption(new Option("-t, --type <type>", "Type of the license check").makeOptionMandatory(true).choices(automaticWhitelists)).option("-a, --account-name <account>", "Account / context name").option("-s, --service-name <service>", "Service name").option("-d, --directory <directory>", "Directory of the project to check licenses for", process.cwd()).option("-u, --upload", "Upload license report to S3", false).option("-v, --verbose", "Enable debug logging", false).action(async (options) => {
190
+ logger.setLevel(options.verbose ? "debug" : "info");
191
+ const contextName = options.accountName;
192
+ const serviceName = options.serviceName;
193
+ const checkType = options.type;
194
+ const upload = options.upload;
195
+ if (upload) {
196
+ if (!contextName) throw new CliArgsError("Account name must not be empty when --upload is specified");
197
+ validateAccountName(contextName);
198
+ if (!serviceName) throw new CliArgsError("Service name must not be empty when --upload is specified");
199
+ validateServiceName(serviceName);
200
+ }
201
+ const licenseWhitelist = await downloadAutomaticWhitelist(checkType);
176
202
  logger.debug(`Licenses whitelist:\n----------------\n${licenseWhitelist.join("\n")}\n----------------`);
177
203
  let result;
178
- if ([LicenseCheckType.NPM_BACKEND, LicenseCheckType.NPM_FRONTEND].includes(checkType)) {
179
- if (!fs$1.existsSync(path$1.join(program.opts().directory, "node_modules"))) throw new Error("No node_modules directory found. Please run \"npm ci\" before using the license checker.");
180
- const excludeForApplicationPackage = getExcludeForApplicationPackage(program.opts().directory);
181
- const ignorePackages = readIgnorePackages(program.opts().directory);
182
- result = await callNpmLicenseChecker({
183
- directory: program.opts().directory,
184
- licenseWhitelist,
185
- excludePackages: [excludeForApplicationPackage, ...ignorePackages]
186
- });
187
- } else if (checkType === LicenseCheckType.GRADLE) result = await callGradleLicenseChecker({
188
- directory: program.opts().directory,
189
- licenseWhitelist
190
- });
191
- else throw new Error(`Unknown license check type: ${program.opts().type}`);
204
+ switch (checkType) {
205
+ case "npm-backend":
206
+ case "npm-frontend": {
207
+ if (!fs.existsSync(path.join(options.directory, "node_modules"))) throw new Error("No node_modules directory found. Please run \"npm ci\" before using the license checker.");
208
+ const excludeForApplicationPackage = getExcludeForApplicationPackage(options.directory);
209
+ const ignorePackages = readIgnorePackages(options.directory);
210
+ result = await callNpmLicenseChecker({
211
+ directory: options.directory,
212
+ licenseWhitelist,
213
+ excludePackages: [excludeForApplicationPackage, ...ignorePackages]
214
+ });
215
+ break;
216
+ }
217
+ case "gradle":
218
+ result = await callGradleLicenseChecker({
219
+ directory: options.directory,
220
+ licenseWhitelist
221
+ });
222
+ break;
223
+ default: throw new Error(`Unknown license check type: ${options.type}`);
224
+ }
192
225
  logger.debug(`License report:\n${result.trimEnd()}`);
193
226
  if (upload) {
194
227
  await uploadLicenseReport(contextName, serviceName, checkType, result);
195
228
  logger.info(`Uploaded license report for ${contextName}/${serviceName}_${checkType}`);
196
229
  }
197
- };
198
- const parseType = (value) => {
199
- for (const type of Object.values(LicenseCheckType)) if (type === value) return type;
200
- throw new Error(`Unknown whitelist type: ${value}`);
201
- };
230
+ });
202
231
  const getExcludeForApplicationPackage = (directory) => {
203
- const packageJson = JSON.parse(fs$1.readFileSync(path$1.join(directory, "package.json"), "utf8"));
232
+ const packageJson = JSON.parse(fs.readFileSync(path.join(directory, "package.json"), "utf8"));
204
233
  const serviceVersion = packageJson.version;
205
234
  return `${packageJson.name}@${serviceVersion}`;
206
235
  };
207
236
  const readIgnorePackages = (directory) => {
208
- const ignorePackagesFile = path$1.join(directory, "oss-licenses-ignore-packages.txt");
237
+ const ignorePackagesFile = path.join(directory, "oss-licenses-ignore-packages.txt");
209
238
  let ignorePackages = [];
210
- if (fs$1.existsSync(ignorePackagesFile)) ignorePackages = fs$1.readFileSync(ignorePackagesFile, "utf8").split("\n").filter((line) => line.trim() !== "");
239
+ if (fs.existsSync(ignorePackagesFile)) ignorePackages = fs.readFileSync(ignorePackagesFile, "utf8").split("\n").filter((line) => line.trim() !== "");
211
240
  return ignorePackages;
212
241
  };
213
242
 
243
+ //#endregion
244
+ //#region src/cli/uploadReportCommand.ts
245
+ const uploadReportCommand = new Command("upload-report").description("Upload a license report (placeholder)").addOption(new Option("-t, --type <type>", "Type of the license check. (only supports \"npm-frontend-bundled\" for now)").makeOptionMandatory(true).choices(["npm-frontend-bundled"])).addOption(new Option("-a, --account-name <account>", "Account / context name").makeOptionMandatory(true)).addOption(new Option("-s, --service-name <service>", "Service name").makeOptionMandatory(true)).addOption(new Option("-r, --report-file <file>", "Path to the license report file").makeOptionMandatory(true)).option("-v, --verbose", "Enable debug logging", false).action(async ({ type, accountName, serviceName, reportFile, verbose }) => {
246
+ logger.setLevel(verbose ? "debug" : "info");
247
+ validateAccountName(accountName);
248
+ validateServiceName(serviceName);
249
+ const reportFileContent = await fs.promises.readFile(reportFile, "utf8");
250
+ logger.debug(`Report file:\n${reportFileContent}\n`);
251
+ await uploadLicenseReport(accountName, serviceName, type, reportFileContent);
252
+ logger.info(`Uploaded license report for ${accountName}/${serviceName}_${type}`);
253
+ });
254
+
255
+ //#endregion
256
+ //#region src/cli.ts
257
+ const wrapCommand = (command) => command.exitOverride((error) => {
258
+ throw error;
259
+ });
260
+ const cli = (args) => {
261
+ const program = new Command();
262
+ program.name("npx @rio-cloud/rio-license-checker").addCommand(wrapCommand(runCheckCommand), { isDefault: true }).addCommand(wrapCommand(downloadWhitelistFileCommand)).addCommand(wrapCommand(uploadReportCommand)).addHelpText("after", `\n"run-check" is the default command if you don't specify one.\nRun "npx @rio-cloud/rio-license-checker help run-check" for details.`);
263
+ return program.parseAsync(args);
264
+ };
265
+
214
266
  //#endregion
215
267
  //#region src/index.ts
216
268
  try {
@@ -222,8 +274,9 @@ try {
222
274
  } else if (error instanceof CliArgsError) {
223
275
  console.error(error.message);
224
276
  process.exit(3);
225
- } else {
226
- console.error(error);
277
+ } else if (error instanceof CommanderError) process.exit(error.exitCode);
278
+ else {
279
+ console.error("unknown error", error);
227
280
  process.exit(1);
228
281
  }
229
282
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rio-cloud/rio-license-checker",
3
- "version": "1.1.7",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -14,8 +14,9 @@
14
14
  },
15
15
  "scripts": {
16
16
  "build": "tsdown",
17
+ "dev": "tsdown --watch ./src",
17
18
  "pretest": "tsc",
18
- "test": "vitest run --test-timeout=60000",
19
+ "test": "vitest run",
19
20
  "vulnerability-check": "npm audit --registry https://registry.npmjs.org --parseable --audit-level=moderate --omit=dev",
20
21
  "bump": "commit-and-tag-version -a --no-verify",
21
22
  "release": "npm run test && npm run bump",
@@ -30,15 +31,15 @@
30
31
  "dist"
31
32
  ],
32
33
  "dependencies": {
33
- "@aws-sdk/client-s3": "3.932.0",
34
- "@aws-sdk/client-ssm": "3.932.0",
34
+ "@aws-sdk/client-s3": "3.937.0",
35
+ "@aws-sdk/client-ssm": "3.936.0",
35
36
  "commander": "14.0.2",
36
37
  "license-checker-rseidelsohn": "4.4.2",
37
38
  "loglevel": "1.9.2",
38
39
  "zx": "8.8.5"
39
40
  },
40
41
  "devDependencies": {
41
- "@biomejs/biome": "2.2.6",
42
+ "@biomejs/biome": "2.3.7",
42
43
  "@rio-cloud/biome-config-claid": "1.0.0",
43
44
  "@tsconfig/node22": "22.0.5",
44
45
  "@types/jest": "30.0.0",
@@ -48,9 +49,9 @@
48
49
  "aws-sdk-client-mock-jest": "4.1.0",
49
50
  "chalk": "5.6.2",
50
51
  "commit-and-tag-version": "12.6.0",
51
- "tsdown": "0.16.5",
52
+ "tsdown": "0.16.6",
52
53
  "typescript": "5.9.3",
53
- "vitest": "3.2.4"
54
+ "vitest": "4.0.14"
54
55
  },
55
56
  "overrides": {
56
57
  "form-data": ">=4.0.4",