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

Remove use of npm ls in grunt tasks #11965

Merged
merged 8 commits into from
May 24, 2017
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,6 @@
"ncp": "2.0.0",
"nock": "8.0.0",
"node-sass": "3.8.0",
"npm": "3.10.10",
"portscanner": "1.0.0",
"proxyquire": "1.7.10",
"sass-loader": "4.0.0",
Expand Down
119 changes: 30 additions & 89 deletions tasks/build/notice.js
Original file line number Diff line number Diff line change
@@ -1,94 +1,35 @@
import _ from 'lodash';
import npmLicense from 'license-checker';
import glob from 'glob';
import path from 'path';
import fs from 'fs';
import { execSync } from 'child_process';
import { resolve } from 'path';

import {
getInstalledPackages,
generateNoticeText,
} from '../lib';

async function generate(grunt, directory) {
return await generateNoticeText({
packages: await getInstalledPackages({
directory,
licenseOverrides: grunt.config.get('licenses.options.overrides')
}),
nodeDir: grunt.config.get('platforms')[0].nodeDir
});
}

export default function licenses(grunt) {
export default function (grunt) {
grunt.registerTask('_build:notice', 'Adds a notice', function () {
const done = this.async();
const buildPath = path.join(grunt.config.get('buildDir'), 'kibana');

function getPackagePaths() {
const packagePaths = {};
const installedPackages = execSync(`npm ls --parseable --long`, {
cwd: buildPath
});
installedPackages.toString().trim().split('\n').forEach(pkg => {
let modulePath;
let dirPath;
let packageName;
let drive;
const packageDetails = pkg.split(':');
if (/^win/.test(process.platform)) {
[drive, dirPath, packageName] = packageDetails;
modulePath = `${drive}:${dirPath}`;
} else {
[modulePath, packageName] = packageDetails;
}
const licenses = glob.sync(path.join(modulePath, '*LICENSE*'));
const notices = glob.sync(path.join(modulePath, '*NOTICE*'));
packagePaths[packageName] = {
relative: modulePath.replace(/.*(\/|\\)kibana(\/|\\)/, ''),
licenses,
notices
};
});
return packagePaths;
}

function combineFiles(filePaths) {
let content = '';
filePaths.forEach(filePath => {
content += fs.readFileSync(filePath) + '\n';
});
return content;
}

function getNodeInfo() {
const nodeVersion = grunt.config.get('nodeVersion');
const nodeDir = path.join(grunt.config.get('root'), '.node_binaries', nodeVersion);
const licensePath = path.join(nodeDir, 'linux-x64', 'LICENSE');
const license = fs.readFileSync(licensePath);
return `This product bundles Node.js.\n\n${license}`;
}

function getPackageInfo(packages) {
const packagePaths = getPackagePaths();
const overrides = grunt.config.get('licenses.options.overrides');
let content = '';
_.forOwn(packages, (value, key) => {
const licenses = [].concat(overrides.hasOwnProperty(key) ? overrides[key] : value.licenses);
if (!licenses.length || licenses.includes('UNKNOWN')) return grunt.fail.fatal(`Unknown license for ${key}`);
const packagePath = packagePaths[key];
const readLicenseAndNotice = combineFiles([].concat(packagePath.licenses, packagePath.notices));
const licenseOverview = licenses.length > 1 ? `the\n"${licenses.join('", ')} licenses` : `a\n"${licenses[0]}" license`;
const licenseAndNotice = readLicenseAndNotice ? `\n${readLicenseAndNotice}` : ` For details, see ${packagePath.relative}/.`;
const combinedText = `This product bundles ${key} which is available under ${licenseOverview}.${licenseAndNotice}\n---\n`;

content += combinedText;
});
return content;
}

function getBaseNotice() {
return fs.readFileSync(path.join(__dirname, 'notice', 'base_notice.txt'));
}

npmLicense.init({
start: buildPath,
production: true,
json: true
}, (result, error) => {
if (error) return grunt.fail.fatal(error);
const noticePath = path.join(buildPath, 'NOTICE.txt');
const fd = fs.openSync(noticePath, 'w');
fs.appendFileSync(fd, getBaseNotice());
fs.appendFileSync(fd, getPackageInfo(result));
fs.appendFileSync(fd, getNodeInfo());
fs.closeSync(fd);
done();
});
const kibanaDir = resolve(grunt.config.get('buildDir'), 'kibana');
const noticePath = resolve(kibanaDir, 'NOTICE.txt');

generate(grunt, kibanaDir).then(
(noticeText) => {
grunt.file.write(noticePath, noticeText);
done();
},
(error) => {
grunt.fail.fatal(error);
done(error);
}
);
});
}
1 change: 1 addition & 0 deletions tasks/config/simplemocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
'test/mocha_setup.js',
'test/**/__tests__/**/*.js',
'src/**/__tests__/**/*.js',
'tasks/**/__tests__/**/*.js',
'test/fixtures/__tests__/*.js',
'!src/**/public/**',
'!**/_*.js'
Expand Down
3 changes: 3 additions & 0 deletions tasks/lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { generateNoticeText } from './notice';
export { getInstalledPackages } from './packages';
export { assertLicensesValid } from './licenses';
62 changes: 62 additions & 0 deletions tasks/lib/licenses/__tests__/valid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { resolve } from 'path';

import expect from 'expect.js';

import { assertLicensesValid } from '../valid';

const NODE_MODULES = resolve(__dirname, '../../../../node_modules');

const PACKAGE = {
name: '@elastic/httpolyglot',
version: '0.1.2-elasticpatch1',
licenses: ['MIT'],
directory: resolve(NODE_MODULES, '@elastic/httpolyglot'),
relative: 'node_modules/@elastic/httpolyglot',
};

describe('tasks/lib/licenses', () => {
describe('assertLicensesValid()', () => {
it('returns undefined when package has valid license', () => {
expect(assertLicensesValid({
packages: [PACKAGE],
validLicenses: [...PACKAGE.licenses]
})).to.be(undefined);
});

it('throw an error when the packages license is invalid', () => {
expect(() => {
assertLicensesValid({
packages: [PACKAGE],
validLicenses: [`not ${PACKAGE.licenses[0]}`]
});
}).to.throwError(PACKAGE.name);
});

it('throws an error when the package has no licenses', () => {
expect(() => {
assertLicensesValid({
packages: [
{
...PACKAGE,
licenses: []
}
],
validLicenses: [...PACKAGE.licenses]
});
}).to.throwError(PACKAGE.name);
});

it('includes the relative path to packages in error message', () => {
try {
assertLicensesValid({
packages: [PACKAGE],
validLicenses: ['none']
});
throw new Error('expected assertLicensesValid() to throw');
} catch (error) {
expect(error.message).to.contain(PACKAGE.relative);
expect(error.message).to.not.contain(PACKAGE.directory);
}
});
});
});
1 change: 1 addition & 0 deletions tasks/lib/licenses/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { assertLicensesValid } from './valid';
47 changes: 47 additions & 0 deletions tasks/lib/licenses/valid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const describeInvalidLicenses = getInvalid => pkg => (
`
${pkg.name}
version: ${pkg.version}
all licenses: ${pkg.licenses}
invalid licenses: ${getInvalid(pkg.licenses).join(', ')}
path: ${pkg.relative}
`
);

/**
* When given a list of packages and the valid license
* options, either throws an error with details about
* violations or returns undefined.
*
* @param {Object} [options={}]
* @property {Array<Package>} options.packages List of packages to check, see
* getInstalledPackages() in ../packages
* @property {Array<string>} options.validLicenses
* @return {undefined}
*/
export function assertLicensesValid(options = {}) {
const {
packages,
validLicenses
} = options;

if (!packages || !validLicenses) {
throw new Error('packages and validLicenses options are required');
}

const getInvalid = licenses => (
licenses.filter(license => !validLicenses.includes(license))
);

const isPackageInvalid = pkg => (
!pkg.licenses.length || getInvalid(pkg.licenses).length > 0
);

const invalidMsgs = packages
.filter(isPackageInvalid)
.map(describeInvalidLicenses(getInvalid));

if (invalidMsgs.length) {
throw new Error(`Non-confirming licenses: ${invalidMsgs.join('')}`);
}
}
55 changes: 55 additions & 0 deletions tasks/lib/notice/__tests__/notice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { resolve } from 'path';
import { readFileSync } from 'fs';

import expect from 'expect.js';

import { generateNoticeText } from '../notice';

const NODE_MODULES = resolve(__dirname, '../../../../node_modules');
const NODE_DIR = resolve(process.execPath, '../..');
const PACKAGES = [
{
name: '@elastic/httpolyglot',
version: '0.1.2-elasticpatch1',
licenses: ['MIT'],
directory: resolve(NODE_MODULES, '@elastic/httpolyglot'),
relative: 'node_modules/@elastic/httpolyglot',
},
{
name: 'aws-sdk',
version: '2.0.31',
licenses: ['Apache 2.0'],
directory: resolve(NODE_MODULES, 'aws-sdk'),
relative: 'node_modules/aws-sdk',
}
];

describe('tasks/lib/notice', () => {
describe('generateNoticeText()', () => {
let notice;
before(async () => notice = await generateNoticeText({
packages: PACKAGES,
nodeDir: NODE_DIR
}));

it('returns a string', () => {
expect(notice).to.be.a('string');
});

it('includes *NOTICE* files from packages', () => {
expect(notice).to.contain(readFileSync(resolve(NODE_MODULES, 'aws-sdk/NOTICE.txt'), 'utf8'));
});

it('includes *LICENSE* files from packages', () => {
expect(notice).to.contain(readFileSync(resolve(NODE_MODULES, '@elastic/httpolyglot/LICENSE'), 'utf8'));
});

it('includes the LICENSE file from node', () => {
expect(notice).to.contain(readFileSync(resolve(NODE_DIR, 'LICENSE'), 'utf8'));
});

it('includes the base_notice.txt file', () => {
expect(notice).to.contain(readFileSync(resolve(__dirname, '../base_notice.txt'), 'utf8'));
});
});
});
File renamed without changes.
14 changes: 14 additions & 0 deletions tasks/lib/notice/bundled_notices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { resolve } from 'path';
import { readFile } from 'fs';

import { fromNode as fcb } from 'bluebird';
import glob from 'glob';

export async function getBundledNotices(packageDirectory) {
const pattern = resolve(packageDirectory, '*{LICENSE,NOTICE}*');
const paths = await fcb(cb => glob(pattern, cb));
return Promise.all(paths.map(async path => ({
path,
text: await fcb(cb => readFile(path, 'utf8', cb))
})));
}
1 change: 1 addition & 0 deletions tasks/lib/notice/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { generateNoticeText } from './notice';
8 changes: 8 additions & 0 deletions tasks/lib/notice/node_notice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { resolve } from 'path';
import { readFileSync } from 'fs';

export function generateNodeNoticeText(nodeDir) {
const licensePath = resolve(nodeDir, 'LICENSE');
const license = readFileSync(licensePath, 'utf8');
return `This product bundles Node.js.\n\n${license}`;
}
28 changes: 28 additions & 0 deletions tasks/lib/notice/notice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { resolve } from 'path';
import { readFileSync } from 'fs';

import { generatePackagesNoticeText } from './packages_notice';
import { generateNodeNoticeText } from './node_notice';

const BASE_NOTICE = resolve(__dirname, './base_notice.txt');

/**
* When given a list of packages and the directory to the
* node distribution that will be shipping with Kibana,
* generates the text for NOTICE.txt
*
* @param {Object} [options={}]
* @property {Array<Package>} options.packages List of packages to check, see
* getInstalledPackages() in ../packages
* @property {string} options.nodeDir The directory containing the version of node.js
* that will ship with Kibana
* @return {undefined}
*/
export async function generateNoticeText(options = {}) {
const { packages, nodeDir } = options;
return [
readFileSync(BASE_NOTICE, 'utf8'),
await generatePackagesNoticeText(packages),
generateNodeNoticeText(nodeDir)
].join('');
}
28 changes: 28 additions & 0 deletions tasks/lib/notice/packages_notice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { map as asyncMap } from 'bluebird';

import { getBundledNotices } from './bundled_notices';

const concatNotices = notices => (
notices.map(notice => notice.text).join('\n')
);

export async function generatePackagesNoticeText(packages) {
const noticeChunks = await asyncMap(packages, async pkg => {
const bundledNotices = concatNotices(await getBundledNotices(pkg.directory));

const intro = `This product bundles ${pkg.name}@${pkg.version}`;
const license = ` which is available under ${
pkg.licenses.length > 1
? `the\n"${pkg.licenses.join('", ')} licenses.`
: `a\n"${pkg.licenses[0]}" license.`
}`;

const moreInfo = bundledNotices
? `\n${bundledNotices}\n`
: ` For details, see ${pkg.relative}/.`;

return `${intro}${license}${moreInfo}`;
});

return noticeChunks.join('\n---\n');
}
1 change: 1 addition & 0 deletions tasks/lib/packages/__tests__/fixtures/fixture1/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('I am fixture 1');
Loading