Skip to content

Commit

Permalink
feat: support plugins with wildcards
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed Feb 28, 2024
1 parent 8cee1ce commit 966db94
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 4 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"indent-string": "^4.0.0",
"is-wsl": "^2.2.0",
"js-yaml": "^3.14.1",
"minimatch": "^9.0.3",
"natural-orderby": "^2.0.3",
"object-treeify": "^1.1.33",
"password-prompt": "^1.1.3",
Expand Down
1 change: 1 addition & 0 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ export class Config implements IConfig {
dataDir: this.dataDir,
devPlugins: this.options.devPlugins,
force: opts?.force ?? false,
pluginAdditions: this.options.pluginAdditions,
rootPlugin: this.rootPlugin,
userPlugins: this.options.userPlugins,
})
Expand Down
59 changes: 55 additions & 4 deletions src/config/plugin-loader.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {minimatch} from 'minimatch'
import {join} from 'node:path'

import {PJSON} from '../interfaces'
Expand All @@ -22,10 +23,19 @@ type LoadOpts = {
force?: boolean
rootPlugin: IPlugin
userPlugins?: boolean
pluginAdditions?: {
core?: string[]
dev?: string[]
path?: string
}
}

type PluginsMap = Map<string, IPlugin>

function findMatchingDependencies(dependencies: Record<string, string>, patterns: string[]): string[] {
return Object.keys(dependencies).filter((p) => patterns.some((w) => minimatch(p, w)))
}

export default class PluginLoader {
public errors: (Error | string)[] = []
public plugins: PluginsMap = new Map()
Expand Down Expand Up @@ -73,8 +83,23 @@ export default class PluginLoader {
}

private async loadCorePlugins(opts: LoadOpts): Promise<void> {
if (opts.rootPlugin.pjson.oclif.plugins) {
await this.loadPlugins(opts.rootPlugin.root, 'core', opts.rootPlugin.pjson.oclif.plugins)
const {plugins: corePlugins} = opts.rootPlugin.pjson.oclif
if (corePlugins) {
const plugins = findMatchingDependencies(opts.rootPlugin.pjson.dependencies ?? {}, corePlugins)
await this.loadPlugins(opts.rootPlugin.root, 'core', plugins)
}

const {core: pluginAdditionsCore, path} = opts.pluginAdditions ?? {core: []}
if (pluginAdditionsCore) {
if (path) {
// If path is provided, load plugins from the path
const pjson = await readJson<PJSON>(join(path, 'package.json'))
const plugins = findMatchingDependencies(pjson.dependencies ?? {}, pluginAdditionsCore)
await this.loadPlugins(path, 'core', plugins)
} else {
const plugins = findMatchingDependencies(opts.rootPlugin.pjson.dependencies ?? {}, pluginAdditionsCore)
await this.loadPlugins(opts.rootPlugin.root, 'core', plugins)
}
}
}

Expand All @@ -84,7 +109,26 @@ export default class PluginLoader {
if (isProd()) return
try {
const {devPlugins} = opts.rootPlugin.pjson.oclif
if (devPlugins) await this.loadPlugins(opts.rootPlugin.root, 'dev', devPlugins)
if (devPlugins) {
const allDeps = {...opts.rootPlugin.pjson.dependencies, ...opts.rootPlugin.pjson.devDependencies}
const plugins = findMatchingDependencies(allDeps ?? {}, devPlugins)
await this.loadPlugins(opts.rootPlugin.root, 'dev', plugins)
}

const {dev: pluginAdditionsDev, path} = opts.pluginAdditions ?? {core: []}
if (pluginAdditionsDev) {
if (path) {
// If path is provided, load plugins from the path
const pjson = await readJson<PJSON>(join(path, 'package.json'))
const allDeps = {...pjson.dependencies, ...pjson.devDependencies}
const plugins = findMatchingDependencies(allDeps ?? {}, pluginAdditionsDev)
await this.loadPlugins(path, 'dev', plugins)
} else {
const allDeps = {...opts.rootPlugin.pjson.dependencies, ...opts.rootPlugin.pjson.devDependencies}
const plugins = findMatchingDependencies(allDeps ?? {}, pluginAdditionsDev)
await this.loadPlugins(opts.rootPlugin.root, 'dev', plugins)
}
}
} catch (error: any) {
process.emitWarning(error)
}
Expand Down Expand Up @@ -140,7 +184,14 @@ export default class PluginLoader {
parent.children.push(instance)
}

await this.loadPlugins(instance.root, type, instance.pjson.oclif.plugins || [], instance)
if (instance.pjson.oclif.plugins) {
const allDeps =
type === 'dev'
? {...instance.pjson.dependencies, ...instance.pjson.devDependencies}
: instance.pjson.dependencies
const plugins = findMatchingDependencies(allDeps ?? {}, instance.pjson.oclif.plugins)
await this.loadPlugins(instance.root, type, plugins, instance)
}
} catch (error: any) {
this.errors.push(error)
}
Expand Down
5 changes: 5 additions & 0 deletions src/interfaces/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export interface Options extends PluginOptions {
devPlugins?: boolean
enablePerf?: boolean
jitPlugins?: boolean
pluginAdditions?: {
core?: string[]
dev?: string[]
path?: string
}
plugins?: Map<string, Plugin>
userPlugins?: boolean
version?: string
Expand Down
19 changes: 19 additions & 0 deletions test/config/fixtures/wildcard/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "wildcard-plugins-fixture",
"version": "0.0.0",
"description": "fixture for testing wildcard plugins",
"private": true,
"files": [],
"dependencies": {
"@oclif/core": "^3",
"@oclif/plugin-help": "^6",
"@oclif/plugin-plugins": "^4"
},
"oclif": {
"commands": "./lib/commands",
"topicSeparator": " ",
"plugins": [
"@oclif/plugin-*"
]
}
}
8 changes: 8 additions & 0 deletions test/config/fixtures/wildcard/src/commands/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {Command} from '../../../../../../src/index'

export default class Foo extends Command {
public static description = 'foo description'
public async run(): Promise<void> {
this.log('hello world!')
}
}
7 changes: 7 additions & 0 deletions test/config/fixtures/wildcard/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"outDir": "./lib",
"rootDirs": ["./src"]
},
"include": ["./src/**/*"]
}
56 changes: 56 additions & 0 deletions test/config/wildcard-plugins.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {expect} from 'chai'
import {resolve} from 'node:path'
import {SinonSandbox, createSandbox} from 'sinon'

