From ed29d5104b9f5a230165b63333e6db71beca3a7c Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Sun, 29 Mar 2020 12:47:45 -0700 Subject: [PATCH] feat(cli): add command to generate/update file headers --- packages/cli/generators/copyright/git.js | 45 ++ packages/cli/generators/copyright/header.js | 287 ++++++++++++ packages/cli/generators/copyright/index.js | 78 ++++ packages/cli/lib/cli.js | 4 + packages/cli/package-lock.json | 420 ++++++++++++++++++ packages/cli/package.json | 3 +- .../cli/cli.integration.snapshots.js | 2 + 7 files changed, 838 insertions(+), 1 deletion(-) create mode 100644 packages/cli/generators/copyright/git.js create mode 100644 packages/cli/generators/copyright/header.js create mode 100644 packages/cli/generators/copyright/index.js diff --git a/packages/cli/generators/copyright/git.js b/packages/cli/generators/copyright/git.js new file mode 100644 index 000000000000..ef10681e6978 --- /dev/null +++ b/packages/cli/generators/copyright/git.js @@ -0,0 +1,45 @@ +// 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 _ = require('lodash'); +const cp = require('child_process'); +const util = require('util'); +const debug = require('debug')('loopback:cli:copyright:git'); + +module.exports = git; + +const cache = new Map(); + +/** + * Run a git command + * @param {string} cwd - Current directory to run the command + * @param {...any} args - Args for the git command + */ +async function git(cwd, ...args) { + const cmd = 'git ' + util.format(...args); + const key = `${cwd}:${cmd}`; + debug('Running %s', cmd); + if (cache.has(key)) { + return cache.get(key); + } + return new Promise((resolve, reject) => { + cp.exec(cmd, {maxBuffer: 1024 * 1024, cwd}, (err, stdout) => { + stdout = _(stdout || '') + .split(/[\r\n]+/g) + .map(_.trim) + .filter() + .value(); + if (err) { + reject(err); + } else { + cache.set(key, stdout); + debug('Stdout', stdout); + resolve(stdout); + } + }); + }); +} diff --git a/packages/cli/generators/copyright/header.js b/packages/cli/generators/copyright/header.js new file mode 100644 index 000000000000..cc3fd049ce8b --- /dev/null +++ b/packages/cli/generators/copyright/header.js @@ -0,0 +1,287 @@ +// 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 + +const _ = require('lodash'); +const git = require('./git'); +const path = require('path'); +const fs = require('fs-extra'); +const Project = require('@lerna/project'); + +const {promisify} = require('util'); +const glob = promisify(require('glob')); + +const debug = require('debug')('loopback:cli:copyright'); + +// Components of the copyright header. +const COPYRIGHT = [ + 'Copyright <%= owner %> <%= years %>. All Rights Reserved.', + 'Node module: <%= name %>', +]; +const LICENSE = [ + 'This file is licensed under the <%= license %>.', + 'License text available <%= ref %>', +]; +const CUSTOM_LICENSE = []; + +// Compiled templates for generating copyright headers +const UNLICENSED = _.template(COPYRIGHT.join('\n')); +const LICENSED = _.template(COPYRIGHT.concat(LICENSE).join('\n')); +let CUSTOM = UNLICENSED; +if (CUSTOM_LICENSE.length) { + CUSTOM = _.template(COPYRIGHT.concat(CUSTOM_LICENSE).join('\n')); +} + +// Patterns for matching previously generated copyright headers +const BLANK = /^\s*$/; +const ANY = COPYRIGHT.concat(LICENSE, CUSTOM_LICENSE).map( + l => new RegExp(l.replace(/<%[^>]+%>/g, '.*')), +); + +/** + * 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 + * @param {string} file - JS/TS file + * @param {object} pkg - Package json object + * @param {object} options - Options + */ +async function copyHeader(file, pkg, options) { + const license = options.license || _.get(pkg, 'license'); + const years = await copyYears(file); + const params = expandLicense(license); + params.years = years.join(','); + const owner = + options.copyrightOwner || + _.get(pkg, 'copyright.owner') || + _.get(pkg, 'author.name', 'Owner'); + + const name = + options.copyrightIdentifer || + _.get(pkg, 'copyright.identifier', _.get(pkg, 'name')); + + _.defaults(params, { + owner, + name, + license, + }); + debug('Params', params); + return params.template(params); +} + +function expandLicense(name) { + if (/^apache/i.test(name)) { + return { + template: LICENSED, + license: 'Apache License 2.0', + ref: 'at https://opensource.org/licenses/Apache-2.0', + }; + } + if (/^artistic/i.test(name)) { + return { + template: LICENSED, + license: 'Artistic License 2.0', + ref: 'at https://opensource.org/licenses/Artistic-2.0', + }; + } + if (/^mit$/i.test(name)) { + return { + template: LICENSED, + license: 'MIT License', + ref: 'at https://opensource.org/licenses/MIT', + }; + } + if (/^isc$/i.test(name)) { + return { + template: LICENSED, + license: 'ISC License (ISC)', + ref: 'at https://opensource.org/licenses/ISC', + }; + } + return { + template: CUSTOM, + license: name, + }; +} + +/** + * Format the header for a file + * @param {string} file - JS/TS file + * @param {string} pkg - Package json object + * @param {object} options - Options + */ +async function formatHeader(file, pkg, options) { + const header = await copyHeader(file, pkg, options); + return header.split('\n').map(line => `// ${line}`); +} + +/** + * Ensure the file is updated with the correct header + * @param {string} file - JS/TS file + * @param {object} pkg - Package json object + * @param {object} options - Options + */ +async function ensureHeader(file, pkg, options = {}) { + const header = await formatHeader(file, pkg, options); + debug('Header: %s', header); + const current = await fs.readFile(file, 'utf8'); + const content = mergeHeaderWithContent(header, current); + if (!options.dryRun) { + await fs.writeFile(file, content, 'utf8'); + } else { + console.log(file, header); + } + return content; +} + +/** + * Merge header with file content + * @param {string} header - Copyright header + * @param {string} content - File content + */ +function mergeHeaderWithContent(header, content) { + const lineEnding = /\r\n/gm.test(content) ? '\r\n' : '\n'; + const preamble = []; + content = content.split(lineEnding); + if (/^#!/.test(content[0])) { + preamble.push(content.shift()); + } + // replace any existing copyright header lines and collapse blank lines down + // to just one. + while (headerOrBlankLine(content[0])) { + content.shift(); + } + return [].concat(preamble, header, '', content).join(lineEnding); +} + +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(paths = []) { + paths = [].concat(paths); + const 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}); + }); + 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 + * @param {object} options - Options + */ +async function updateFileHeaders(projectRoot, options = {}) { + options = { + dryRun: false, + gitOnly: true, + ...options, + }; + + const isMonorepo = await fs.exists(path.join(projectRoot, 'lerna.json')); + if (isMonorepo) { + // List all packages for the monorepo + const project = new Project(projectRoot); + debug('Lerna monorepo', project); + const packages = await project.getPackages(); + + // Update file headers for each package + const visited = []; + for (const p of packages) { + visited.push(p.location); + await updateFileHeaders(p.location, options); + } + + // Now handle the root level package + // Exclude files that have been processed + const filter = f => + !visited.some(dir => { + dir = path.relative(projectRoot, dir); + return f.startsWith(path.join(dir, '/')); + }); + await updateFileHeadersForSinglePackage(projectRoot, {filter, ...options}); + } else { + await updateFileHeadersForSinglePackage(projectRoot, options); + } +} + +/** + * Update file headers for the given project + * @param {string} projectRoot - Root directory of a package + * @param {object} options - Options + */ +async function updateFileHeadersForSinglePackage(projectRoot, options) { + debug('Options', options); + debug('Project root: %s', projectRoot); + const pkgFile = path.join(projectRoot, 'package.json'); + const exists = await fs.exists(pkgFile); + if (!exists) { + console.error('No package.json exists at %s.', projectRoot); + return; + } + const pkg = await fs.readJson(pkgFile); + console.log( + 'Updating project %s (%s)', + pkg.name, + path.relative(process.cwd(), projectRoot) || '.', + ); + debug('Package', pkg); + let files = options.gitOnly + ? await git(projectRoot, 'ls-files') + : [projectRoot]; + debug('Paths', files); + if (typeof options.filter === 'function') { + files = files.filter(options.filter); + } + files = await jsOrTsFiles(files); + debug('JS/TS files', files); + for (const file of files) { + await ensureHeader(file, pkg, options); + } +} + +exports.updateFileHeaders = updateFileHeaders; + +if (require.main === module) { + updateFileHeaders(process.cwd()).catch(err => { + console.error(err); + process.exit(1); + }); +} diff --git a/packages/cli/generators/copyright/index.js b/packages/cli/generators/copyright/index.js new file mode 100644 index 000000000000..eac5fe78a8eb --- /dev/null +++ b/packages/cli/generators/copyright/index.js @@ -0,0 +1,78 @@ +// Copyright IBM Corp. 2017,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 BaseGenerator = require('../../lib/base-generator'); +const {updateFileHeaders} = require('./header'); +const g = require('../../lib/globalize'); +const _ = require('lodash'); + +module.exports = class CopyrightGenerator extends BaseGenerator { + // Note: arguments and options should be defined in the constructor. + constructor(args, opts) { + super(args, opts); + } + + setOptions() { + this.option('owner', { + type: String, + required: false, + description: g.f('Copyright owner'), + }); + this.option('license', { + type: String, + required: false, + description: g.f('License'), + }); + return super.setOptions(); + } + + async promptOwnerAndLicense() { + const pkgFile = this.destinationPath('package.json'); + const pkg = this.fs.readJSON(this.destinationPath('package.json')); + if (pkg == null) { + this.exit(`${pkgFile} does not exist.`); + return; + } + let author = _.get(pkg, 'author'); + if (typeof author === 'object') { + author = author.name; + } + const owner = + this.options.copyrightOwner || _.get(pkg, 'copyright.owner', author); + const license = this.options.license || _.get(pkg, 'license', 'MIT'); + + let answers = await this.prompt([ + { + type: 'input', + name: 'owner', + message: g.f('Copyright owner:'), + default: owner, + when: this.options.owner == null, + }, + { + type: 'input', + name: 'license', + message: g.f('License name:'), + default: license, + when: this.options.license == null, + }, + ]); + answers = answers || {}; + this.headerOptions = { + copyrightOwner: answers.owner || this.options.owner, + license: answers.license || this.options.license, + }; + } + + async updateHeaders() { + if (this.shouldExit()) return; + await updateFileHeaders(this.destinationRoot(), this.headerOptions); + } + + async end() { + await super.end(); + } +}; diff --git a/packages/cli/lib/cli.js b/packages/cli/lib/cli.js index 60879b3e9dd1..0d18f626df6a 100644 --- a/packages/cli/lib/cli.js +++ b/packages/cli/lib/cli.js @@ -109,6 +109,10 @@ function setupGenerators() { path.join(__dirname, '../generators/rest-crud'), PREFIX + 'rest-crud', ); + env.register( + path.join(__dirname, '../generators/copyright'), + PREFIX + 'copyright', + ); return env; } diff --git a/packages/cli/package-lock.json b/packages/cli/package-lock.json index 6106fa4d4244..0252477af125 100644 --- a/packages/cli/package-lock.json +++ b/packages/cli/package-lock.json @@ -153,6 +153,81 @@ "integrity": "sha1-hJAPDu/DcnmPR1G1JigwuCCJIuw=", "dev": true }, + "@lerna/package": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/package/-/package-3.16.0.tgz", + "integrity": "sha512-2lHBWpaxcBoiNVbtyLtPUuTYEaB/Z+eEqRS9duxpZs6D+mTTZMNy6/5vpEVSCBmzvdYpyqhqaYjjSLvjjr5Riw==", + "requires": { + "load-json-file": "^5.3.0", + "npm-package-arg": "^6.1.0", + "write-pkg": "^3.1.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "@lerna/project": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/@lerna/project/-/project-3.18.0.tgz", + "integrity": "sha512-+LDwvdAp0BurOAWmeHE3uuticsq9hNxBI0+FMHiIai8jrygpJGahaQrBYWpwbshbQyVLeQgx3+YJdW2TbEdFWA==", + "requires": { + "@lerna/package": "3.16.0", + "@lerna/validation-error": "3.13.0", + "cosmiconfig": "^5.1.0", + "dedent": "^0.7.0", + "dot-prop": "^4.2.0", + "glob-parent": "^5.0.0", + "globby": "^9.2.0", + "load-json-file": "^5.3.0", + "npmlog": "^4.1.2", + "p-map": "^2.1.0", + "resolve-from": "^4.0.0", + "write-json-file": "^3.2.0" + }, + "dependencies": { + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "^1.0.0" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" + } + } + }, + "@lerna/validation-error": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-3.13.0.tgz", + "integrity": "sha512-SiJP75nwB8GhgwLKQfdkSnDufAaCbkZWJqEDlKOUPUvVOplRGnfL+BPQZH5nvq2BYSRXsksXWZ4UHVnQZI/HYA==", + "requires": { + "npmlog": "^4.1.2" + } + }, "@livereach/jsonpath": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@livereach/jsonpath/-/jsonpath-1.2.2.tgz", @@ -599,6 +674,15 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1089,6 +1173,27 @@ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + }, "camel-case": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz", @@ -1406,6 +1511,11 @@ "xdg-basedir": "^4.0.0" } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "constant-case": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.3.tgz", @@ -1489,6 +1599,28 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", @@ -1594,6 +1726,11 @@ "mimic-response": "^1.0.0" } }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -1676,6 +1813,11 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -1687,6 +1829,11 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, + "detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=" + }, "dezalgo": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", @@ -2472,6 +2619,54 @@ "@livereach/jsonpath": "^1.2.2" } }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -2856,6 +3051,11 @@ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -3075,6 +3275,22 @@ "minimatch": "^3.0.4" } }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -3259,6 +3475,11 @@ } } }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -3668,6 +3889,39 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, + "load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", + "requires": { + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==" + } + } + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -4992,6 +5246,17 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -5054,6 +5319,11 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -5206,6 +5476,11 @@ "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", "dev": true }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, "os-locale": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-4.0.0.tgz", @@ -5221,6 +5496,15 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -5806,6 +6090,11 @@ "path-parse": "^1.0.6" } }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -6315,6 +6604,14 @@ "socks": "^2.3.3" } }, + "sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", + "requires": { + "is-plain-obj": "^1.0.0" + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -7346,6 +7643,43 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", @@ -7440,6 +7774,92 @@ "typedarray-to-buffer": "^3.1.5" } }, + "write-json-file": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-3.2.0.tgz", + "integrity": "sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==", + "requires": { + "detect-indent": "^5.0.0", + "graceful-fs": "^4.1.15", + "make-dir": "^2.1.0", + "pify": "^4.0.1", + "sort-keys": "^2.0.0", + "write-file-atomic": "^2.4.2" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } + } + }, + "write-pkg": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-3.2.0.tgz", + "integrity": "sha512-tX2ifZ0YqEFOF1wjRW2Pk93NLsj02+n1UP5RvO6rCs0K6R2g1padvf006cY74PQJKMGS2r42NK7FD0dG6Y6paw==", + "requires": { + "sort-keys": "^2.0.0", + "write-json-file": "^2.2.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "write-json-file": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz", + "integrity": "sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=", + "requires": { + "detect-indent": "^5.0.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "pify": "^3.0.0", + "sort-keys": "^2.0.0", + "write-file-atomic": "^2.0.0" + } + } + } + }, "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", diff --git a/packages/cli/package.json b/packages/cli/package.json index 6ce39aed347a..c82ed6d98098 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -28,7 +28,6 @@ "@loopback/testlab": "^2.0.2", "@types/ejs": "^3.0.1", "@types/node": "^10.17.17", - "glob": "^7.1.6", "loopback": "^3.27.0", "loopback-datasource-juggler": "^4.19.2", "mem-fs": "^1.1.3", @@ -42,12 +41,14 @@ "yeoman-test": "~2.2.0" }, "dependencies": { + "@lerna/project": "^3.18.0", "@phenomnomnominal/tsquery": "^4.0.0", "camelcase-keys": "^6.2.1", "chalk": "^3.0.0", "change-case": "^4.1.1", "debug": "^4.1.1", "fs-extra": "^9.0.0", + "glob": "^7.1.6", "inquirer": "~7.0.7", "json5": "^2.1.2", "latest-version": "^5.1.0", diff --git a/packages/cli/snapshots/integration/cli/cli.integration.snapshots.js b/packages/cli/snapshots/integration/cli/cli.integration.snapshots.js index ff00cc442e51..98c55ab91318 100644 --- a/packages/cli/snapshots/integration/cli/cli.integration.snapshots.js +++ b/packages/cli/snapshots/integration/cli/cli.integration.snapshots.js @@ -25,6 +25,7 @@ Available commands: lb4 relation lb4 update lb4 rest-crud + lb4 copyright `; @@ -46,4 +47,5 @@ Available commands: lb4 relation lb4 update lb4 rest-crud + lb4 copyright `;