Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ng-dev): validate all licenses as being allowed for our Angular projects #644

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
josephperrott marked this conversation as resolved.
Show resolved Hide resolved
// 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