Skip to content

Commit

Permalink
feat(cli): add updateLicense to lb4 copyright command
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondfeng committed Apr 2, 2020
1 parent abc6111 commit 535df04
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 83 deletions.
46 changes: 46 additions & 0 deletions packages/cli/generators/copyright/fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/cli
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

const fse = require('fs-extra');
const _ = require('lodash');
const {promisify} = require('util');
const glob = promisify(require('glob'));

const defaultFS = {
write: fse.writeFile,
read: fse.readFile,
writeJSON: fse.writeJson,
readJSON: fse.readJson,
exists: fse.exists,
};

/**
* List all JS/TS files
* @param {string[]} paths - Paths to search
*/
async function jsOrTsFiles(cwd, paths = []) {
paths = [].concat(paths);
let globs;
if (paths.length === 0) {
globs = [glob('**/*.{js,ts}', {nodir: true, follow: false, cwd})];
} else {
globs = paths.map(p => {
if (/\/$/.test(p)) {
p += '**/*.{js,ts}';
} else if (!/[^*]\.(js|ts)$/.test(p)) {
p += '/**/*.{js,ts}';
}
return glob(p, {nodir: true, follow: false, cwd});
});
}
paths = await Promise.all(globs);
paths = _.flatten(paths);
return _.filter(paths, /\.(js|ts)$/);
}

exports.FSE = defaultFS;
exports.jsOrTsFiles = jsOrTsFiles;
35 changes: 32 additions & 3 deletions packages/cli/generators/copyright/git.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ const cp = require('child_process');
const util = require('util');
const debug = require('debug')('loopback:cli:copyright:git');

module.exports = git;

const cache = new Map();

/**
Expand All @@ -34,7 +32,8 @@ async function git(cwd, ...args) {
.filter()
.value();
if (err) {
reject(err);
// reject(err);
resolve([]);
} else {
cache.set(key, stdout);
debug('Stdout', stdout);
Expand All @@ -43,3 +42,33 @@ async function git(cwd, ...args) {
});
});
}

/**
* Inspect years for a given file based on git history
* @param {string} file - JS/TS file
*/
async function getYears(file) {
file = file || '.';
let dates = await git(
process.cwd(),
'--no-pager log --pretty=%%ai --all -- %s',
file,
);
debug('Dates for %s', file, dates);
if (_.isEmpty(dates)) {
// if the given path doesn't have any git history, assume it is new
dates = [new Date().toJSON()];
} else {
dates = [_.head(dates), _.last(dates)];
}
const years = _.map(dates, getYear);
return _.uniq(years).sort();
}

// assumes ISO-8601 (or similar) format
function getYear(str) {
return str.slice(0, 4);
}

exports.git = git;
exports.getYears = getYears;
100 changes: 25 additions & 75 deletions packages/cli/generators/copyright/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
// License text available at https://opensource.org/licenses/MIT

const _ = require('lodash');
const git = require('./git');
const {git, getYears} = require('./git');
const path = require('path');
const fs = require('fs-extra');
const {FSE, jsOrTsFiles} = require('./fs');
const chalk = require('chalk');
const Project = require('@lerna/project');

const {promisify} = require('util');
const glob = promisify(require('glob'));
const {spdxLicenseList} = require('./license');

const debug = require('debug')('loopback:cli:copyright');

Expand Down Expand Up @@ -40,57 +39,19 @@ const ANY = COPYRIGHT.concat(LICENSE, CUSTOM_LICENSE).map(
l => new RegExp(l.replace(/<%[^>]+%>/g, '.*')),
);

const spdxLicenses = require('spdx-license-list');
const spdxLicenseList = {};
for (const id in spdxLicenses) {
spdxLicenseList[id.toLowerCase()] = {id, ...spdxLicenses[id]};
}

/**
* Inspect years for a given file based on git history
* @param {string} file - JS/TS file
*/
async function copyYears(file) {
file = file || '.';
let dates = await git(
process.cwd(),
'--no-pager log --pretty=%%ai --all -- %s',
file,
);
debug('Dates for %s', file, dates);
if (_.isEmpty(dates)) {
// if the given path doesn't have any git history, assume it is new
dates = [new Date().toJSON()];
} else {
dates = [_.head(dates), _.last(dates)];
}
const years = _.map(dates, getYear);
return _.uniq(years).sort();
}

// assumes ISO-8601 (or similar) format
function getYear(str) {
return str.slice(0, 4);
}

