Skip to content

Commit

Permalink
feat(ng-dev): validate all licenses as being allowed for our Angular …
Browse files Browse the repository at this point in the history
…projects (#644)

A tool for validating that all packages provide allowed licenses in our projects.

PR Close #644
  • Loading branch information
josephperrott committed Aug 19, 2022
1 parent 758b1b8 commit 613e401
Show file tree
Hide file tree
Showing 8 changed files with 412 additions and 17 deletions.
2 changes: 1 addition & 1 deletion bazel/esbuild/index.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def esbuild_esm_bundle(name, **kwargs):
"""

args = dict(
resolveExtensions = [".mjs", ".js"],
resolveExtensions = [".mjs", ".js", ".json"],
outExtension = {".js": ".mjs"},
# Workaround for: https://github.com/evanw/esbuild/issues/1921.
banner = {
Expand Down
1 change: 1 addition & 0 deletions ng-dev/misc/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ts_library(
srcs = glob(["**/*.ts"]),
visibility = ["//ng-dev:__subpackages__"],
deps = [
"//ng-dev/misc/validate-licenses",
"//ng-dev/release/build",
"//ng-dev/release/config",
"//ng-dev/utils",
Expand Down
4 changes: 3 additions & 1 deletion ng-dev/misc/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {Argv} from 'yargs';
import {BuildAndLinkCommandModule} from './build-and-link/cli.js';
import {NewMainBranchCommandModule} from './new-main-branch/cli.js';
import {UpdateYarnCommandModule} from './update-yarn/cli.js';
import {ValidateLicensesModule} from './validate-licenses/cli.js';

/** Build the parser for the misc commands. */
export function buildMiscParser(localYargs: Argv) {
Expand All @@ -18,5 +19,6 @@ export function buildMiscParser(localYargs: Argv) {
.strict()
.command(BuildAndLinkCommandModule)
.command(NewMainBranchCommandModule)
.command(UpdateYarnCommandModule);
.command(UpdateYarnCommandModule)
.command(ValidateLicensesModule);
}
16 changes: 16 additions & 0 deletions ng-dev/misc/validate-licenses/BUILD.bazel
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",
],
)
59 changes: 59 additions & 0 deletions ng-dev/misc/validate-licenses/cli.ts
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',
};
122 changes: 122 additions & 0 deletions ng-dev/misc/validate-licenses/validate.ts
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;
}
}
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@
"@types/inquirer": "9.0.1",
"@types/jasmine": "^4.0.0",
"@types/jsdom": "^20.0.0",
"@types/license-checker": "^25.0.3",
"@types/opener": "^1.4.0",
"@types/semver": "^7.3.6",
"@types/spdx-satisfies": "^0.1.0",
"@types/supports-color": "^8.1.1",
"@types/wait-on": "^5.3.1",
"@types/which": "^2.0.1",
Expand Down Expand Up @@ -125,6 +127,7 @@
"karma-jasmine-html-reporter": "~2.0.0",
"karma-requirejs": "^1.1.0",
"karma-sourcemap-loader": "^0.3.8",
"license-checker": "^25.0.1",
"minimatch": "^5.0.0",
"multimatch": "^6.0.0",
"nock": "^13.0.3",
Expand All @@ -134,6 +137,7 @@
"requirejs": "^2.3.6",
"rxjs": "^7.4.0",
"semver": "^7.3.5",
"spdx-satisfies": "^5.0.1",
"supports-color": "9.2.2",
"terser": "^5.14.1",
"ts-node": "^10.8.1",
Expand Down
Loading

0 comments on commit 613e401

Please sign in to comment.