import {run, ux} from '../../src/index'

describe('plugins defined as patterns in package.json', () => {
let sandbox: SinonSandbox

beforeEach(() => {
sandbox = createSandbox()
sandbox.stub(ux.write, 'stdout')
})

afterEach(() => {
sandbox.restore()
})

it('should load all core plugins in dependencies that match pattern', async () => {
const result = (await run(['plugins', '--core'], {
root: resolve(__dirname, 'fixtures/wildcard/package.json'),
pluginAdditions: {
core: ['@oclif/plugin-*'],
path: resolve(__dirname, '..', '..'),
},
})) as Array<{name: string; type: string}>

expect(result.length).to.equal(3)
const rootPlugin = result.find((r) => r.name === 'wildcard-plugins-fixture')
const pluginHelp = result.find((r) => r.name === '@oclif/plugin-help')
const pluginPlugins = result.find((r) => r.name === '@oclif/plugin-plugins')

expect(rootPlugin).to.exist
expect(pluginHelp).to.exist
expect(pluginPlugins).to.exist
})

it('should load all dev plugins in dependencies and devDependencies that match pattern', async () => {
const result = (await run(['plugins', '--core'], {
root: resolve(__dirname, 'fixtures/wildcard/package.json'),
pluginAdditions: {
dev: ['@oclif/plugin-*'],
path: resolve(__dirname, '..', '..'),
},
})) as Array<{name: string; type: string}>

expect(result.length).to.equal(3)
const rootPlugin = result.find((r) => r.name === 'wildcard-plugins-fixture')
const pluginHelp = result.find((r) => r.name === '@oclif/plugin-help')
const pluginPlugins = result.find((r) => r.name === '@oclif/plugin-plugins')

expect(rootPlugin).to.exist
expect(pluginHelp).to.exist
expect(pluginPlugins).to.exist
})
})

0 comments on commit 966db94

Please sign in to comment.