Skip to content

Commit 49f3284

Browse files
authored
feat: add allowonly option (#103)
1 parent cafcaa6 commit 49f3284

File tree

7 files changed

+443
-162
lines changed

7 files changed

+443
-162
lines changed

README.md

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,19 @@ node_modules/.bin/license-checker scan --failOn <license>
7171

7272
#### 🚩 <a name="options"></a>Options
7373

74-
| Option | Description | Requiered | Type | Default |
75-
|---|---|---|---|---|
76-
| --start | Path of the initial json to look for | false | string | `process.cwd()` |
77-
| --failOn | Fail (exit with code 1) if any package license does not satisfies any license in the provided list | true | string[] | |
78-
| --outputFileName | Name of the report file generated | false | string | `license-report-<timestamp>.md` |
79-
| --errorReportFileName | Name of the error report file generated when a license in the `failOn` option is found | false | string | `license-error-<timestamp>.md` |
80-
| --disableErrorReport | Flag to disable the error report file generation | false | boolean | `false` |
81-
| --disableReport | Flag to disable the report file generation, whether there is an error or not | false | boolean | `false` |
82-
| --customHeader | Name of a text file containing the custom header to add at the start of the generated report | false | string | This application makes use of the following open source packages: |
74+
| Option | Description | Requiered | Type | Default |
75+
|-----------------------|-----------------------------------------------------------------------------------------------------------------------|---|---|---|
76+
| --start | Path of the initial json to look for | false | string | `process.cwd()` |
77+
| --failOn | Fail (exit with code 1) if at least one package license **satisfies** one of the licenses in the provided list | true | string[] | |
78+
| --allowOnly | Fail (exit with code 1) if at least one package license **does not satisfy** one of the licenses in the provided list | true | string[] | |
79+
| --outputFileName | Name of the report file generated | false | string | `license-report-<timestamp>.md` |
80+
| --errorReportFileName | Name of the error report file generated when a license in the `failOn` option is found | false | string | `license-error-<timestamp>.md` |
81+
| --disableErrorReport | Flag to disable the error report file generation | false | boolean | `false` |
82+
| --disableReport | Flag to disable the report file generation, whether there is an error or not | false | boolean | `false` |
83+
| --customHeader | Name of a text file containing the custom header to add at the start of the generated report | false | string | This application makes use of the following open source packages: |
84+
85+
86+
> ❗The options `--failOn` and `--allowOnly` are mutually exclusive. You must use one of them.
8387
8488
## 🧑‍💻 <a name="examples"></a>Examples
8589

@@ -95,7 +99,7 @@ If the value provided is not SPDX compliant, the process fails (exit error 1).
9599

96100
### scan command
97101

98-
All the values provided in the `failOn` list must be [SPDX](https://spdx.dev/specifications/) compliant. Otherwise, an error will be thrown (exit error 1).
102+
All the values provided in the `failOn` or `allowOnly` list must be [SPDX](https://spdx.dev/specifications/) compliant. Otherwise, an error will be thrown (exit error 1).
99103
Check the [SPDX license list](https://spdx.org/licenses/).
100104

101105
```sh
@@ -105,14 +109,30 @@ npx @onebeyond/license-checker scan --failOn MIT GPL-1.0+
105109
The input list is transformed into a SPDX expression with the `OR` logical operator. In the example, that is `MIT OR GPL-1.0+`.
106110
If any of the packages' licenses satisfies that expression, the process fails (exit error 1).
107111

112+
SPDX compliance and `OR` input concatenation also apply for the `allowOnly` option:
113+
114+
```sh
115+
npx @onebeyond/license-checker scan --allowOnly MIT GPL-1.0+
116+
```
117+
118+
In this case, all the packages' licenses must be either `MIT` or `GPL-1.0+`.
119+
120+
Arguments to `failOn` and `allowOnly` are not limited to one license. Expressions with logical operators are also accepted:
121+
122+
```sh
123+
npx @onebeyond/license-checker scan --allowOnly "MIT AND Apache-2.0" GPL-1.0+
124+
```
125+
126+
In this example, all the packages' licenses must be either `MIT AND Apache-2.0` **or** `GPL-1.0+`.
127+
108128
## 🔗 Useful links
109129

110130
- [Licensing a repository](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/licensing-a-repository)
111131
- [Choose a license](https://choosealicense.com/appendix/)
112132

113133
## ⚠️ Temporal issue
114134

115-
An issue in `spdx-satisfies` has been found and it's pending resolution. Until then, GFDL 1x licenses are not supported and an error will be thrown if either packages or failOn arguments contain it.
135+
An issue in `spdx-satisfies` has been found, and it's pending resolution. Until then, GFDL 1x licenses are not supported and an error will be thrown if either packages or failOn arguments contain it.
116136

117137
## Contributors ✨
118138

cli.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
#!/usr/bin/env node
22

33
const yargs = require('yargs')(process.argv.slice(2));
4+
const { checkArgs } = require('./src/utils');
45

56
// https://github.com/yargs/yargs/blob/main/docs/advanced.md#commanddirdirectory-opts
67
module.exports = yargs
78
.parserConfiguration({ 'camel-case-expansion': false })
89
.commandDir('commands')
910
.demandCommand()
11+
.check(checkArgs)
1012
.help()
1113
.alias('help', 'h')
1214
.argv;

commands/scan.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@ exports.builder = {
1919
default: process.cwd()
2020
},
2121
failOn: {
22-
description: 'fail (exit with code 1) if any package license does not satisfies any license in the provided list',
22+
description: 'fail (exit with code 1) if at least one package license satisfies one of the licenses in the provided list ',
2323
type: 'array',
24-
demandOption: true
24+
conflicts: 'allowOnly'
25+
},
26+
allowOnly: {
27+
description: 'fail (exit with code 1) if at least one package license does not satisfy one of the licenses in the provided list',
28+
type: 'array',
29+
conflicts: 'failOn'
2530
},
2631
outputFileName: {
2732
description: 'name of the output file generated',

src/runner.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,22 @@ const check = (license) => {
2525
};
2626

2727
const scan = async (options) => {
28-
const { failOn } = options;
28+
const { failOn, allowOnly } = options;
2929

30-
if (!failOn) throw new Error('Error: You must provide a list of licenses to fail on in the "failOn" option.');
30+
const allowSelected = !!allowOnly;
31+
const licensesList = allowSelected ? allowOnly : failOn;
3132

32-
checkLicenseError(failOn); // @TODO Remove after issue has been solved
33-
checkSPDXCompliance(failOn);
34-
const bannedLicenses = generateSPDXExpression(failOn);
33+
checkLicenseError(licensesList); // @TODO Remove after issue has been solved
34+
checkSPDXCompliance(licensesList);
35+
const spdxLicensesExpression = generateSPDXExpression(licensesList);
3536

3637
const packages = await checker.parsePackages(options.start);
3738
const packageList = getPackageInfoList(packages);
3839

39-
const { forbidden: forbiddenPackages, nonCompliant: invalidPackages } = checkPackagesLicenses(bannedLicenses, packageList);
40+
const {
41+
forbidden: forbiddenPackages,
42+
nonCompliant: invalidPackages
43+
} = checkPackagesLicenses(spdxLicensesExpression, packageList, allowSelected);
4044
if (invalidPackages.length) {
4145
logger.warn(`The following package licenses are not SPDX compliant and cannot be validated:\n${invalidPackages.map(pkg => ` > ${pkg.package} | ${pkg.licenses}`).join('\n')}`);
4246
}

src/utils.js

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ const formatForbiddenLicenseError = licenses => {
4141
[licenses]: !stats[licenses] ? 1 : stats[licenses] + 1
4242
}), {});
4343

44-
const header = `Found ${licenses.length} packages with licenses defined by the --failOn flag:`;
44+
const header = `Found ${licenses.length} packages with licenses defined by the provided option:`;
4545
const lines = Object
4646
.entries(forbiddenLicenseStats)
4747
.map(([license, value]) => ` > ${value} packages with license ${license}`)
@@ -66,7 +66,7 @@ const checkSPDXCompliance = (licenses = []) => {
6666
const invalidLicenses = licenses.filter(arg => !isSPDXCompliant(arg));
6767
if (invalidLicenses.length) {
6868
throw new Error(
69-
`The following licenses are not SPDX compliant. Please, use the --checkLicense option to validate your input:\n${invalidLicenses.join(' | ')}`
69+
`The following licenses are not SPDX compliant. Please, use the "check" command to validate your input:\n${invalidLicenses.join(' | ')}`
7070
);
7171
}
7272
};
@@ -80,7 +80,7 @@ const checkLicenseError = (licenses = []) => {
8080
const errorLicenses = licenses.some(isLicenseError);
8181
if (errorLicenses) {
8282
throw new Error(
83-
'Your failOn list contains a GFDL-1.x licenses and they are temporary unallowed. There\'s an issue pending to solve.'
83+
'Your licenses list contains a GFDL-1.x licenses and they are temporary unallowed. There\'s an issue pending to solve.'
8484
);
8585
}
8686
};
@@ -93,37 +93,62 @@ const checkLicenseError = (licenses = []) => {
9393
*/
9494
const isLicenseError = (license = '') => licensesExceptions.includes(license);
9595

96+
/**
97+
* Filter out licenses from the SPDX complete list depending on the value of the shouldSatisfy flag.
98+
* @param {string} expression - A valid SPDX expression
99+
* @param {boolean} shouldSatisfy - If true, the result will yield all the licenses that satisfies the incoming expression. If false,
100+
* the result will yield any other license that do not match the expression
101+
* @return {string[]} - List of resulting licenses
102+
*/
103+
const getValidLicenses = (expression, shouldSatisfy) => spdxIds.filter(id => {
104+
if (isLicenseError(id)) return false;// @TODO Refactor after issue has been solved
105+
const isSatisfied = satisfiesSPDXLicense(id, expression);
106+
return shouldSatisfy ? isSatisfied : !isSatisfied;
107+
});
108+
96109
/**
97110
* Subtracts the expression from the full list of SPDX ids and check the result (the allowed licenses) against the list of packages.
98111
* If the license of the package itself is not SPDX compliant, the package will be included on the "nonCompliant" list.
99112
* If a package license does not satisfy the allowed SPDX id list, the package will be included on the "forbidden" list.
100113
* @param {string} expression - A SPDX expression
101114
* @param {object[]} packages - A list of packages to be checked against the SPDX expression
115+
* @param {boolean} allow - Determines the license check. If true, forbidden will contain all the packages that
116+
* do not comply with the expression. If false, forbidden will contain all the packages that comply with the expression
102117
* @return {{forbidden: object[], nonCompliant: object[]}} - A couple of lists including the packages that satisfy the SPDX expression
103118
* and the packages with a non SPDX compliant license
104119
*/
105-
const checkPackagesLicenses = (expression, packages) => {
106-
const validSpdxIds = expression && spdxIds.filter(id => !isLicenseError(id) && !satisfiesSPDXLicense(id, expression)); // @TODO Refactor after issue has been solved
107-
const allowedLicensesExp = expression && generateSPDXExpression(validSpdxIds);
120+
const checkPackagesLicenses = (expression, packages, allow = false) => {
121+
const validSpdxIds = getValidLicenses(expression, allow);
122+
123+
const allowedLicensesExp = generateSPDXExpression(validSpdxIds);
108124

109125
return packages.reduce((total, pkg) => {
110126
const { licenses } = pkg;
111127

112128
if (!isSPDXCompliant(licenses)) return { ...total, nonCompliant: [...total.nonCompliant, pkg] };
113129

114-
const isSatisfiedLicense = expression && !satisfiesSPDXLicense(licenses, allowedLicensesExp);
115-
if (isSatisfiedLicense) return { ...total, forbidden: [...total.forbidden, pkg] };
130+
if (!satisfiesSPDXLicense(licenses, allowedLicensesExp)) return { ...total, forbidden: [...total.forbidden, pkg] };
116131

117132
return total;
118133
}, { forbidden: [], nonCompliant: [] });
119134
};
120135

136+
const checkArgs = (args) => {
137+
if (args._[0] === 'scan') {
138+
const { failOn, allowOnly } = args;
139+
if (!failOn && !allowOnly) throw new Error('You need to provide the "failOn" or "allowOnly" option.');
140+
if ((failOn && !failOn.length) || (allowOnly && !allowOnly.length)) throw new Error('You need to provide at least one license.');
141+
}
142+
return true;
143+
};
144+
121145
module.exports = {
122146
getPackageInfoList,
123147
formatForbiddenLicenseError,
124148
generateSPDXExpression,
125149
checkSPDXCompliance,
126150
checkPackagesLicenses,
127151
isLicenseError,
128-
checkLicenseError
152+
checkLicenseError,
153+
checkArgs
129154
};

0 commit comments

Comments
 (0)