/**
* Copy header for a file
* Build header for a file
* @param {string} file - JS/TS file
* @param {object} pkg - Package json object
* @param {object} options - Options
*/
async function copyHeader(file, pkg, options) {
async function buildHeader(file, pkg, options) {
const license =
options.license || _.get(pkg, 'license') || options.defaultLicense;
const years = await copyYears(file);
const years = await getYears(file);
const params = expandLicense(license);
params.years = years.join(',');
const owner =
options.copyrightOwner ||
_.get(pkg, 'copyright.owner') ||
_.get(pkg, 'author.name') ||
options.defaultCopyrightOwner ||
'Owner';
const owner = getCopyrightOwner(pkg, options);

const name =
options.copyrightIdentifer ||
Expand All @@ -109,6 +70,16 @@ async function copyHeader(file, pkg, options) {
return params.template(params);
}

function getCopyrightOwner(pkg, options) {
return (
options.copyrightOwner ||
_.get(pkg, 'copyright.owner') ||
_.get(pkg, 'author.name') ||
options.defaultCopyrightOwner ||
'Owner'
);
}

/**
* Build the license template params
* @param {string|object} spdxLicense - SPDX license id or object
Expand Down Expand Up @@ -137,7 +108,7 @@ function expandLicense(spdxLicense) {
* @param {object} options - Options
*/
async function formatHeader(file, pkg, options) {
const header = await copyHeader(file, pkg, options);
const header = await buildHeader(file, pkg, options);
return header.split('\n').map(line => `// ${line}`);
}

Expand All @@ -148,12 +119,13 @@ async function formatHeader(file, pkg, options) {
* @param {object} options - Options
*/
async function ensureHeader(file, pkg, options = {}) {
const fs = options.fs || FSE;
const header = await formatHeader(file, pkg, options);
debug('Header: %s', header);
const current = await fs.readFile(file, 'utf8');
const current = await fs.read(file, 'utf8');
const content = mergeHeaderWithContent(header, current);
if (!options.dryRun) {
await fs.writeFile(file, content, 'utf8');
await fs.write(file, content, 'utf8');
} else {
const log = options.log || console.log;
log(file, header);
Expand Down Expand Up @@ -185,30 +157,6 @@ function headerOrBlankLine(line) {
return BLANK.test(line) || ANY.some(pat => pat.test(line));
}

/**
* List all JS/TS files
* @param {string[]} paths - Paths to search
*/
async function jsOrTsFiles(cwd, paths = []) {
paths = [].concat(paths);
let globs;
if (paths.length === 0) {
globs = [glob('**/*.{js,ts}', {nodir: true, follow: false, cwd})];
} else {
globs = paths.map(p => {
if (/\/$/.test(p)) {
p += '**/*.{js,ts}';
} else if (!/[^*]\.(js|ts)$/.test(p)) {
p += '/**/*.{js,ts}';
}
return glob(p, {nodir: true, follow: false, cwd});
});
}
paths = await Promise.all(globs);
paths = _.flatten(paths);
return _.filter(paths, /\.(js|ts)$/);
}

/**
* Update file headers for the given project
* @param {string} projectRoot - Root directory of a package or a lerna monorepo
Expand All @@ -222,6 +170,7 @@ async function updateFileHeaders(projectRoot, options = {}) {
...options,
};

const fs = options.fs || FSE;
const isMonorepo = await fs.exists(path.join(projectRoot, 'lerna.json'));
if (isMonorepo) {
// List all packages for the monorepo
Expand Down Expand Up @@ -275,12 +224,13 @@ async function updateFileHeadersForSinglePackage(projectRoot, options) {
debug('Project root: %s', projectRoot);
const log = options.log || console.log;
const pkgFile = path.join(projectRoot, 'package.json');
const fs = options.fs || FSE;
const exists = await fs.exists(pkgFile);
if (!exists) {
log(chalk.red(`No package.json exists at ${projectRoot}`));
return;
}
const pkg = await fs.readJson(pkgFile);
const pkg = await fs.readJSON(pkgFile);

log(
'Updating project %s (%s)',
Expand All @@ -301,7 +251,7 @@ async function updateFileHeadersForSinglePackage(projectRoot, options) {
}

exports.updateFileHeaders = updateFileHeaders;
exports.spdxLicenseList = spdxLicenseList;
exports.getYears = getYears;

if (require.main === module) {
updateFileHeaders(process.cwd()).catch(err => {
Expand Down
32 changes: 31 additions & 1 deletion packages/cli/generators/copyright/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

'use strict';
const BaseGenerator = require('../../lib/base-generator');
const {updateFileHeaders, spdxLicenseList} = require('./header');
const {updateFileHeaders} = require('./header');
const {spdxLicenseList, updateLicense} = require('./license');
const g = require('../../lib/globalize');
const _ = require('lodash');
const autocomplete = require('inquirer-autocomplete-prompt');
Expand Down Expand Up @@ -50,6 +51,11 @@ module.exports = class CopyrightGenerator extends BaseGenerator {
required: false,
description: g.f('License'),
});
this.option('updateLicense', {
type: Boolean,
required: false,
description: g.f('Update license in package.json and LICENSE'),
});
this.option('gitOnly', {
type: Boolean,
required: false,
Expand All @@ -66,6 +72,7 @@ module.exports = class CopyrightGenerator extends BaseGenerator {
this.exit(`${pkgFile} does not exist.`);
return;
}
this.packageJson = pkg;
let author = _.get(pkg, 'author');
if (typeof author === 'object') {
author = author.name;
Expand Down Expand Up @@ -120,9 +127,32 @@ module.exports = class CopyrightGenerator extends BaseGenerator {
license: answers.license || this.options.license,
log: this.log,
gitOnly: this.options.gitOnly,
fs: this.fs,
};
}

async updateLicense() {
if (this.shouldExit()) return;
const answers = await this.prompt([
{
type: 'confirm',
name: 'updateLicense',
message: g.f('Do you want to update package.json and LICENSE?'),
default: false,
when: this.options.updateLicense == null,
},
]);
const updateLicenseFile =
(answers && answers.updateLicense) || this.options.updateLicense;
if (!updateLicenseFile) return;
this.headerOptions.updateLicense = updateLicenseFile;
await updateLicense(
this.destinationRoot(),
this.packageJson,
this.headerOptions,
);
}

async updateHeaders() {
if (this.shouldExit()) return;
await updateFileHeaders(this.destinationRoot(), this.headerOptions);
Expand Down
Loading

0 comments on commit 535df04

Please sign in to comment.