diff --git a/.eslintrc.json b/.eslintrc.json index c3bf0b37ad05..ad06da4e2265 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -164,7 +164,7 @@ "guard-for-in": "off", "handle-callback-err": "off", "id-length": "off", - "indent": [2,2], + "indent": [2,2, { "SwitchCase": 1 }], "init-declarations": "off", "jsx-quotes": "off", "key-spacing": [2, { diff --git a/package.json b/package.json index bd68e383d824..0da972783909 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,14 @@ "build": "node ./scripts/publish/build.js", "build:patch": "node ./scripts/patch.js", "build:packages": "for PKG in packages/*; do echo Building $PKG...; tsc -p $PKG; done", - "test": "npm-run-all -c test:packages test:cli test:deps", + "test": "npm-run-all -c test:packages test:cli test:deps test:licenses", "e2e": "npm run test:e2e", "e2e:nightly": "node tests/run_e2e.js --nightly", "test:e2e": "node tests/run_e2e.js", "test:cli": "node tests/runner", "test:deps": "node scripts/publish/validate_dependencies.js", "test:inspect": "node --inspect --debug-brk tests/runner", + "test:licenses": "node scripts/test-licenses.js", "test:packages": "node scripts/run-packages-spec.js", "eslint": "eslint .", "tslint": "tslint \"**/*.ts\" -c tslint.json -e \"**/config/schema.d.ts\" -e \"**/tests/**\" -e \"**/blueprints/*/files/**/*.ts\" -e \"node_modules/**\" -e \"tmp/**\" -e \"dist/**\"", @@ -57,7 +58,6 @@ "exports-loader": "^0.6.3", "extract-text-webpack-plugin": "^2.0.0-rc.3", "file-loader": "^0.10.0", - "findup": "0.1.5", "fs-extra": "~2.0.0", "get-caller-file": "^1.0.0", "glob": "^7.0.3", @@ -144,6 +144,7 @@ "resolve-bin": "^0.4.0", "rewire": "^2.5.1", "sinon": "^1.17.3", + "spdx-satisfies": "^0.1.3", "through": "^2.3.6", "tree-kill": "^1.0.0", "ts-node": "^2.0.0", diff --git a/packages/@angular/cli/ember-cli/lib/models/project.js b/packages/@angular/cli/ember-cli/lib/models/project.js index 1905a850c49b..fb993b77cd98 100644 --- a/packages/@angular/cli/ember-cli/lib/models/project.js +++ b/packages/@angular/cli/ember-cli/lib/models/project.js @@ -5,7 +5,7 @@ */ var Promise = require('../ext/promise'); var path = require('path'); -var findup = Promise.denodeify(require('findup')); +var findUp = require('../../../utilities/find-up').findUp; var resolve = Promise.denodeify(require('resolve')); var fs = require('fs'); var find = require('lodash/find'); @@ -470,7 +470,7 @@ Project.closest = function(pathName, _ui, _cli) { return new Project(result.directory, result.pkg, ui, _cli); }) .catch(function(reason) { - handleFindupError(pathName, reason); + handleFindupError(pathName); }); }; @@ -546,7 +546,7 @@ Project.projectOrnullProject = function(_ui, _cli) { */ Project.getProjectRoot = function () { try { - var directory = findup.sync(process.cwd(), 'package.json'); + var directory = path.dirname(findUp(process.cwd(), 'package.json')); var pkg = require(path.join(directory, 'package.json')); if (pkg && pkg.name === 'ember-cli') { @@ -557,12 +557,8 @@ Project.getProjectRoot = function () { debug('getProjectRoot %s -> %s', process.cwd(), directory); return directory; } catch (reason) { - if (isFindupError(reason)) { - debug('getProjectRoot: not found. Will use cwd: %s', process.cwd()); - return process.cwd(); - } else { - throw reason; - } + debug('getProjectRoot: not found. Will use cwd: %s', process.cwd()); + return process.cwd(); } }; @@ -594,34 +590,24 @@ function ensureUI(_ui) { } function closestPackageJSON(pathName) { - return findup(pathName, 'package.json') - .then(function(directory) { - return Promise.hash({ - directory: directory, - pkg: require(path.join(directory, 'package.json')) - }); - }); + return Promise.resolve() + .then(() => findUp('package.json', pathName)) + .then(filePath => ({ + directory: path.dirname(filePath), + pkg: require(filePath) + })); } function findupPath(pathName) { try { - return findup.sync(pathName, 'package.json'); + return path.dirname(findUp('package.json', pathName)); } catch (reason) { - handleFindupError(pathName, reason); + handleFindupError(pathName); } } -function isFindupError(reason) { - // Would be nice if findup threw error subclasses - return reason && /not found/i.test(reason.message); -} - -function handleFindupError(pathName, reason) { - if (isFindupError(reason)) { - throw new NotFoundError('No project found at or up from: `' + pathName + '`'); - } else { - throw reason; - } +function handleFindupError(pathName) { + throw new NotFoundError('No project found at or up from: `' + pathName + '`'); } // Export diff --git a/packages/@angular/cli/models/config.ts b/packages/@angular/cli/models/config.ts index 8de57cce60b1..547823d70358 100644 --- a/packages/@angular/cli/models/config.ts +++ b/packages/@angular/cli/models/config.ts @@ -5,28 +5,11 @@ import * as chalk from 'chalk'; import * as fs from 'fs'; import * as path from 'path'; -export const CLI_CONFIG_FILE_NAME = '.angular-cli.json'; -const CLI_CONFIG_FILE_NAME_ALT = 'angular-cli.json'; - - -function _findUp(name: string, from: string) { - let currentDir = from; - while (currentDir && currentDir !== path.parse(currentDir).root) { - const p = path.join(currentDir, name); - if (fs.existsSync(p)) { - return p; - } - - const nodeModuleP = path.join(currentDir, 'node_modules'); - if (fs.existsSync(nodeModuleP)) { - return null; - } +import {findUp} from '../utilities/find-up'; - currentDir = path.dirname(currentDir); - } - return null; -} +export const CLI_CONFIG_FILE_NAME = '.angular-cli.json'; +const CLI_CONFIG_FILE_NAME_ALT = 'angular-cli.json'; function getUserHome() { @@ -38,12 +21,12 @@ export class CliConfig extends CliConfigBase { static configFilePath(projectPath?: string): string { // Find the configuration, either where specified, in the Angular CLI project // (if it's in node_modules) or from the current process. - return (projectPath && _findUp(CLI_CONFIG_FILE_NAME, projectPath)) - || (projectPath && _findUp(CLI_CONFIG_FILE_NAME_ALT, projectPath)) - || _findUp(CLI_CONFIG_FILE_NAME, process.cwd()) - || _findUp(CLI_CONFIG_FILE_NAME_ALT, process.cwd()) - || _findUp(CLI_CONFIG_FILE_NAME, __dirname) - || _findUp(CLI_CONFIG_FILE_NAME_ALT, __dirname); + return (projectPath && findUp(CLI_CONFIG_FILE_NAME, projectPath)) + || (projectPath && findUp(CLI_CONFIG_FILE_NAME_ALT, projectPath)) + || findUp(CLI_CONFIG_FILE_NAME, process.cwd()) + || findUp(CLI_CONFIG_FILE_NAME_ALT, process.cwd()) + || findUp(CLI_CONFIG_FILE_NAME, __dirname) + || findUp(CLI_CONFIG_FILE_NAME_ALT, __dirname); } static fromGlobal(): CliConfig { diff --git a/packages/@angular/cli/package.json b/packages/@angular/cli/package.json index 9ab9bc9569a1..2df914d490a9 100644 --- a/packages/@angular/cli/package.json +++ b/packages/@angular/cli/package.json @@ -46,7 +46,6 @@ "exports-loader": "^0.6.3", "extract-text-webpack-plugin": "^2.0.0-rc.3", "file-loader": "^0.10.0", - "findup": "0.1.5", "fs-extra": "^2.0.0", "get-caller-file": "^1.0.0", "glob": "^7.0.3", diff --git a/packages/@angular/cli/upgrade/version.ts b/packages/@angular/cli/upgrade/version.ts index 1b363ab5e1a7..63c4850f5528 100644 --- a/packages/@angular/cli/upgrade/version.ts +++ b/packages/@angular/cli/upgrade/version.ts @@ -5,32 +5,18 @@ import {readFileSync, existsSync} from 'fs'; import * as path from 'path'; import {CliConfig} from '../models/config'; +import {findUp} from '../utilities/find-up'; const resolve = require('resolve'); -function _findUp(name: string, from: string) { - let currentDir = from; - while (currentDir && currentDir !== path.parse(currentDir).root) { - const p = path.join(currentDir, name); - if (existsSync(p)) { - return p; - } - - currentDir = path.dirname(currentDir); - } - - return null; -} - - function _hasOldCliBuildFile() { - return existsSync(_findUp('angular-cli-build.js', process.cwd())) - || existsSync(_findUp('angular-cli-build.ts', process.cwd())) - || existsSync(_findUp('ember-cli-build.js', process.cwd())) - || existsSync(_findUp('angular-cli-build.js', __dirname)) - || existsSync(_findUp('angular-cli-build.ts', __dirname)) - || existsSync(_findUp('ember-cli-build.js', __dirname)); + return existsSync(findUp('angular-cli-build.js', process.cwd())) + || existsSync(findUp('angular-cli-build.ts', process.cwd())) + || existsSync(findUp('ember-cli-build.js', process.cwd())) + || existsSync(findUp('angular-cli-build.js', __dirname)) + || existsSync(findUp('angular-cli-build.ts', __dirname)) + || existsSync(findUp('ember-cli-build.js', __dirname)); } @@ -112,7 +98,7 @@ export class Version { console.error(bold(red(stripIndents` This version of CLI is only compatible with angular version 2.3.1 or better. Please upgrade your angular version, e.g. by running: - + npm install @angular/core@latest ` + '\n'))); process.exit(3); diff --git a/packages/@angular/cli/utilities/find-up.ts b/packages/@angular/cli/utilities/find-up.ts new file mode 100644 index 000000000000..0d54428a3659 --- /dev/null +++ b/packages/@angular/cli/utilities/find-up.ts @@ -0,0 +1,23 @@ +import * as path from 'path'; +import { existsSync } from 'fs'; + +export function findUp(name: string, from: string, stopOnNodeModules = false) { + let currentDir = from; + while (currentDir && currentDir !== path.parse(currentDir).root) { + const p = path.join(currentDir, name); + if (existsSync(p)) { + return p; + } + + if (stopOnNodeModules) { + const nodeModuleP = path.join(currentDir, 'node_modules'); + if (existsSync(nodeModuleP)) { + return null; + } + } + + currentDir = path.dirname(currentDir); + } + + return null; +} diff --git a/scripts/test-licenses.js b/scripts/test-licenses.js new file mode 100644 index 000000000000..7cef8219198d --- /dev/null +++ b/scripts/test-licenses.js @@ -0,0 +1,148 @@ +require('../lib/bootstrap-local'); + +const path = require('path'); +const glob = require('glob'); +const chalk = require('chalk'); +const spdxSatisfies = require('spdx-satisfies'); +const Logger = require('@ngtools/logger').Logger; +require('rxjs/add/operator/filter'); + +// Configure logger +const logger = new Logger('test-licenses'); + +logger.subscribe((entry) => { + let color = chalk.white; + let output = process.stdout; + switch (entry.level) { + case 'info': color = chalk.white; break; + case 'warn': color = chalk.yellow; break; + case 'error': color = chalk.red; output = process.stderr; break; + case 'fatal': color = (x) => chalk.bold(chalk.red(x)); output = process.stderr; break; + } + + output.write(color(entry.message) + '\n'); +}); + +logger + .filter((entry) => entry.level == 'fatal') + .subscribe(() => { + process.stderr.write('A fatal error happened. See details above.'); + process.exit(1); + }); + +// SPDX defined licenses, see https://spdx.org/licenses/. +// TODO(hansl): confirm this list +const acceptedSpdxLicenses = [ + 'MIT', + 'ISC', + 'Apache-2.0', + 'BSD-2-Clause', + 'BSD-3-Clause', + 'BSD-4-Clause', + 'CC-BY-3.0', + 'CC-BY-4.0', + 'Beerware', + 'Unlicense' +]; + +// Name variations of SPDX licenses that some packages have. +// Licenses not included in SPDX but accepted will be converted to MIT. +// TODO(hansl): make sure all of these are ok +const licenseReplacements = [ + { name: 'Apache License, Version 2.0', replacement: 'Apache-2.0' }, + { name: 'AFLv2.1', replacement: 'AFL-2.1' }, + // I guess these are kinda the same? + { name: 'BSD', replacement: 'BSD-2-Clause' }, + { name: 'BSD-like', replacement: 'BSD-2-Clause' }, + { name: 'MIT/X11', replacement: 'MIT' }, + // Not sure how to deal with public domain. + // http://wiki.spdx.org/view/Legal_Team/Decisions/Dealing_with_Public_Domain_within_SPDX_Files + { name: 'Public Domain', replacement: 'MIT' } +]; + +// Specific packages to ignore, add a reason in a comment. Format: package-name@version. +// TODO(hansl): review these +const ignoredPackages = [ + 'async-foreach@0.1.3', // MIT, but doesn't list it in package.json + 'domelementtype@1.1.3', // Looks like MIT + 'domelementtype@1.3.0', // Looks like MIT + 'domhandler@2.1.0', // Looks like MIT + 'domutils@1.5.1', // Looks like MIT + 'domutils@1.1.6', // Looks like MIT + 'extsprintf@1.0.2', // Looks like MIT + 'formatio@1.1.1', // BSD, but doesn't list it in package.json + 'indexof@0.0.1', // MIT, but doesn't list it in package.json + 'map-stream@0.1.0', // MIT, license but it's not listed in package.json. + 'mime@1.2.11', // MIT, but doesn't list it in package.json + 'ms@0.7.1', // MIT, but doesn't list it in package.json + 'pause-stream@0.0.11', // MIT AND Apache-2.0, but broken license field in package.json lists. + 'progress@1.1.8', // MIT, but doesn't list it in package.json + 'samsam@1.1.2', // BSD, but doesn't list it in package.json + 'stdout-stream@1.4.0', // MIT, but doesn't list it in package.json + 'undefined@undefined', // Test package with no name nor version. + 'verror@1.3.6' // Looks like MIT +]; + +const root = path.resolve(__dirname, '../'); + +// Find all folders directly under a `node_modules` that have a package.json. +const allPackages = glob.sync(path.join(root, '**/node_modules/*/package.json'), { nodir: true }) + .map(packageJsonPath => { + const packageJson = require(packageJsonPath); + return { + id: `${packageJson.name}@${packageJson.version}`, + path: path.dirname(packageJsonPath), + packageJson: packageJson + }; + }) + // Figure out what kind of license the package uses. + .map(pkg => { + let license = null; + if (pkg.packageJson.license) { + // Use license field if present + if (typeof pkg.packageJson.license === 'string') { + license = replace(pkg.packageJson.license); + } else if (typeof pkg.packageJson.license === 'object' && typeof pkg.packageJson.type) { + license = replace(pkg.packageJson.license.type); + } + } else if (Array.isArray(pkg.packageJson.licenses)) { + // If there's an (outdated) licenses array use that joined by OR. + // TODO verify multiple licenses is OR and not AND + license = pkg.packageJson.licenses + .map(license => replace(license.type)) + .join(' OR '); + } + pkg.license = license; + return pkg; + }) + +logger.info(`Testing ${allPackages.length} packages.\n`) + +// Packages with bad licenses are those that neither pass SPDX nor are ignored. +const badLicensePackages = allPackages + .filter(pkg => !passesSpdx(pkg.license, acceptedSpdxLicenses)) + .filter(pkg => !ignoredPackages.find(ignored => ignored === pkg.id)); + +// Report packages with bad licenses +if (badLicensePackages.length > 0) { + logger.error('Invalid package licences found:'); + badLicensePackages.forEach(pkg => logger.error(`${pkg.id} (${pkg.path}): ${pkg.license}`)); + logger.fatal(`\n${badLicensePackages.length} total packages with invalid licenses.`); +} else { + logger.info('All package licenses are valid.'); +} + +// Check if a license is accepted by an array of accepted licenses +function passesSpdx(license, accepted) { + try { + return spdxSatisfies(license, `(${accepted.join(' OR ')})`) + } catch (_) { + return false; + } +} + +// Apply license name replacement if any +function replace(license) { + const match = licenseReplacements.find(rpl => rpl.name === license); + return match ? match.replacement : license; +}