diff --git a/src/commands/plugins/index.ts b/src/commands/plugins/index.ts index b75f4e2e..4aee2918 100644 --- a/src/commands/plugins/index.ts +++ b/src/commands/plugins/index.ts @@ -1,9 +1,12 @@ import * as chalk from 'chalk' -import {Command, Flags, Plugin, ux} from '@oclif/core' +import {Command, Flags, Interfaces, Plugin, ux} from '@oclif/core' import Plugins from '../../plugins' import {sortBy} from '../../util' +type JitPlugin = {name: string; version: string; type: 'jit'} +type PluginsJson = Array + export default class PluginsIndex extends Command { static enableJsonFlag = true static flags = { @@ -16,7 +19,7 @@ export default class PluginsIndex extends Command { plugins = new Plugins(this.config) - async run(): Promise> { + async run(): Promise { const {flags} = await this.parse(PluginsIndex) let plugins = this.config.getPluginsList() sortBy(plugins, p => this.plugins.friendlyName(p.name)) @@ -29,11 +32,19 @@ export default class PluginsIndex extends Command { return [] } + const jitPluginsConfig = this.config.pjson.oclif.jitPlugins ?? {} + + const jitPlugins: JitPlugin[] = Object.entries(jitPluginsConfig).map(([name, version]) => ({name: this.plugins.friendlyName(name), version, type: 'jit'})) + sortBy(jitPlugins, p => p.name) + if (!this.jsonEnabled()) { this.display(plugins as Plugin[]) + this.displayJitPlugins(jitPlugins) } - return this.plugins.list() + const results = this.config.getPluginsList() + + return [...results, ...jitPlugins] } private display(plugins: Plugin[]) { @@ -46,6 +57,14 @@ export default class PluginsIndex extends Command { } } + private displayJitPlugins(jitPlugins: JitPlugin[]) { + if (jitPlugins.length === 0) return + this.log(chalk.dim('\nUninstalled JIT Plugins:')) + for (const {name, version} of jitPlugins) { + this.log(`${name} ${chalk.dim(version)}`) + } + } + private createTree(plugin: Plugin) { const tree = ux.tree() for (const p of plugin.children) { diff --git a/src/commands/plugins/inspect.ts b/src/commands/plugins/inspect.ts index 6604ab03..eeba9d0b 100644 --- a/src/commands/plugins/inspect.ts +++ b/src/commands/plugins/inspect.ts @@ -90,6 +90,10 @@ export default class PluginsInspect extends Command { const pluginConfig = this.config.getPluginsList().find(plg => plg.name === pluginName) if (pluginConfig) return pluginConfig as Plugin + if (this.config.pjson.oclif.jitPlugins?.[pluginName]) { + this.warn(`Plugin ${pluginName} is a JIT plugin. It will be installed the first time you run one of it's commands.`) + } + throw new Error(`${pluginName} not installed`) } diff --git a/src/commands/plugins/install.ts b/src/commands/plugins/install.ts index ffea434c..ea30d17b 100644 --- a/src/commands/plugins/install.ts +++ b/src/commands/plugins/install.ts @@ -1,4 +1,4 @@ -import {Command, Flags, ux, Args, Errors} from '@oclif/core' +import {Command, Flags, ux, Args, Errors, Interfaces} from '@oclif/core' import * as validate from 'validate-npm-package-name' import * as chalk from 'chalk' @@ -34,17 +34,22 @@ e.g. If you have a core plugin that has a 'hello' command, installing a user-ins char: 'f', description: 'Run yarn install with force flag.', }), + jit: Flags.boolean({ + hidden: true, + }), }; static aliases = ['plugins:add']; plugins = new Plugins(this.config); + flags!: Interfaces.InferredFlags; // In this case we want these operations to happen // sequentially so the `no-await-in-loop` rule is ignored /* eslint-disable no-await-in-loop */ async run(): Promise { const {flags, argv} = await this.parse(PluginsInstall) + this.flags = flags if (flags.verbose) this.plugins.verbose = true const aliases = this.config.pjson.oclif.aliases || {} for (let name of argv as string[]) { @@ -98,6 +103,16 @@ e.g. If you have a core plugin that has a 'hello' command, installing a user-ins const [splitName, tag = 'latest'] = input.split('@') const name = await this.plugins.maybeUnfriendlyName(splitName) validateNpmPkgName(name) + + if (this.flags.jit) { + const jitVersion = this.config.pjson.oclif?.jitPlugins?.[name] + if (jitVersion && input.includes('@')) { + this.warn(`--jit flag is present. Ignoring tag ${tag} and using the version specified in package.json (${jitVersion}).`) + } + + return {name, tag: jitVersion ?? tag, type: 'npm'} + } + return {name, tag, type: 'npm'} } } diff --git a/test/commands/plugins/index.test.ts b/test/commands/plugins/index.test.ts index 28a3b201..62cb2d4f 100644 --- a/test/commands/plugins/index.test.ts +++ b/test/commands/plugins/index.test.ts @@ -20,15 +20,18 @@ describe('command', () => { .it('installs and uninstalls @oclif/example-plugin-ts') test + .stdout() .command(['plugins', '--json'], {reset: true}) .do(output => expect((output.returned)).to.deep.equal([])) .command(['plugins:install', '@oclif/example-plugin-ts'], {reset: true}) + .stdout() .command(['plugins', '--json'], {reset: true}) .do(output => expect((output.returned as [{name: string}]).find(o => o.name === '@oclif/example-plugin-ts')).to.have.property('type', 'user')) .stdout() .command(['hello'], {reset: true}) .do(output => expect(output.stdout).to.contain('hello world')) .command(['plugins:uninstall', '@heroku-cli/plugin-@oclif/example-plugin-ts']) + .stdout() .command(['plugins', '--json'], {reset: true}) .do(output => expect((output.returned)).to.deep.equal([])) .it('installs and uninstalls @oclif/example-plugin-ts (--json)')