diff --git a/cli.js b/cli.js index 9040236228..7ea434f3d5 100755 --- a/cli.js +++ b/cli.js @@ -1,6 +1,7 @@ -const {argv, env, stderr} = require('process'); // eslint-disable-line node/prefer-global/process -const util = require('util'); -const hideSensitive = require('./lib/hide-sensitive'); +import util from 'node:util'; +import yargs from 'yargs'; +import {hideBin} from 'yargs/helpers'; +import hideSensitive from './lib/hide-sensitive.js'; const stringList = { type: 'string', @@ -11,8 +12,8 @@ const stringList = { : values.reduce((values, value) => values.concat(value.split(',').map((value) => value.trim())), []), }; -module.exports = async () => { - const cli = require('yargs') +export default async () => { + const cli = yargs(hideBin(process.argv)) .command('$0', 'Run automated package publishing', (yargs) => { yargs.demandCommand(0, 0).usage(`Run automated package publishing @@ -36,12 +37,11 @@ Usage: .option('debug', {describe: 'Output debugging information', type: 'boolean', group: 'Options'}) .option('d', {alias: 'dry-run', describe: 'Skip publishing', type: 'boolean', group: 'Options'}) .option('h', {alias: 'help', group: 'Options'}) - .option('v', {alias: 'version', group: 'Options'}) .strict(false) .exitProcess(false); try { - const {help, version, ...options} = cli.parse(argv.slice(2)); + const {help, version, ...options} = cli.parse(process.argv.slice(2)); if (Boolean(help) || Boolean(version)) { return 0; @@ -49,16 +49,16 @@ Usage: if (options.debug) { // Debug must be enabled before other requires in order to work - require('debug').enable('semantic-release:*'); + (await import('debug')).enable('semantic-release:*'); } - await require('.')(options); + await (await import('./index.js')).default(options); return 0; } catch (error) { if (error.name !== 'YError') { - stderr.write(hideSensitive(env)(util.inspect(error, {colors: true}))); + process.stderr.write(hideSensitive(process.env)(util.inspect(error, {colors: true}))); } return 1; } -}; +} diff --git a/package-lock.json b/package-lock.json index dcd093d684..8bad75cee8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "semver": "^7.3.2", "semver-diff": "^3.1.1", "signale": "^1.2.1", - "yargs": "^16.2.0" + "yargs": "^17.5.1" }, "bin": { "semantic-release": "bin/semantic-release.js" @@ -2187,6 +2187,24 @@ "node": ">=6" } }, + "node_modules/ava/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -2749,6 +2767,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/c8/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -13524,6 +13560,24 @@ "node": ">=16 || ^14.17" } }, + "node_modules/semantic-release/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "peer": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -16411,20 +16465,20 @@ } }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-parser": { @@ -16435,6 +16489,14 @@ "node": ">=10" } }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -18182,6 +18244,21 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } } } }, @@ -18616,6 +18693,21 @@ "requires": { "p-limit": "^3.0.2" } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } } } }, @@ -26720,6 +26812,23 @@ "semver-diff": "^3.1.1", "signale": "^1.2.1", "yargs": "^16.2.0" + }, + "dependencies": { + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "peer": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + } } }, "semver": { @@ -29042,17 +29151,24 @@ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.0.0" + }, + "dependencies": { + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } } }, "yargs-parser": { diff --git a/package.json b/package.json index 2e52229f38..5f34139a26 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "semver": "^7.3.2", "semver-diff": "^3.1.1", "signale": "^1.2.1", - "yargs": "^16.2.0" + "yargs": "^17.5.1" }, "devDependencies": { "ava": "3.15.0", diff --git a/test/cli.test.js b/test/cli.test.js index 9807fd5a67..77a6fd5a56 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -30,7 +30,6 @@ test.afterEach.always((t) => { }); test.serial('Pass options to semantic-release API', async (t) => { - const run = stub().resolves(true); const argv = [ '', '', @@ -72,33 +71,49 @@ test.serial('Pass options to semantic-release API', async (t) => { '--debug', '-d', ]; - td.replace('..', run); + const index = await td.replaceEsm('../index.js'); process.argv = argv; - const cli = require('../cli'); + const cli = (await import('../cli.js')).default; const exitCode = await cli(); - t.deepEqual(run.args[0][0].branches, ['master', 'next']); - t.is(run.args[0][0].repositoryUrl, 'https://github/com/owner/repo.git'); - t.is(run.args[0][0].tagFormat, `v\${version}`); - t.deepEqual(run.args[0][0].plugins, ['plugin1', 'plugin2']); - t.deepEqual(run.args[0][0].extends, ['config1', 'config2']); - t.deepEqual(run.args[0][0].verifyConditions, ['condition1', 'condition2']); - t.is(run.args[0][0].analyzeCommits, 'analyze'); - t.deepEqual(run.args[0][0].verifyRelease, ['verify1', 'verify2']); - t.deepEqual(run.args[0][0].generateNotes, ['notes']); - t.deepEqual(run.args[0][0].prepare, ['prepare1', 'prepare2']); - t.deepEqual(run.args[0][0].publish, ['publish1', 'publish2']); - t.deepEqual(run.args[0][0].success, ['success1', 'success2']); - t.deepEqual(run.args[0][0].fail, ['fail1', 'fail2']); - t.is(run.args[0][0].debug, true); - t.is(run.args[0][0].dryRun, true); + td.verify(index.default({ + branches: ['master', 'next'], + b: ['master', 'next'], + 'repository-url': 'https://github/com/owner/repo.git', + repositoryUrl: 'https://github/com/owner/repo.git', + r: 'https://github/com/owner/repo.git', + 'tag-format': `v\${version}`, + tagFormat: `v\${version}`, + t: `v\${version}`, + plugins: ['plugin1', 'plugin2'], + p: ['plugin1', 'plugin2'], + extends: ['config1', 'config2'], + e: ['config1', 'config2'], + 'dry-run': true, + dryRun: true, + d: true, + verifyConditions: ['condition1', 'condition2'], + 'verify-conditions': ['condition1', 'condition2'], + analyzeCommits: 'analyze', + 'analyze-commits': 'analyze', + verifyRelease: ['verify1', 'verify2'], + 'verify-release': ['verify1', 'verify2'], + generateNotes: 'notes', + 'generate-notes': 'notes', + prepare: ['prepare1', 'prepare2'], + publish: ['publish1', 'publish2'], + success: ['success1', 'success2'], + fail: ['fail1', 'fail2'], + debug: true, + _: [], + '$0': '' + })); t.is(exitCode, 0); }); test.serial('Pass options to semantic-release API with alias arguments', async (t) => { - const run = stub().resolves(true); const argv = [ '', '', @@ -116,48 +131,65 @@ test.serial('Pass options to semantic-release API with alias arguments', async ( 'config2', '--dry-run', ]; - td.replace('..', run); + const index = await td.replaceEsm('../index.js'); process.argv = argv; - const cli = require('../cli'); + const cli = (await import('../cli.js')).default; const exitCode = await cli(); - t.deepEqual(run.args[0][0].branches, ['master']); - t.is(run.args[0][0].repositoryUrl, 'https://github/com/owner/repo.git'); - t.is(run.args[0][0].tagFormat, `v\${version}`); - t.deepEqual(run.args[0][0].plugins, ['plugin1', 'plugin2']); - t.deepEqual(run.args[0][0].extends, ['config1', 'config2']); - t.is(run.args[0][0].dryRun, true); + td.verify(index.default({ + branches: ['master'], + b: ['master'], + 'repository-url': 'https://github/com/owner/repo.git', + repositoryUrl: 'https://github/com/owner/repo.git', + r: 'https://github/com/owner/repo.git', + 'tag-format': `v\${version}`, + tagFormat: `v\${version}`, + t: `v\${version}`, + plugins: ['plugin1', 'plugin2'], + p: ['plugin1', 'plugin2'], + extends: ['config1', 'config2'], + e: ['config1', 'config2'], + 'dry-run': true, + dryRun: true, + d: true, + _: [], + '$0': '' + })); t.is(exitCode, 0); }); test.serial('Pass unknown options to semantic-release API', async (t) => { - const run = stub().resolves(true); const argv = ['', '', '--bool', '--first-option', 'value1', '--second-option', 'value2', '--second-option', 'value3']; - td.replace('..', run); + const index = await td.replaceEsm('../index.js'); process.argv = argv; - const cli = require('../cli'); + const cli = (await import('../cli.js')).default; const exitCode = await cli(); - t.is(run.args[0][0].bool, true); - t.is(run.args[0][0].firstOption, 'value1'); - t.deepEqual(run.args[0][0].secondOption, ['value2', 'value3']); + td.verify(index.default({ + bool: true, + firstOption: 'value1', + 'first-option': 'value1', + secondOption: ['value2', 'value3'], + 'second-option': ['value2', 'value3'], + _: [], + '$0': '' + })); t.is(exitCode, 0); }); test.serial('Pass empty Array to semantic-release API for list option set to "false"', async (t) => { - const run = stub().resolves(true); const argv = ['', '', '--publish', 'false']; - td.replace('..', run); + const index = await td.replaceEsm('../index.js'); process.argv = argv; - const cli = require('../cli'); + const cli = (await import('../cli.js')).default; const exitCode = await cli(); - t.deepEqual(run.args[0][0].publish, []); + td.verify(index.default({publish: [], _: [], '$0': ''})); t.is(exitCode, 0); }); @@ -165,9 +197,9 @@ test.serial('Pass empty Array to semantic-release API for list option set to "fa test.serial('Do not set properties in option for which arg is not in command line', async (t) => { const run = stub().resolves(true); const argv = ['', '', '-b', 'master']; - td.replace('..', run); + await td.replaceEsm('../index.js', null, run); process.argv = argv; - const cli = require('../cli'); + const cli = (await import('../cli.js')).default; await cli(); @@ -184,9 +216,9 @@ test.serial('Do not set properties in option for which arg is not in command lin test.serial('Display help', async (t) => { const run = stub().resolves(true); const argv = ['', '', '--help']; - td.replace('..', run); + await td.replaceEsm('../index.js', null, run); process.argv = argv; - const cli = require('../cli'); + const cli = (await import('../cli.js')).default; const exitCode = await cli(); @@ -197,9 +229,9 @@ test.serial('Display help', async (t) => { test.serial('Return error exitCode and prints help if called with a command', async (t) => { const run = stub().resolves(true); const argv = ['', '', 'pre']; - td.replace('..', run); + await td.replaceEsm('../index.js', null, run); process.argv = argv; - const cli = require('../cli'); + const cli = (await import('../cli.js')).default; const exitCode = await cli(); @@ -211,9 +243,9 @@ test.serial('Return error exitCode and prints help if called with a command', as test.serial('Return error exitCode if multiple plugin are set for single plugin', async (t) => { const run = stub().resolves(true); const argv = ['', '', '--analyze-commits', 'analyze1', 'analyze2']; - td.replace('..', run); + await td.replaceEsm('../index.js', null, run); process.argv = argv; - const cli = require('../cli'); + const cli = (await import('../cli.js')).default; const exitCode = await cli(); @@ -223,11 +255,11 @@ test.serial('Return error exitCode if multiple plugin are set for single plugin' }); test.serial('Return error exitCode if semantic-release throw error', async (t) => { - const run = stub().rejects(new Error('semantic-release error')); const argv = ['', '']; - td.replace('..', run); + const index = await td.replaceEsm('../index.js'); + td.when(index.default()).thenReject(new Error('semantic-release error')); process.argv = argv; - const cli = require('../cli'); + const cli = (await import('../cli.js')).default; const exitCode = await cli(); @@ -237,12 +269,12 @@ test.serial('Return error exitCode if semantic-release throw error', async (t) = test.serial('Hide sensitive environment variable values from the logs', async (t) => { const env = {MY_TOKEN: 'secret token'}; - const run = stub().rejects(new Error(`Throw error: Exposing token ${env.MY_TOKEN}`)); const argv = ['', '']; - td.replace('..', run); + const index = await td.replaceEsm('../index.js'); + td.when(index.default({_: [], '$0': ''})).thenReject(new Error(`Throw error: Exposing token ${env.MY_TOKEN}`)); process.argv = argv; process.env = {...process.env, ...env}; - const cli = require('../cli'); + const cli = (await import('../cli.js')).default; const exitCode = await cli();