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

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/dist/index.mjs ADDED
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
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";
6
+ import logger from "loglevel";
7
+ import { GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
8
+ import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
9
+ import { $, fs, path } from "zx";
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
33
+ let ossLicensesBucketName;
34
+ const OSS_LICENSES_BUCKET_NAME_SSM_PARAMETER = "/config/oss-licenses/bucket-name";
35
+ const getOssLicensesBucketName = async () => {
36
+ if (!ossLicensesBucketName) {
37
+ const ssmClient = new SSMClient();
38
+ const command = new GetParameterCommand({ Name: OSS_LICENSES_BUCKET_NAME_SSM_PARAMETER });
39
+ const response = await ssmClient.send(command);
40
+ if (!response.Parameter?.Value) throw new Error("No license bucket name found");
41
+ ossLicensesBucketName = response.Parameter.Value;
42
+ logger.debug(`OSS Licenses Bucket: ${ossLicensesBucketName}`);
43
+ }
44
+ return ossLicensesBucketName;
45
+ };
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
+ }
53
+ };
54
+ const downloadWhitelist = async (checkType) => {
55
+ const getWhitelistObject = new GetObjectCommand({
56
+ Bucket: await getOssLicensesBucketName(),
57
+ Key: getWhitelistKey(checkType)
58
+ });
59
+ const whitelistResponse = await new S3Client().send(getWhitelistObject);
60
+ 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);
62
+ };
63
+ const stripQuotes = (line) => {
64
+ if (line.startsWith("\"") && line.endsWith("\"")) return line.slice(1, -1);
65
+ return line;
66
+ };
67
+ const uploadLicenseReport = async (accountName, serviceName, type, report) => {
68
+ const s3Client = new S3Client();
69
+ const putObjectCommand = new PutObjectCommand({
70
+ Bucket: await getOssLicensesBucketName(),
71
+ Key: `reports/${accountName}/${serviceName}_${type}.txt`,
72
+ Body: report
73
+ });
74
+ await s3Client.send(putObjectCommand);
75
+ };
76
+
77
+ //#endregion
78
+ //#region src/license-checker-gradle.ts
79
+ const normalizeLicenseReport = (report) => {
80
+ const sortedLicences = report.licences.map((license) => ({
81
+ ...license,
82
+ dependencies: [...license.dependencies].sort((a, b) => a.localeCompare(b))
83
+ })).sort((a, b) => {
84
+ const nameCompare = a.name.localeCompare(b.name);
85
+ if (nameCompare !== 0) return nameCompare;
86
+ const urlCompare = (a.url ?? "").localeCompare(b.url ?? "");
87
+ if (urlCompare !== 0) return urlCompare;
88
+ const dependenciesA = a.dependencies.join("|");
89
+ const dependenciesB = b.dependencies.join("|");
90
+ return dependenciesA.localeCompare(dependenciesB);
91
+ });
92
+ return {
93
+ ...report,
94
+ licences: sortedLicences
95
+ };
96
+ };
97
+ /**
98
+ * Calls the gradle license checker with the given options. If a license is found that is not in the whitelist, an error is thrown.
99
+ * @param directory Folder containing the package.json to analyze
100
+ * @param licenseWhitelist List of allowed licenses
101
+ * @param excludePackages List of packages to exclude from the report, e.g. the current package
102
+ *
103
+ * @returns A dependency report generated by the license checker
104
+ */
105
+ const callGradleLicenseChecker = async ({ directory, licenseWhitelist }) => {
106
+ const gradleWrapper = await discoverGradleWrapper(directory);
107
+ const result = await $({
108
+ cwd: directory,
109
+ nothrow: true,
110
+ quiet: true
111
+ })`./${gradleWrapper} downloadLicenses`;
112
+ 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")));
115
+ 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
+ const allowedLicenses = new Set(licenseWhitelist);
117
+ const notAllowedLicenses = /* @__PURE__ */ new Set();
118
+ for (const license of licensesByLicenseName.licences) if (!allowedLicenses.has(license.name)) {
119
+ notAllowedLicenses.add(license.name);
120
+ logger.warn(`License "${license.name}" is not in the whitelist. It is used by:\n${license.dependencies.join("\n")}`);
121
+ }
122
+ if (notAllowedLicenses.size > 0) throw new LicenseCheckError(`The following licenses are not in the whitelist: ${Array.from(notAllowedLicenses).sort().join(", ")}`);
123
+ return JSON.stringify(licensesByLicenseName, null, 2);
124
+ };
125
+ 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";
129
+ throw new LicenseCheckError(`Could not find gradle wrapper in ${directory} or the parent directory. Please ensure that the gradle wrapper is present.`);
130
+ };
131
+
132
+ //#endregion
133
+ //#region src/license-checker-npm.ts
134
+ const resolveLicenseCheckerPath = () => {
135
+ return createRequire(import.meta.url).resolve("license-checker-rseidelsohn/bin/license-checker-rseidelsohn");
136
+ };
137
+ /**
138
+ * Calls the npm license checker with the given options. If a license is found that is not in the whitelist, an error is thrown.
139
+ * @param directory Folder containing the package.json to analyze
140
+ * @param licenseWhitelist List of allowed licenses
141
+ * @param excludePackages List of packages to exclude from the report, e.g. the current package
142
+ *
143
+ * @returns A dependency report generated by the license checker
144
+ */
145
+ const callNpmLicenseChecker = async ({ directory, licenseWhitelist, excludePackages }) => {
146
+ const licenseCheckExecutable = resolveLicenseCheckerPath();
147
+ if (licenseWhitelist.length === 0) throw new Error("No licenses in whitelist. This would allow all licenses, which is certainly not intended.");
148
+ const onlyAllowOption = licenseWhitelist.join(";");
149
+ const excludePackagesOption = excludePackages.join(";");
150
+ const result = await $({
151
+ cwd: directory,
152
+ nothrow: true,
153
+ quiet: true
154
+ })`${licenseCheckExecutable} --json --production --onlyAllow ${onlyAllowOption} --excludePackages ${excludePackagesOption} --relativeModulePath`;
155
+ if (result.exitCode !== 0) throw new LicenseCheckError(result.stderr.trimEnd());
156
+ return result.stdout;
157
+ };
158
+
159
+ //#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);
176
+ logger.debug(`Licenses whitelist:\n----------------\n${licenseWhitelist.join("\n")}\n----------------`);
177
+ 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}`);
192
+ logger.debug(`License report:\n${result.trimEnd()}`);
193
+ if (upload) {
194
+ await uploadLicenseReport(contextName, serviceName, checkType, result);
195
+ logger.info(`Uploaded license report for ${contextName}/${serviceName}_${checkType}`);
196
+ }
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
+ };
202
+ const getExcludeForApplicationPackage = (directory) => {
203
+ const packageJson = JSON.parse(fs$1.readFileSync(path$1.join(directory, "package.json"), "utf8"));
204
+ const serviceVersion = packageJson.version;
205
+ return `${packageJson.name}@${serviceVersion}`;
206
+ };
207
+ const readIgnorePackages = (directory) => {
208
+ const ignorePackagesFile = path$1.join(directory, "oss-licenses-ignore-packages.txt");
209
+ let ignorePackages = [];
210
+ if (fs$1.existsSync(ignorePackagesFile)) ignorePackages = fs$1.readFileSync(ignorePackagesFile, "utf8").split("\n").filter((line) => line.trim() !== "");
211
+ return ignorePackages;
212
+ };
213
+
214
+ //#endregion
215
+ //#region src/index.ts
216
+ try {
217
+ await cli(process.argv);
218
+ } catch (error) {
219
+ if (error instanceof LicenseCheckError) {
220
+ console.error(error.message);
221
+ process.exit(2);
222
+ } else if (error instanceof CliArgsError) {
223
+ console.error(error.message);
224
+ process.exit(3);
225
+ } else {
226
+ console.error(error);
227
+ process.exit(1);
228
+ }
229
+ }
230
+
231
+ //#endregion
232
+ export { };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rio-cloud/rio-license-checker",
3
- "version": "1.1.2",
3
+ "version": "1.1.7",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -13,8 +13,9 @@
13
13
  "organization": true
14
14
  },
15
15
  "scripts": {
16
+ "build": "tsdown",
16
17
  "pretest": "tsc",
17
- "test": "vitest --test-timeout=60000",
18
+ "test": "vitest run --test-timeout=60000",
18
19
  "vulnerability-check": "npm audit --registry https://registry.npmjs.org --parseable --audit-level=moderate --omit=dev",
19
20
  "bump": "commit-and-tag-version -a --no-verify",
20
21
  "release": "npm run test && npm run bump",
@@ -23,39 +24,37 @@
23
24
  "release:push": "echo '✅ pushing release' && git push origin main --follow-tags"
24
25
  },
25
26
  "bin": {
26
- "rio-license-checker": "src/rio-license-checker.ts"
27
+ "rio-license-checker": "dist/index.mjs"
27
28
  },
28
29
  "files": [
29
- "src",
30
- "tsconfig.json",
31
- "README.md",
32
- "CHANGELOG.md"
30
+ "dist"
33
31
  ],
34
32
  "dependencies": {
35
- "@aws-sdk/client-s3": "3.879.0",
36
- "@aws-sdk/client-ssm": "3.879.0",
37
- "commander": "14.0.0",
33
+ "@aws-sdk/client-s3": "3.932.0",
34
+ "@aws-sdk/client-ssm": "3.932.0",
35
+ "commander": "14.0.2",
38
36
  "license-checker-rseidelsohn": "4.4.2",
39
37
  "loglevel": "1.9.2",
40
- "tsx": "4.20.5",
41
- "zx": "8.8.1"
38
+ "zx": "8.8.5"
42
39
  },
43
40
  "devDependencies": {
44
- "@biomejs/biome": "2.2.2",
41
+ "@biomejs/biome": "2.2.6",
45
42
  "@rio-cloud/biome-config-claid": "1.0.0",
46
- "@tsconfig/node22": "22.0.2",
43
+ "@tsconfig/node22": "22.0.5",
47
44
  "@types/jest": "30.0.0",
48
45
  "@types/license-checker": "25.0.6",
49
- "@types/node": "24.0.3",
46
+ "@types/node": "24.10.1",
50
47
  "aws-sdk-client-mock": "4.1.0",
51
48
  "aws-sdk-client-mock-jest": "4.1.0",
52
- "chalk": "5.6.0",
53
- "commit-and-tag-version": "12.5.2",
54
- "typescript": "5.9.2",
49
+ "chalk": "5.6.2",
50
+ "commit-and-tag-version": "12.6.0",
51
+ "tsdown": "0.16.5",
52
+ "typescript": "5.9.3",
55
53
  "vitest": "3.2.4"
56
54
  },
57
55
  "overrides": {
58
- "form-data": ">=4.0.4"
56
+ "form-data": ">=4.0.4",
57
+ "glob": ">=10.5.0"
59
58
  },
60
59
  "commit-and-tag-version": {
61
60
  "commitUrlFormat": "https://bitbucket.collaboration-man.com/projects/RIODEV/repos/rio-license-checker/commits/{{hash}}",
package/CHANGELOG.md DELETED
@@ -1,34 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
-
5
- ## [1.1.2](https://bitbucket.collaboration-man.com/projects/RIODEV/repos/rio-license-checker/compare/commits?targetBranch=refs%2Ftags%2Fv1.1.1&sourceBranch=refs%2Ftags%2Fv1.1.2) (2025-09-08)
6
-
7
- ## [1.1.1](https://bitbucket.collaboration-man.com/projects/RIODEV/repos/rio-license-checker/compare/commits?targetBranch=refs%2Ftags%2Fv1.1.0&sourceBranch=refs%2Ftags%2Fv1.1.1) (2025-06-03)
8
-
9
- ## [1.1.0](https://bitbucket.collaboration-man.com/projects/RIODEV/repos/rio-license-checker/compare/commits?targetBranch=refs%2Ftags%2Fv1.0.1&sourceBranch=refs%2Ftags%2Fv1.1.0) (2025-06-03)
10
-
11
-
12
- ### Features
13
-
14
- * Support gradle subprojects ([0cee7bf](https://bitbucket.collaboration-man.com/projects/RIODEV/repos/rio-license-checker/commits/0cee7bfc2c2bde766fd5aaa329dc592c53d89de7))
15
-
16
- ## [1.0.1](https://bitbucket.collaboration-man.com/projects/RIODEV/repos/rio-license-checker/compare/commits?targetBranch=refs%2Ftags%2Fv1.0.0&sourceBranch=refs%2Ftags%2Fv1.0.1) (2025-05-20)
17
-
18
-
19
- ### Bug Fixes
20
-
21
- * relax required node version ([c8a3d8d](https://bitbucket.collaboration-man.com/projects/RIODEV/repos/rio-license-checker/commits/c8a3d8de0a2da634a7e869b97932d9be0e6bc36c))
22
-
23
- ## [1.0.0](https://bitbucket.collaboration-man.com/projects/RIODEV/repos/rio-license-checker/compare/commits?targetBranch=refs%2Ftags%2Fv1.0.0-alpha.2&sourceBranch=refs%2Ftags%2Fv1.0.0) (2025-03-28)
24
-
25
- ## [1.0.0-alpha.2](https://bitbucket.collaboration-man.com/projects/RIODEV/repos/rio-license-checker/compare/commits?targetBranch=refs%2Ftags%2Fv1.0.0-alpha.1&sourceBranch=refs%2Ftags%2Fv1.0.0-alpha.2) (2025-03-21)
26
-
27
-
28
- ### Features
29
-
30
- * Append type (passed via --type) as suffix to the uploaded filename ([382362b](https://bitbucket.collaboration-man.com/projects/RIODEV/repos/rio-license-checker/commits/382362ba1afdfaa9f758cf156f01c392f60ebea1))
31
-
32
- ## [1.0.0-alpha.1](https://bitbucket.collaboration-man.com/projects/RIODEV/repos/rio-license-checker/compare/commits?targetBranch=refs%2Ftags%2Fv1.0.0-alpha.0&sourceBranch=refs%2Ftags%2Fv1.0.0-alpha.1) (2025-03-13)
33
-
34
- ## 1.0.0-alpha.0 (2025-03-12)
package/src/cli.ts DELETED
@@ -1,115 +0,0 @@
1
- import { Command, Option } from 'commander';
2
- import * as fs from 'node:fs';
3
- import * as path from 'node:path';
4
- import { downloadWhitelist, uploadLicenseReport } from './license-bucket.js';
5
- import logger from 'loglevel';
6
- import { callNpmLicenseChecker } from './license-checker-npm.js';
7
- import { callGradleLicenseChecker } from './license-checker-gradle.js';
8
- import { CliArgsError, LicenseCheckType } from './types.js';
9
-
10
- export const cli = async (args: string[]) => {
11
- const program = new Command();
12
-
13
- program
14
- .name('check-licenses')
15
- .option('-a, --account-name <account>', 'Account / context name')
16
- .option('-s, --service-name <service>', 'Service name')
17
- .addOption(
18
- new Option('-t, --type <type>', 'Type of the license check')
19
- .makeOptionMandatory(true)
20
- .choices(Object.values(LicenseCheckType)),
21
- )
22
- .addOption(
23
- new Option(
24
- '-d, --directory <directory>',
25
- 'Directory to check licenses for (defaults to current directory)',
26
- ).default(process.cwd()),
27
- )
28
- .addOption(new Option('-u, --upload', 'Upload license report to S3').default(false))
29
- .addOption(new Option('-v, --verbose', 'Enable debug logging').default(false))
30
- .parse(args);
31
-
32
- logger.setLevel(program.opts().verbose ? 'debug' : 'info');
33
-
34
- const contextName = program.opts().accountName;
35
- const serviceName = program.opts().serviceName;
36
- const checkType = parseType(program.opts().type);
37
- const upload = program.opts().upload as boolean;
38
-
39
- if (!contextName) {
40
- if (upload) {
41
- throw new CliArgsError('Account name must not be empty when --upload is specified');
42
- }
43
- } else if (!contextName.match(/^[a-zA-Z0-9-_]+$/)) {
44
- throw new CliArgsError('Account name must only contain alphanumeric characters, hyphens and underscores');
45
- }
46
- if (!serviceName) {
47
- if (upload) {
48
- throw new CliArgsError('Service name must not be empty when --upload is specified');
49
- }
50
- } else if (!serviceName.match(/^[a-zA-Z0-9-_]+$/)) {
51
- throw new CliArgsError('Service name must only contain alphanumeric characters, hyphens and underscores');
52
- }
53
-
54
- const licenseWhitelist = await downloadWhitelist(checkType);
55
- logger.debug(`Licenses whitelist:\n----------------\n${licenseWhitelist.join('\n')}\n----------------`);
56
-
57
- let result: string;
58
-
59
- if ([LicenseCheckType.NPM_BACKEND, LicenseCheckType.NPM_FRONTEND].includes(checkType)) {
60
- if (!fs.existsSync(path.join(program.opts().directory, 'node_modules'))) {
61
- throw new Error('No node_modules directory found. Please run "npm ci" before using the license checker.');
62
- }
63
-
64
- const excludeForApplicationPackage = getExcludeForApplicationPackage(program.opts().directory);
65
- const ignorePackages = readIgnorePackages(program.opts().directory);
66
-
67
- result = await callNpmLicenseChecker({
68
- directory: program.opts().directory,
69
- licenseWhitelist,
70
- excludePackages: [excludeForApplicationPackage, ...ignorePackages],
71
- });
72
- } else if (checkType === LicenseCheckType.GRADLE) {
73
- result = await callGradleLicenseChecker({
74
- directory: program.opts().directory,
75
- licenseWhitelist,
76
- });
77
- } else {
78
- throw new Error(`Unknown license check type: ${program.opts().type}`);
79
- }
80
-
81
- logger.debug(`License report:\n${result.trimEnd()}`);
82
-
83
- if (upload) {
84
- await uploadLicenseReport(contextName, serviceName, checkType, result);
85
- logger.info(`Uploaded license report for ${contextName}/${serviceName}_${checkType}`);
86
- }
87
- };
88
-
89
- export const parseType = (value: string): LicenseCheckType => {
90
- for (const type of Object.values(LicenseCheckType)) {
91
- if (type === value) {
92
- return type;
93
- }
94
- }
95
- throw new Error(`Unknown whitelist type: ${value}`);
96
- };
97
-
98
- const getExcludeForApplicationPackage = (directory: string): string => {
99
- const packageJson = JSON.parse(fs.readFileSync(path.join(directory, 'package.json'), 'utf8'));
100
- const serviceVersion = packageJson.version;
101
- const packageName = packageJson.name;
102
- return `${packageName}@${serviceVersion}`;
103
- };
104
-
105
- const readIgnorePackages = (directory: string): string[] => {
106
- const ignorePackagesFile = path.join(directory, 'oss-licenses-ignore-packages.txt');
107
- let ignorePackages: string[] = [];
108
- if (fs.existsSync(ignorePackagesFile)) {
109
- ignorePackages = fs
110
- .readFileSync(ignorePackagesFile, 'utf8')
111
- .split('\n')
112
- .filter((line) => line.trim() !== '');
113
- }
114
- return ignorePackages;
115
- };
@@ -1,73 +0,0 @@
1
- import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
2
- import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
3
- import logger from 'loglevel';
4
- import { LicenseCheckType } from './types.js';
5
-
6
- let ossLicensesBucketName: string;
7
-
8
- const OSS_LICENSES_BUCKET_NAME_SSM_PARAMETER = '/config/oss-licenses/bucket-name';
9
-
10
- const getOssLicensesBucketName = async (): Promise<string> => {
11
- if (!ossLicensesBucketName) {
12
- const ssmClient = new SSMClient();
13
- const command = new GetParameterCommand({
14
- Name: OSS_LICENSES_BUCKET_NAME_SSM_PARAMETER,
15
- });
16
- const response = await ssmClient.send(command);
17
- if (!response.Parameter?.Value) {
18
- throw new Error('No license bucket name found');
19
- }
20
- ossLicensesBucketName = response.Parameter.Value;
21
- logger.debug(`OSS Licenses Bucket: ${ossLicensesBucketName}`);
22
- }
23
- return ossLicensesBucketName;
24
- };
25
-
26
- const getWhitelistKey = (checkType: LicenseCheckType): string => {
27
- switch (checkType) {
28
- case LicenseCheckType.NPM_FRONTEND:
29
- return 'whitelist-npm-frontend.txt';
30
- case LicenseCheckType.NPM_BACKEND:
31
- return 'whitelist-npm-backend.txt';
32
- case LicenseCheckType.GRADLE:
33
- return 'whitelist-gradle.txt';
34
- default:
35
- throw new Error(`Unknown whitelist type: ${checkType}`);
36
- }
37
- };
38
-
39
- export const downloadWhitelist = async (checkType: LicenseCheckType): Promise<string[]> => {
40
- const getWhitelistObject = new GetObjectCommand({
41
- Bucket: await getOssLicensesBucketName(),
42
- Key: getWhitelistKey(checkType),
43
- });
44
- const s3Client = new S3Client();
45
-
46
- const whitelistResponse = await s3Client.send(getWhitelistObject);
47
- if (!whitelistResponse.Body) {
48
- throw new Error('No whitelist found');
49
- }
50
-
51
- const whitelistBuffer = await whitelistResponse.Body.transformToString();
52
- return whitelistBuffer
53
- .split('\n')
54
- .map((line) => stripQuotes(line))
55
- .filter((line) => line.trim().length > 0);
56
- };
57
-
58
- const stripQuotes = (line: string) => {
59
- if (line.startsWith('"') && line.endsWith('"')) {
60
- return line.slice(1, -1);
61
- }
62
- return line;
63
- };
64
-
65
- export const uploadLicenseReport = async (accountName: string, serviceName: string, type: string, report: string) => {
66
- const s3Client = new S3Client();
67
- const putObjectCommand = new PutObjectCommand({
68
- Bucket: await getOssLicensesBucketName(),
69
- Key: `reports/${accountName}/${serviceName}_${type}.txt`,
70
- Body: report,
71
- });
72
- await s3Client.send(putObjectCommand);
73
- };
@@ -1,70 +0,0 @@
1
- import {$, fs, path} from 'zx';
2
- import {type LicenseCheckerOptions, LicenseCheckError} from './types.js';
3
- import logger from 'loglevel';
4
-
5
- /**
6
- * Calls the gradle license checker with the given options. If a license is found that is not in the whitelist, an error is thrown.
7
- * @param directory Folder containing the package.json to analyze
8
- * @param licenseWhitelist List of allowed licenses
9
- * @param excludePackages List of packages to exclude from the report, e.g. the current package
10
- *
11
- * @returns A dependency report generated by the license checker
12
- */
13
- export const callGradleLicenseChecker = async ({
14
- directory,
15
- licenseWhitelist,
16
- }: Omit<LicenseCheckerOptions, 'excludePackages'>): Promise<string> => {
17
- const gradleWrapper = await discoverGradleWrapper(directory);
18
- const $$ = $({cwd: directory, nothrow: true, quiet: true});
19
- const result = await $$`./${gradleWrapper} downloadLicenses`;
20
-
21
- if (result.exitCode !== 0) {
22
- throw new LicenseCheckError(
23
- `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()}`,
24
- );
25
- }
26
-
27
- // open file
28
- const licensesByLicenseNameFile = path.join(directory, 'build', 'reports', 'license', 'license-dependency.json');
29
- const licensesByLicenseName = JSON.parse(await fs.readFile(licensesByLicenseNameFile, 'utf8'));
30
-
31
- if (licensesByLicenseName.licences.length === 0) {
32
- throw new LicenseCheckError(
33
- 'List of licenses is empty. It is highly unlikely your project has no dependencies. Check the configuration of the license-gradle-plugin.',
34
- );
35
- }
36
-
37
- // filter out allowed licenses
38
- const allowedLicenses = new Set(licenseWhitelist);
39
- const notAllowedLicenses = new Set();
40
- for (const license of licensesByLicenseName.licences) {
41
- if (!allowedLicenses.has(license.name)) {
42
- notAllowedLicenses.add(license.name);
43
- logger.warn(
44
- `License "${license.name}" is not in the whitelist. It is used by:\n${license.dependencies.join('\n')}`,
45
- );
46
- }
47
- }
48
-
49
- if (notAllowedLicenses.size > 0) {
50
- throw new LicenseCheckError(
51
- `The following licenses are not in the whitelist: ${Array.from(notAllowedLicenses).join(', ')}`,
52
- );
53
- }
54
-
55
- return JSON.stringify(licensesByLicenseName, null, 2);
56
- };
57
-
58
- const discoverGradleWrapper = async (directory: string): Promise<string | undefined> => {
59
- // For now, only check the current directory and the parent directory for the gradle wrapper - this might need to be extended if someone has a more complex project structure.
60
- const gradleWrapperFile = path.join(directory, 'gradlew');
61
- if (fs.existsSync(gradleWrapperFile)) {
62
- return 'gradlew';
63
- }
64
- if (fs.existsSync(path.join(directory, '..', 'gradlew'))) {
65
- return '../gradlew';
66
- }
67
- throw new LicenseCheckError(
68
- `Could not find gradle wrapper in ${directory} or the parent directory. Please ensure that the gradle wrapper is present.`,
69
- );
70
- }
@@ -1,38 +0,0 @@
1
- import { $ } from 'zx';
2
- import { createRequire } from 'node:module';
3
- import { type LicenseCheckerOptions, LicenseCheckError } from './types.js';
4
-
5
- const resolveLicenseCheckerPath = (): string => {
6
- const require = createRequire(import.meta.url);
7
- return require.resolve('license-checker-rseidelsohn/bin/license-checker-rseidelsohn');
8
- };
9
-
10
- /**
11
- * Calls the npm license checker with the given options. If a license is found that is not in the whitelist, an error is thrown.
12
- * @param directory Folder containing the package.json to analyze
13
- * @param licenseWhitelist List of allowed licenses
14
- * @param excludePackages List of packages to exclude from the report, e.g. the current package
15
- *
16
- * @returns A dependency report generated by the license checker
17
- */
18
- export const callNpmLicenseChecker = async ({
19
- directory,
20
- licenseWhitelist,
21
- excludePackages,
22
- }: LicenseCheckerOptions): Promise<string> => {
23
- const licenseCheckExecutable = resolveLicenseCheckerPath();
24
- if (licenseWhitelist.length === 0) {
25
- throw new Error('No licenses in whitelist. This would allow all licenses, which is certainly not intended.');
26
- }
27
- const onlyAllowOption = licenseWhitelist.join(';');
28
- const excludePackagesOption = excludePackages.join(';');
29
-
30
- const $$ = $({ cwd: directory, nothrow: true, quiet: true });
31
- const result =
32
- await $$`${licenseCheckExecutable} --json --production --onlyAllow ${onlyAllowOption} --excludePackages ${excludePackagesOption} --relativeModulePath`;
33
- if (result.exitCode !== 0) {
34
- throw new LicenseCheckError(result.stderr.trimEnd());
35
- }
36
-
37
- return result.stdout;
38
- };
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env -S npx tsx
2
-
3
- import { cli } from './cli.js';
4
- import { CliArgsError, LicenseCheckError } from './types.js';
5
-
6
- try {
7
- await cli(process.argv);
8
- } catch (error) {
9
- if (error instanceof LicenseCheckError) {
10
- console.error(error.message);
11
- process.exit(2);
12
- } else if (error instanceof CliArgsError) {
13
- console.error(error.message);
14
- process.exit(3);
15
- } else {
16
- console.error(error);
17
- process.exit(1);
18
- }
19
- }
package/src/types.ts DELETED
@@ -1,25 +0,0 @@
1
- export interface LicenseCheckerOptions {
2
- directory: string;
3
- licenseWhitelist: string[];
4
- excludePackages: string[];
5
- }
6
-
7
- export class LicenseCheckError extends Error {
8
- constructor(message: string) {
9
- super(message);
10
- this.name = 'LicenseCheckError';
11
- }
12
- }
13
-
14
- export class CliArgsError extends Error {
15
- constructor(message: string) {
16
- super(message);
17
- this.name = 'CliArgsError';
18
- }
19
- }
20
-
21
- export enum LicenseCheckType {
22
- NPM_BACKEND = 'npm-backend',
23
- NPM_FRONTEND = 'npm-frontend',
24
- GRADLE = 'gradle',
25
- }
package/tsconfig.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "extends": "@tsconfig/node22/tsconfig.json",
3
- "compilerOptions": {
4
- "noEmit": true
5
- }
6
- }