-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ng-dev): validate all licenses as being allowed for our Angular …
…projects (#644) A tool for validating that all packages provide allowed licenses in our projects. PR Close #644
- Loading branch information
1 parent
758b1b8
commit 613e401
Showing
8 changed files
with
412 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
load("//tools:defaults.bzl", "ts_library") | ||
|
||
ts_library( | ||
name = "validate-licenses", | ||
srcs = glob(["*.ts"]), | ||
visibility = ["//ng-dev/misc:__pkg__"], | ||
deps = [ | ||
"//ng-dev/utils", | ||
"@npm//@types/license-checker", | ||
"@npm//@types/node", | ||
"@npm//@types/spdx-satisfies", | ||
"@npm//@types/yargs", | ||
"@npm//license-checker", | ||
"@npm//spdx-satisfies", | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {Argv, Arguments, CommandModule} from 'yargs'; | ||
import {Log, green, red} from '../../utils/logging.js'; | ||
import {determineRepoBaseDirFromCwd} from '../../utils/repo-directory.js'; | ||
|
||
import {checkAllLicenses} from './validate.js'; | ||
|
||
/** Command line options. */ | ||
export interface Options {} | ||
|
||
/** Yargs command builder for the command. */ | ||
function builder(argv: Argv): Argv<Options> { | ||
return argv; | ||
} | ||
|
||
/** Yargs command handler for the command. */ | ||
async function handler({}: Arguments<Options>) { | ||
try { | ||
const {valid, maxPkgNameLength, packages} = await checkAllLicenses( | ||
determineRepoBaseDirFromCwd(), | ||
); | ||
if (valid) { | ||
Log.info( | ||
` ${green('✓')} All discovered licenses comply with our restrictions (${ | ||
packages.length | ||
} packages)`, | ||
); | ||
return; | ||
} | ||
|
||
Log.info(red(' ✘ The following packages were found to have disallowed licenses:\n')); | ||
Log.info(`${' Package Name'.padEnd(maxPkgNameLength)} | LICENSE`); | ||
packages | ||
.filter((pkg) => !pkg.allowed) | ||
.forEach((pkg) => { | ||
Log.info(` - ${pkg.name.padEnd(maxPkgNameLength)} | ${pkg.licenses}`); | ||
}); | ||
process.exitCode = 1; | ||
} catch (err) { | ||
Log.info(red(' ✘ An error occured while processing package licenses:')); | ||
Log.error(err); | ||
process.exitCode = 1; | ||
} | ||
} | ||
|
||
/** CLI command module. */ | ||
export const ValidateLicensesModule: CommandModule<{}, Options> = { | ||
builder, | ||
handler, | ||
command: 'validate-licenses', | ||
describe: 'Validate the licenses for all dependencies in the project', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
import licenseChecker, {ModuleInfo, ModuleInfos} from 'license-checker'; | ||
import spdx from 'spdx-satisfies'; | ||
|
||
// A general note on some disallowed licenses: | ||
// - CC0 | ||
// This is not a valid license. It does not grant copyright of the code/asset, and does not | ||
// resolve patents or other licensed work. The different claims also have no standing in court | ||
// and do not provide protection to or from Google and/or third parties. | ||
// We cannot use nor contribute to CC0 licenses. | ||
// - Public Domain | ||
// Same as CC0, it is not a valid license. | ||
|
||
/** List of established allowed licenses for depdenencies. */ | ||
const allowedLicenses = [ | ||
// Notice licenses | ||
'MIT', | ||
'ISC', | ||
'Apache-2.0', | ||
'Python-2.0', | ||
'Artistic-2.0', | ||
'BSD-2-Clause', | ||
'BSD-3-Clause', | ||
'BSD-4-Clause', | ||
'Zlib', | ||
'AFL-2.1', | ||
'CC-BY-3.0', | ||
'CC-BY-4.0', | ||
|
||
// Unencumbered | ||
'Unlicense', | ||
'CC0-1.0', | ||
'0BSD', | ||
]; | ||
|
||
/** Known name variations of SPDX licenses. */ | ||
const licenseReplacements = new Map<string, string>([ | ||
// Just a longer string that our script catches. SPDX official name is the shorter one. | ||
['Apache License, Version 2.0', 'Apache-2.0'], | ||
['Apache2', 'Apache-2.0'], | ||
['Apache 2.0', 'Apache-2.0'], | ||
['Apache v2', 'Apache-2.0'], | ||
|
||
// Alternate syntax | ||
['AFLv2.1', 'AFL-2.1'], | ||
|
||
// BSD is BSD-2-clause by default. | ||
['BSD', 'BSD-2-Clause'], | ||
]); | ||
|
||
interface ExpandedModuleInfo extends ModuleInfo { | ||
name: string; | ||
allowed: boolean; | ||
} | ||
|
||
export interface LicenseCheckResult { | ||
valid: boolean; | ||
packages: ExpandedModuleInfo[]; | ||
maxPkgNameLength: number; | ||
} | ||
|
||
export async function checkAllLicenses(start: string): Promise<LicenseCheckResult> { | ||
return new Promise((resolve, reject) => { | ||
let maxPkgNameLength = 0; | ||
licenseChecker.init({start}, (err: Error, pkgInfoObject: ModuleInfos) => { | ||
// If the license processor fails, reject the process with the error. | ||
if (err) { | ||
console.log('thats an error'); | ||
return reject(err); | ||
} | ||
|
||
// Check each package to ensure its license(s) are allowed. | ||
const packages = Object.entries(pkgInfoObject).map<ExpandedModuleInfo>( | ||
([name, pkg]: [string, ModuleInfo]) => { | ||
maxPkgNameLength = Math.max(maxPkgNameLength, name.length); | ||
/** | ||
* Array of licenses for the package. | ||
* | ||
* Note: Typically a package will only have one license, but support for multiple license | ||
* is necessary for full support. | ||
*/ | ||
const licenses = Array.isArray(pkg.licenses) ? pkg.licenses : [pkg.licenses!]; | ||
|
||
return { | ||
...pkg, | ||
name, | ||
allowed: licenses.some(assertAllowedLicense), | ||
}; | ||
}, | ||
); | ||
|
||
resolve({ | ||
valid: packages.every((pkg) => pkg.allowed), | ||
packages, | ||
maxPkgNameLength, | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
const allowedLicensesSpdxExpression = allowedLicenses.join(' OR '); | ||
// Check if a license is accepted by an array of accepted licenses | ||
function assertAllowedLicense(license: string) { | ||
// Licenses which are determined based on a file other than LICENSE are have an * appended. | ||
// See https://www.npmjs.com/package/license-checker#how-licenses-are-found | ||
const strippedLicense = license.endsWith('*') ? license.slice(0, -1) : license; | ||
try { | ||
// If the license is included in the known replacements, use the replacement instead. | ||
return spdx( | ||
licenseReplacements.get(strippedLicense) ?? strippedLicense, | ||
allowedLicensesSpdxExpression, | ||
); | ||
} catch { | ||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.