diff --git a/__tests__/commands/install/integration.js b/__tests__/commands/install/integration.js index e793f1cf05..dba22c2167 100644 --- a/__tests__/commands/install/integration.js +++ b/__tests__/commands/install/integration.js @@ -2,7 +2,7 @@ import type Config from '../../../src/config'; import PackageResolver from '../../../src/package-resolver.js'; -import {run as cache} from '../../../src/cli/commands/cache.js'; +import CacheCommand from '../../../src/cli/commands/cache.js'; import {run as check} from '../../../src/cli/commands/check.js'; import * as constants from '../../../src/constants.js'; import * as reporters from '../../../src/reporters/index.js'; @@ -22,6 +22,10 @@ const path = require('path'); const stream = require('stream'); const os = require('os'); +function cache(config: Config, reporter: reporters.Reporter, flags: Object, args: Array): Promise { + return new CacheCommand().run(config, reporter, flags, args); +} + async function mockConstants(base: Config, mocks: Object, cb: (config: Config) => Promise): Promise { // We cannot put this function inside _helpers, because we need to change the "request" variable // after resetting the modules. Updating this variable is required because some tests check what diff --git a/src/cli/commands/_base.js b/src/cli/commands/_base.js index 1936bd7bbd..69eca8e6e6 100644 --- a/src/cli/commands/_base.js +++ b/src/cli/commands/_base.js @@ -1,7 +1,7 @@ /* @flow */ export default class BaseCommand { - hasWrapper(): boolean { + hasWrapper(flags: Object, args: Array): boolean { return true; } } diff --git a/src/cli/commands/_sub-command.js b/src/cli/commands/_sub-command.js new file mode 100644 index 0000000000..e5dd745737 --- /dev/null +++ b/src/cli/commands/_sub-command.js @@ -0,0 +1,61 @@ +/* @flow */ + +import BaseCommand from './_base.js'; +import type {Reporter} from '../../reporters/index.js'; +import type Config from '../../config.js'; +import type {CLIFunction} from '../../types.js'; +import {MessageError} from '../../errors.js'; +import {camelCase, hyphenate} from '../../util/misc.js'; + +type SubCommands = { + [commandName: string]: CLIFunction, +}; + +type Usage = Array; + +export default class SubCommand extends BaseCommand { + rootCommandName: string; + subCommands: SubCommands; + subCommandNames: Array; + usage: Usage; + examples: Array; + + constructor(rootCommandName: string, subCommands: SubCommands, usage?: Usage = []) { + super(); + this.rootCommandName = rootCommandName; + this.subCommands = subCommands; + this.subCommandNames = Object.keys(subCommands).map(hyphenate); + this.usage = usage; + this.examples = usage.map((cmd: string): string => { + return `${rootCommandName} ${cmd}`; + }); + } + + setFlags(commander: Object) { + commander.usage(`${this.rootCommandName} [${this.subCommandNames.join('|')}] [flags]`); + } + + async run( + config: Config, + reporter: Reporter, + flags: Object, + args: Array, + ): Promise { + const subName: ?string = camelCase(args.shift() || ''); + if (subName && this.subCommands[subName]) { + const command: CLIFunction = this.subCommands[subName]; + const res = await command(config, reporter, flags, args); + if (res !== false) { + return Promise.resolve(); + } + } + + if (this.usage && this.usage.length) { + reporter.error(`${reporter.lang('usage')}:`); + for (const msg of this.usage) { + reporter.error(`yarn ${this.rootCommandName} ${msg}`); + } + } + return Promise.reject(new MessageError(reporter.lang('invalidCommand', this.subCommandNames.join(', ')))); + } +} diff --git a/src/cli/commands/cache.js b/src/cli/commands/cache.js index 84e789d497..4581d381ce 100644 --- a/src/cli/commands/cache.js +++ b/src/cli/commands/cache.js @@ -1,72 +1,76 @@ /* @flow */ +import SubCommand from './_sub-command.js'; import type {Reporter} from '../../reporters/index.js'; import type Config from '../../config.js'; -import buildSubCommands from './_build-sub-commands.js'; import * as fs from '../../util/fs.js'; import {METADATA_FILENAME} from '../../constants'; const path = require('path'); -export function hasWrapper(flags: Object, args: Array): boolean { - return args[0] !== 'dir'; -} +export default class CacheCommand extends SubCommand { + constructor() { + super('cache', { + async ls( + config: Config, + reporter: Reporter, + flags: Object, + args: Array, + ): Promise { + async function readCacheMetadata( + parentDir = config.cacheFolder, + metadataFile = METADATA_FILENAME, + ): Promise>> { + const folders = await fs.readdir(parentDir); + const packagesMetadata = []; -export const {run, setFlags} = buildSubCommands('cache', { - async ls( - config: Config, - reporter: Reporter, - flags: Object, - args: Array, - ): Promise { - async function readCacheMetadata( - parentDir = config.cacheFolder, - metadataFile = METADATA_FILENAME, - ): Promise>> { - const folders = await fs.readdir(parentDir); - const packagesMetadata = []; + for (const folder of folders) { + if (folder[0] === '.') { + continue; + } - for (const folder of folders) { - if (folder[0] === '.') { - continue; - } + const loc = path.join(config.cacheFolder, parentDir.replace(config.cacheFolder, ''), folder); + // Check if this is a scoped package + if (!(await fs.exists(path.join(loc, metadataFile)))) { + // If so, recurrently read scoped packages metadata + packagesMetadata.push(...await readCacheMetadata(loc)); + } else { + const {registry, package: manifest, remote} = await config.readPackageMetadata(loc); + packagesMetadata.push([manifest.name, manifest.version, registry, (remote && remote.resolved) || '']); + } + } - const loc = path.join(config.cacheFolder, parentDir.replace(config.cacheFolder, ''), folder); - // Check if this is a scoped package - if (!(await fs.exists(path.join(loc, metadataFile)))) { - // If so, recurrently read scoped packages metadata - packagesMetadata.push(...await readCacheMetadata(loc)); - } else { - const {registry, package: manifest, remote} = await config.readPackageMetadata(loc); - packagesMetadata.push([manifest.name, manifest.version, registry, (remote && remote.resolved) || '']); + return packagesMetadata; } - } - return packagesMetadata; - } + const body = await readCacheMetadata(); - const body = await readCacheMetadata(); + reporter.table(['Name', 'Version', 'Registry', 'Resolved'], body); + }, - reporter.table(['Name', 'Version', 'Registry', 'Resolved'], body); - }, + dir( + config: Config, + reporter: Reporter, + ) { + reporter.log(config.cacheFolder); + }, - dir( - config: Config, - reporter: Reporter, - ) { - reporter.log(config.cacheFolder); - }, + async clean( + config: Config, + reporter: Reporter, + flags: Object, + args: Array, + ): Promise { + if (config.cacheFolder) { + await fs.unlink(config._cacheRootFolder); + await fs.mkdirp(config.cacheFolder); + reporter.success(reporter.lang('clearedCache')); + } + }, + }); + } - async clean( - config: Config, - reporter: Reporter, - flags: Object, - args: Array, - ): Promise { - if (config.cacheFolder) { - await fs.unlink(config._cacheRootFolder); - await fs.mkdirp(config.cacheFolder); - reporter.success(reporter.lang('clearedCache')); - } - }, -}); + hasWrapper(flags: Object, args: Array): boolean { + return args[0] !== 'dir'; + } +} diff --git a/src/cli/commands/index.js b/src/cli/commands/index.js index b390cb6f3c..1bd480a8ed 100644 --- a/src/cli/commands/index.js +++ b/src/cli/commands/index.js @@ -3,7 +3,7 @@ import * as access from './access.js'; export {access}; import * as add from './add.js'; export {add}; import bin from './bin.js'; export {bin}; -import * as cache from './cache.js'; export {cache}; +import cache from './cache.js'; export {cache}; import * as check from './check.js'; export {check}; import * as clean from './clean.js'; export {clean}; import * as config from './config.js'; export {config};