diff --git a/.changeset/perfect-kangaroos-agree.md b/.changeset/perfect-kangaroos-agree.md new file mode 100644 index 000000000..d1c482cc8 --- /dev/null +++ b/.changeset/perfect-kangaroos-agree.md @@ -0,0 +1,5 @@ +--- +'@verdaccio/cli': major +--- + +feat: use clipanion over commander diff --git a/.github/workflows/ci-e2e-ui.yml b/.github/workflows/ci-e2e-ui.yml index 88ff5d028..8b18e7e5a 100644 --- a/.github/workflows/ci-e2e-ui.yml +++ b/.github/workflows/ci-e2e-ui.yml @@ -3,7 +3,7 @@ name: E2E UI on: push: branches: - - 5.x + - 6.x - 'changeset-release/6.x' pull_request: paths: @@ -30,7 +30,7 @@ jobs: - name: Use Node ${{ matrix.node_version }} uses: actions/setup-node@v1 with: - node_version: ${{ matrix.node_version }} + node-version: ${{ matrix.node_version }} - name: Install pnpm run: npm i -g pnpm@latest - name: Install diff --git a/.github/workflows/ci-e2e.yml b/.github/workflows/ci-e2e.yml index 3a7089a8a..289787803 100644 --- a/.github/workflows/ci-e2e.yml +++ b/.github/workflows/ci-e2e.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest] node_version: [14] name: ${{ matrix.os }} / Node ${{ matrix.node_version }} @@ -30,7 +30,7 @@ jobs: - name: Use Node ${{ matrix.node_version }} uses: actions/setup-node@v1 with: - node_version: ${{ matrix.node_version }} + node-version: ${{ matrix.node_version }} - name: Install pnpm run: npm i -g pnpm@latest - name: Install diff --git a/packages/cli/package.json b/packages/cli/package.json index ff6c2e1aa..05924fb4a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -47,6 +47,7 @@ "@verdaccio/cli-ui": "workspace:5.0.0-alpha.3", "@verdaccio/node-api": "workspace:5.0.0-alpha.8", "commander": "6.2.0", + "clipanion": "3.0.0-rc.11", "envinfo": "7.4.0", "kleur": "3.0.3", "semver": "7.3.2" diff --git a/packages/cli/src/.eslintrc b/packages/cli/src/.eslintrc new file mode 100644 index 000000000..b090d8ea9 --- /dev/null +++ b/packages/cli/src/.eslintrc @@ -0,0 +1,6 @@ + +{ + "rules": { + "max-len": 0 + } +} diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 6d833b31e..26e731d25 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -1,58 +1,33 @@ -import commander from 'commander'; -import { bgYellow, bgRed } from 'kleur'; - import { displayError } from '@verdaccio/cli-ui'; +import { Cli } from 'clipanion'; +import { InfoCommand } from './commands/info'; +import { InitCommand } from './commands/init'; +import { VersionCommand } from './commands/version'; +import { isVersionValid, MIN_NODE_VERSION } from './utils'; -import infoCommand from './commands/info'; -import initProgram from './commands/init'; -import { isVersionValid } from './utils'; +if (process.getuid && process.getuid() === 0) { + process.emitWarning(`Verdaccio doesn't need superuser privileges. don't run it under root`); +} -const isRootUser = process.getuid && process.getuid() === 0; - -if (isRootUser) { - global.console.warn( - bgYellow().red( - "*** WARNING: Verdaccio doesn't need superuser privileges. Don't run it under root! ***" - ) +if (!isVersionValid(process.version)) { + throw new Error( + `Verdaccio requires at least Node.js v${MIN_NODE_VERSION} or higher and you have installed ${process.version}, + please upgrade your Node.js distribution` ); } -if (isVersionValid()) { - global.console.error( - bgRed( - 'Verdaccio requires at least Node.js ${MIN_NODE_VERSION} or higher,' + - ' please upgrade your Node.js distribution' - ) - ); - process.exit(1); -} +const [node, app, ...args] = process.argv; -process.title = 'verdaccio'; +const cli = new Cli({ + binaryLabel: `verdaccio`, + binaryName: `${node} ${app}`, + binaryVersion: require('../package.json').version, +}); -const pkgVersion = require('../package.json').version; -const pkgName = 'verdaccio'; - -commander - .option('-i, --info', 'prints debugging information about the local environment') - .option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)') - .option('-c, --config ', 'use this configuration file (default: ./config.yaml)') - .version(pkgVersion) - .parse(process.argv); - -const fallbackConfig = commander.args.length == 1 && !commander.config; -const isHelp = commander.args.length !== 0; - -if (commander.info) { - infoCommand(); -} else if (fallbackConfig) { - // handling "verdaccio [config]" case if "-c" is missing in command line - commander.config = commander.args.pop(); - initProgram(commander, pkgVersion, pkgName); -} else if (isHelp) { - commander.help(); -} else { - initProgram(commander, pkgVersion, pkgName); -} +cli.register(InfoCommand); +cli.register(InitCommand); +cli.register(VersionCommand); +cli.runExit(args, Cli.defaultContext); process.on('uncaughtException', function (err) { displayError( diff --git a/packages/cli/src/commands/info.ts b/packages/cli/src/commands/info.ts index 520d9f056..89d092742 100644 --- a/packages/cli/src/commands/info.ts +++ b/packages/cli/src/commands/info.ts @@ -1,18 +1,20 @@ import envinfo from 'envinfo'; +import { Command } from 'clipanion'; -export default function infoCommand() { - // eslint-disable-next-line no-console - console.log('\nEnvironment Info:'); - (async () => { +export class InfoCommand extends Command { + static paths = [[`--info`], [`-i`]]; + + async execute() { + this.context.stdout.write('\nEnvironment Info:'); const data = await envinfo.run({ System: ['OS', 'CPU'], - Binaries: ['Node', 'Yarn', 'npm'], + Binaries: ['node', 'yarn', 'npm', 'pnpm'], Virtualization: ['Docker'], Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'], npmGlobalPackages: ['verdaccio'], }); - // eslint-disable-next-line no-console - console.log(data); + + this.context.stdout.write(data); process.exit(0); - })(); + } } diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 41ffaffaf..b495ffbb1 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1,37 +1,108 @@ +import { Command, Option } from 'clipanion'; + import { ConfigRuntime } from '@verdaccio/types'; import { findConfigFile, parseConfigFile } from '@verdaccio/config'; import { startVerdaccio, listenDefaultCallback } from '@verdaccio/node-api'; export const DEFAULT_PROCESS_NAME: string = 'verdaccio'; -export default function initProgram(commander, pkgVersion, pkgName) { - const cliListener = commander.listen; - let configPathLocation; - let verdaccioConfiguration: ConfigRuntime; - try { - configPathLocation = findConfigFile(commander.config); - verdaccioConfiguration = parseConfigFile(configPathLocation); - const { web, https } = verdaccioConfiguration; +export class InitCommand extends Command { + static paths = [Command.Default]; - process.title = web?.title || DEFAULT_PROCESS_NAME; + listen = Option.String('-l,--listen', { + description: 'host:port number to listen on (default: localhost:4873)', + }); - if (!https) { - verdaccioConfiguration = Object.assign({}, verdaccioConfiguration, { - https: { enable: false }, - }); + // eslint-disable-next-line + static usage = Command.Usage({ + description: `launch the server`, + details: ` + This start the registry in the default port. + + When used without arguments, it: + + - bootstrap the server at the port \`4873\` + + The optional arguments are: + + - \`--listen\` to switch the default server port, + - \`--config\` to define a different configuration path location, + + `, + examples: [ + [`Runs the server with the default configuration`, `verdaccio`], + [`Runs the server in the port 5000`, `verdaccio --listen 5000`], + [ + `Runs the server by using a different absolute location of the configuration file`, + `verdaccio --config /home/user/verdaccio/config.yaml`, + ], + ], + }); + + config = Option.String('-c,--config', { + description: 'use this configuration file (default: ./config.yaml)', + }); + + async execute() { + let configPathLocation; + let verdaccioConfiguration: ConfigRuntime; + try { + configPathLocation = findConfigFile(this.config as string); + verdaccioConfiguration = parseConfigFile(configPathLocation); + const { web, https } = verdaccioConfiguration; + + process.title = web?.title || DEFAULT_PROCESS_NAME; + + if (!https) { + verdaccioConfiguration = Object.assign({}, verdaccioConfiguration, { + https: { enable: false }, + }); + } + + const { version, name } = require('../../package.json'); + + startVerdaccio( + verdaccioConfiguration, + this.listen as string, + configPathLocation, + version, + name, + listenDefaultCallback + ); + } catch (err) { + process.exit(1); } - - // initLogger.warn({file: configPathLocation}, 'config file - @{file}'); - - startVerdaccio( - verdaccioConfiguration, - cliListener, - configPathLocation, - pkgVersion, - pkgName, - listenDefaultCallback - ); - } catch (err) { - process.exit(1); } } + +// export default function initProgram(commander, pkgVersion, pkgName) { +// const cliListener = commander.listen; +// let configPathLocation; +// let verdaccioConfiguration: ConfigRuntime; +// try { +// configPathLocation = findConfigFile(commander.config); +// verdaccioConfiguration = parseConfigFile(configPathLocation); +// const { web, https } = verdaccioConfiguration; + +// process.title = web?.title || DEFAULT_PROCESS_NAME; + +// if (!https) { +// verdaccioConfiguration = Object.assign({}, verdaccioConfiguration, { +// https: { enable: false }, +// }); +// } + +// // initLogger.warn({file: configPathLocation}, 'config file - @{file}'); + +// startVerdaccio( +// verdaccioConfiguration, +// cliListener, +// configPathLocation, +// pkgVersion, +// pkgName, +// listenDefaultCallback +// ); +// } catch (err) { +// process.exit(1); +// } +// } diff --git a/packages/cli/src/commands/version.ts b/packages/cli/src/commands/version.ts new file mode 100644 index 000000000..0b6800084 --- /dev/null +++ b/packages/cli/src/commands/version.ts @@ -0,0 +1,11 @@ +import { Command } from 'clipanion'; + +export class VersionCommand extends Command { + static paths = [[`--version`], [`-v`]]; + + async execute() { + const version = require('../../package.json').version; + this.context.stdout.write(`v${version}`); + process.exit(0); + } +} diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 117631ee9..cbb2ab5b7 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -1,6 +1,7 @@ import semver from 'semver'; -export const MIN_NODE_VERSION = '10.22.1'; +export const MIN_NODE_VERSION = '12'; -export const isVersionValid = () => - semver.satisfies(process.version, `>=${MIN_NODE_VERSION}`) === false; +export function isVersionValid(version) { + return semver.satisfies(version, `>=${MIN_NODE_VERSION}`); +} diff --git a/packages/cli/test/utils.spec.ts b/packages/cli/test/utils.spec.ts new file mode 100644 index 000000000..54add97d7 --- /dev/null +++ b/packages/cli/test/utils.spec.ts @@ -0,0 +1,13 @@ +import { isVersionValid } from '../src/utils'; + +test('valid version node.js', () => { + expect(isVersionValid('14.0.0')).toBeTruthy(); +}); + +test('is invalid version node.js', () => { + expect(isVersionValid('11.0.0')).toBeFalsy(); +}); + +test('Node 12 should valid version node.js', () => { + expect(isVersionValid('12.0.0')).toBeTruthy(); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04b01cbc5..1bf0246dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -289,6 +289,7 @@ importers: '@verdaccio/cli-ui': link:../core/cli-ui '@verdaccio/config': link:../config '@verdaccio/node-api': link:../node-api + clipanion: 3.0.0-rc.11 commander: 6.2.0 envinfo: 7.4.0 kleur: 3.0.3 @@ -297,6 +298,7 @@ importers: '@verdaccio/cli-ui': workspace:5.0.0-alpha.3 '@verdaccio/config': workspace:5.0.0-alpha.3 '@verdaccio/node-api': workspace:5.0.0-alpha.8 + clipanion: 3.0.0-rc.11 commander: 6.2.0 envinfo: 7.4.0 kleur: 3.0.3 @@ -10417,6 +10419,12 @@ packages: node: '>= 10' resolution: integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + /clipanion/3.0.0-rc.11: + dependencies: + typanion: 3.3.0 + dev: false + resolution: + integrity: sha512-e0GyZXjprnxRuls/pHH0cqAgKtpiceQy8PztEZP9fdnkMxuo05D5en9fx60Iiqqjge8pmrMfsiglueS8SWmlgg== /clipboard/2.0.6: dependencies: good-listener: 1.2.2 @@ -26412,6 +26420,10 @@ packages: /tweetnacl/0.14.5: resolution: integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + /typanion/3.3.0: + dev: false + resolution: + integrity: sha512-e+54C4+ozFsTorFe50JNQlXlt4HVGQUvqul7VS0GbDfKlxh3aXbJY87Yu9IdtzvJWCyTgDX7q1PeMd3FH9zZqA== /type-check/0.3.2: dependencies: prelude-ls: 1.1.2