Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: use npm for plugin operations #776

Merged
merged 53 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
ba6a18c
feat: add npm and pnpm options
mdonnalley Jan 18, 2024
ea2163f
Merge branch 'main' into mdonnalley/revamp
mdonnalley Jan 18, 2024
94f0668
feat!: use npm for installs
mdonnalley Jan 23, 2024
1e80a62
chore: bump to v5 prerelease
mdonnalley Jan 23, 2024
3953b9c
Merge branch 'main' into mdonnalley/revamp
mdonnalley Jan 23, 2024
737b967
fix: remove yarn.lock
mdonnalley Jan 24, 2024
d87fcbf
test: skip sf integration tests
mdonnalley Jan 24, 2024
6c3cb6f
fix: force rm yarn.lock
mdonnalley Jan 25, 2024
6ebb1fc
fix: use fork for npm show
mdonnalley Jan 25, 2024
b155136
feat: remove all yarn functionality
mdonnalley Jan 25, 2024
d7c09cb
chore: clean up
mdonnalley Jan 25, 2024
b1ea1f1
test: debug failing windows tests
mdonnalley Jan 25, 2024
3cc66c8
feat: add --npm-log-level flag
mdonnalley Jan 25, 2024
fd4f30f
chore: clean up
mdonnalley Jan 25, 2024
85b2da0
test: debug failing windows tests
mdonnalley Jan 25, 2024
002675b
test: compilation errors
mdonnalley Jan 25, 2024
b2a4d14
fix: use CLIError
mdonnalley Jan 25, 2024
9b736f6
test: debug failing windows tests
mdonnalley Jan 25, 2024
503a9d3
test: debug failing windows tests
mdonnalley Jan 25, 2024
6d23bb9
test: debug failing windows tests
mdonnalley Jan 25, 2024
0e6c372
chore: remove unused dep
mdonnalley Jan 25, 2024
daac083
chore: clean up
mdonnalley Jan 25, 2024
7e3f0a8
fix: add suggestion for failed install
mdonnalley Jan 25, 2024
dd8f69c
fix: npm-run-path is actually needed
mdonnalley Jan 25, 2024
b4da5fd
test: debug failing windows tests
mdonnalley Jan 25, 2024
409db00
test: debug failing windows tests
mdonnalley Jan 25, 2024
9331d65
test: extend timeout
mdonnalley Jan 25, 2024
be382e7
test: renable sf integration tests
mdonnalley Jan 26, 2024
d1c9d74
feat: simplify logging options
mdonnalley Jan 26, 2024
b4ef3d3
fix: clean up yarn.lock and node_modules if they exist
mdonnalley Jan 30, 2024
d044775
Merge branch 'main' into mdonnalley/revamp
mdonnalley Jan 31, 2024
57dda03
perf: spawn new process for removing node_modules
mdonnalley Jan 31, 2024
a739a4f
chore(release): 5.0.0-beta.1 [skip ci]
svc-cli-bot Feb 1, 2024
82ee43c
Merge branch 'main' into mdonnalley/revamp
mdonnalley Feb 22, 2024
95f6cac
chore(release): 5.0.0-beta.2 [skip ci]
svc-cli-bot Feb 22, 2024
eab8e5b
Merge branch 'main' into mdonnalley/revamp
mdonnalley Mar 7, 2024
6581914
chore(release): 5.0.0-beta.3 [skip ci]
svc-cli-bot Mar 7, 2024
b7eca7a
chore: merge main conflicts
iowillhoit Mar 14, 2024
7c33ba1
chore(release): 5.0.0-beta.4 [skip ci]
svc-cli-bot Mar 14, 2024
a127059
fix: reinstall plugin from url if applicable
mdonnalley Mar 21, 2024
d9a67ae
feat: better logging
mdonnalley Mar 21, 2024
7167090
fix: uninstall mis-scoped plugin
mdonnalley Mar 21, 2024
6fdb181
fix: warn about missing expected files after github install
mdonnalley Mar 21, 2024
c07eccf
fix: add type
mdonnalley Mar 21, 2024
b799b38
feat: improve ux when no output from npm
mdonnalley Mar 21, 2024
2c722b4
chore: merge main conflicts
iowillhoit Mar 22, 2024
fa30d5e
chore(release): 5.0.0-beta.5 [skip ci]
svc-cli-bot Mar 22, 2024
a53853b
fix: display name when uninstalling wiht no args
mdonnalley Mar 22, 2024
ac2c394
test: set timeout on install test
mdonnalley Mar 22, 2024
566af61
fix: ensure dir exists before checking for name
mdonnalley Mar 22, 2024
034a067
Merge branch 'main' into mdonnalley/revamp
iowillhoit Mar 25, 2024
183be3f
chore(release): 5.0.0-beta.6 [skip ci]
svc-cli-bot Mar 25, 2024
12808a5
Merge branch 'main' into mdonnalley/revamp
mdonnalley Mar 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
{
"name": "@oclif/plugin-plugins",
"description": "plugins plugin for oclif",
"version": "4.3.10",
"version": "5.0.0-beta.6",
"author": "Salesforce",
"bugs": "https://github.com/oclif/plugin-plugins/issues",
"dependencies": {
"@oclif/core": "^3.25.2",
"chalk": "^5.3.0",
"debug": "^4.3.4",
"npm": "10.5.0",
"npm-run-path": "^4.0.1",
"npm-package-arg": "^11.0.1",
"npm-run-path": "^5.2.0",
"semver": "^7.6.0",
"shelljs": "^0.8.5",
"validate-npm-package-name": "^5.0.0",
"yarn": "^1.22.21"
"validate-npm-package-name": "^5.0.0"
},
"devDependencies": {
"@commitlint/config-conventional": "^18",
Expand All @@ -23,6 +22,7 @@
"@types/debug": "^4.1.12",
"@types/mocha": "^10.0.6",
"@types/node": "^18",
"@types/npm-package-arg": "^6.1.4",
"@types/semver": "^7.5.8",
"@types/shelljs": "^0.8.15",
"@types/sinon": "^17",
Expand Down Expand Up @@ -50,8 +50,7 @@
"files": [
"oclif.manifest.json",
"/lib",
"npm-shrinkwrap.json",
"oclif.lock"
"npm-shrinkwrap.json"
],
"homepage": "https://github.com/oclif/plugin-plugins",
"keywords": [
Expand All @@ -77,15 +76,15 @@
"repository": "oclif/plugin-plugins",
"scripts": {
"build": "shx rm -rf lib && tsc",
"clean": "shx rm -f oclif.manifest.json npm-shrinkwrap.json oclif.lock",
"clean": "shx rm -f oclif.manifest.json npm-shrinkwrap.json",
"compile": "tsc",
"lint": "eslint . --ext .ts",
"postpack": "yarn run clean",
"posttest": "yarn lint",
"prepack": "yarn build && oclif manifest && oclif readme && npm shrinkwrap && oclif lock",
"prepack": "yarn build && oclif manifest && oclif readme && npm shrinkwrap",
"prepare": "husky && yarn build",
"pretest": "yarn build --noEmit && tsc -p test --noEmit",
"test:integration:install": "mocha \"test/**/install.integration.ts\"",
"test:integration:install": "mocha \"test/**/install.integration.ts\" --timeout 1200000",
"test:integration:link": "mocha \"test/**/link.integration.ts\"",
"test:integration:sf": "mocha \"test/**/sf.integration.ts\"",
"test:integration": "mocha \"test/**/*.integration.ts\"",
Expand Down
6 changes: 5 additions & 1 deletion src/commands/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ export default class PluginsIndex extends Command {
core: Flags.boolean({description: 'Show core plugins.'}),
}

plugins = new Plugins(this.config)
plugins!: Plugins

async run(): Promise<PluginsJson> {
const {flags} = await this.parse(PluginsIndex)
this.plugins = new Plugins({
config: this.config,
})

let plugins = this.config.getPluginsList()
sortBy(plugins, (p) => this.plugins.friendlyName(p.name))
if (!flags.core) {
Expand Down
8 changes: 6 additions & 2 deletions src/commands/plugins/inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import chalk from 'chalk'
import {readFile} from 'node:fs/promises'
import {dirname, join, sep} from 'node:path'

import {determineLogLevel} from '../../log-level.js'
import Plugins from '../../plugins.js'
import {sortBy} from '../../util.js'

Expand Down Expand Up @@ -59,7 +60,7 @@ export default class PluginsInspect extends Command {

static usage = 'plugins:inspect PLUGIN...'

plugins = new Plugins(this.config)
plugins!: Plugins

async findDep(plugin: Plugin, dependency: string): Promise<{pkgPath: null | string; version: null | string}> {
const dependencyPath = join(...dependency.split('/'))
Expand Down Expand Up @@ -140,7 +141,10 @@ export default class PluginsInspect extends Command {
/* eslint-disable no-await-in-loop */
async run(): Promise<PluginWithDeps[]> {
const {argv, flags} = await this.parse(PluginsInspect)
if (flags.verbose) this.plugins.verbose = true
this.plugins = new Plugins({
config: this.config,
logLevel: determineLogLevel(this.config, flags, 'silent'),
})
const aliases = this.config.pjson.oclif.aliases ?? {}
const plugins: PluginWithDeps[] = []
for (let name of argv as string[]) {
Expand Down
65 changes: 37 additions & 28 deletions src/commands/plugins/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {Args, Command, Errors, Flags, Interfaces, ux} from '@oclif/core'
import chalk from 'chalk'
import validate from 'validate-npm-package-name'

import {determineLogLevel} from '../../log-level.js'
import Plugins from '../../plugins.js'
import {YarnMessagesCache} from '../../util.js'

export default class PluginsInstall extends Command {
static aliases = ['plugins:add']
Expand All @@ -13,26 +13,34 @@ export default class PluginsInstall extends Command {
plugin: Args.string({description: 'Plugin to install.', required: true}),
}

static description = `Installs a plugin into the CLI.
Can be installed from npm or a git url.
static description = `Uses bundled npm executable to install plugins into <%= config.dataDir %>

Installation of a user-installed plugin will override a core plugin.

e.g. If you have a core plugin that has a 'hello' command, installing a user-installed plugin with a 'hello' command will override the core plugin implementation. This is useful if a user needs to update core plugin functionality in the CLI without the need to patch and update the whole CLI.
`
Use the <%= config.scopedEnvVarKey('NPM_LOG_LEVEL') %> environment variable to set the npm loglevel.
Use the <%= config.scopedEnvVarKey('NPM_REGISTRY') %> environment variable to set the npm registry.`

public static enableJsonFlag = true

static examples = [
'<%= config.bin %> <%= command.id %> <%- config.pjson.oclif.examplePlugin || "myplugin" %> ',
'<%= config.bin %> <%= command.id %> https://github.com/someuser/someplugin',
'<%= config.bin %> <%= command.id %> someuser/someplugin',
{
command: '<%= config.bin %> <%= command.id %> <%- config.pjson.oclif.examplePlugin || "myplugin" %> ',
description: 'Install a plugin from npm registry.',
},
{
command: '<%= config.bin %> <%= command.id %> https://github.com/someuser/someplugin',
description: 'Install a plugin from a github url.',
},
{
command: '<%= config.bin %> <%= command.id %> someuser/someplugin',
description: 'Install a plugin from a github slug.',
},
]

static flags = {
force: Flags.boolean({
char: 'f',
description: 'Run yarn install with force flag.',
description: 'Force npm to fetch remote resources even if a local copy exists on disk.',
}),
help: Flags.help({char: 'h'}),
jit: Flags.boolean({
Expand All @@ -46,7 +54,7 @@ e.g. If you have a core plugin that has a 'hello' command, installing a user-ins
const jitPluginsConfig = ctx.config.pjson.oclif.jitPlugins ?? {}
if (Object.keys(jitPluginsConfig).length === 0) return input

const plugins = new Plugins(ctx.config)
const plugins = new Plugins({config: ctx.config})

const nonJitPlugins = await Promise.all(
requestedPlugins.map(async (plugin) => {
Expand All @@ -65,26 +73,28 @@ e.g. If you have a core plugin that has a 'hello' command, installing a user-ins
}),
silent: Flags.boolean({
char: 's',
description: 'Silences yarn output.',
description: 'Silences npm output.',
exclusive: ['verbose'],
}),
verbose: Flags.boolean({
char: 'v',
description: 'Show verbose yarn output.',
description: 'Show verbose npm output.',
exclusive: ['silent'],
}),
}

static strict = false

static usage = 'plugins:install PLUGIN...'
static summary = 'Installs a plugin into <%= config.bin %>.'

flags!: Interfaces.InferredFlags<typeof PluginsInstall.flags>
plugins = new Plugins(this.config)

// In this case we want these operations to happen
// sequentially so the `no-await-in-loop` rule is ignored
async parsePlugin(input: string): Promise<{name: string; tag: string; type: 'npm'} | {type: 'repo'; url: string}> {
async parsePlugin(
plugins: Plugins,
input: string,
): Promise<{name: string; tag: string; type: 'npm'} | {type: 'repo'; url: string}> {
// git ssh url
if (input.startsWith('git+ssh://') || input.endsWith('.git')) {
return {type: 'repo', url: input}
Expand All @@ -93,7 +103,7 @@ e.g. If you have a core plugin that has a 'hello' command, installing a user-ins
const getNameAndTag = async (input: string): Promise<{name: string; tag: string}> => {
const regexAtSymbolNotAtBeginning = /(?<!^)@/
const [splitName, tag = 'latest'] = input.split(regexAtSymbolNotAtBeginning)
const name = splitName.startsWith('@') ? splitName : await this.plugins.maybeUnfriendlyName(splitName)
const name = splitName.startsWith('@') ? splitName : await plugins.maybeUnfriendlyName(splitName)
validateNpmPkgName(name)

if (this.flags.jit) {
Expand Down Expand Up @@ -135,39 +145,38 @@ e.g. If you have a core plugin that has a 'hello' command, installing a user-ins
async run(): Promise<void> {
const {argv, flags} = await this.parse(PluginsInstall)
this.flags = flags
if (flags.verbose && !flags.silent) this.plugins.verbose = true
if (flags.silent && !flags.verbose) this.plugins.silent = true
const plugins = new Plugins({
config: this.config,
logLevel: determineLogLevel(this.config, this.flags, 'notice'),
})
const aliases = this.config.pjson.oclif.aliases || {}
for (let name of argv as string[]) {
if (aliases[name] === null) this.error(`${name} is blocked`)
name = aliases[name] || name
const p = await this.parsePlugin(name)
const p = await this.parsePlugin(plugins, name)
let plugin
await this.config.runHook('plugins:preinstall', {
plugin: p,
})
try {
if (p.type === 'npm') {
ux.action.start(`Installing plugin ${chalk.cyan(this.plugins.friendlyName(p.name) + '@' + p.tag)}`)
plugin = await this.plugins.install(p.name, {
ux.action.start(
`${this.config.name}: Installing plugin ${chalk.cyan(plugins.friendlyName(p.name) + '@' + p.tag)}`,
)
plugin = await plugins.install(p.name, {
force: flags.force,
tag: p.tag,
})
} else {
ux.action.start(`Installing plugin ${chalk.cyan(p.url)}`)
plugin = await this.plugins.install(p.url, {force: flags.force})
ux.action.start(`${this.config.name}: Installing plugin ${chalk.cyan(p.url)}`)
plugin = await plugins.install(p.url, {force: flags.force})
}
} catch (error) {
ux.action.stop(chalk.bold.red('failed'))
YarnMessagesCache.getInstance().flush(plugin)
throw error
}

ux.action.stop(`installed v${plugin.version}`)

YarnMessagesCache.getInstance().flush(plugin)

this.log(chalk.green(`\nSuccessfully installed ${plugin.name} v${plugin.version}`))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to remove this log message?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The spinner now shows the success (or failure) message

@salesforce/cli: Installing plugin @oclif/plugin-version@latest... installed v2.0.14

}
}
}
Expand Down
19 changes: 9 additions & 10 deletions src/commands/plugins/link.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Args, Command, Flags, ux} from '@oclif/core'
import chalk from 'chalk'

import {determineLogLevel} from '../../log-level.js'
import Plugins from '../../plugins.js'
import {YarnMessagesCache} from '../../util.js'

export default class PluginsLink extends Command {
static args = {
Expand All @@ -27,17 +27,16 @@ e.g. If you have a user-installed or core plugin that has a 'hello' command, ins
verbose: Flags.boolean({char: 'v'}),
}

static usage = 'plugins:link PLUGIN'

plugins = new Plugins(this.config)

async run(): Promise<void> {
const {args, flags} = await this.parse(PluginsLink)
this.plugins.verbose = flags.verbose
ux.action.start(`Linking plugin ${chalk.cyan(args.path)}`)
await this.plugins.link(args.path, {install: flags.install})
ux.action.stop()

YarnMessagesCache.getInstance().flush()
const plugins = new Plugins({
config: this.config,
logLevel: determineLogLevel(this.config, flags, 'silent'),
})

ux.action.start(`${this.config.name}: Linking plugin ${chalk.cyan(args.path)}`)
await plugins.link(args.path, {install: flags.install})
ux.action.stop()
}
}
9 changes: 6 additions & 3 deletions src/commands/plugins/reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export default class Reset extends Command {

async run(): Promise<void> {
const {flags} = await this.parse(Reset)
const plugins = new Plugins(this.config)
const plugins = new Plugins({
config: this.config,
})
const userPlugins = await plugins.list()

this.log(`Found ${userPlugins.length} plugin${userPlugins.length === 0 ? '' : 's'}:`)
Expand All @@ -44,7 +46,6 @@ export default class Reset extends Command {
}

await Promise.all(filesToDelete.map((file) => rm(file, {force: true, recursive: true})))

for (const plugin of userPlugins) {
this.log(`✅ ${plugin.type === 'link' ? 'Unlinked' : 'Uninstalled'} ${plugin.name}`)
}
Expand Down Expand Up @@ -76,7 +77,9 @@ export default class Reset extends Command {

if (plugin.type === 'user') {
try {
const newPlugin = await plugins.install(plugin.name, {tag: plugin.tag})
const newPlugin = plugin.url
? await plugins.install(plugin.url)
: await plugins.install(plugin.name, {tag: plugin.tag})
const newVersion = chalk.dim(`-> ${newPlugin.version}`)
const tag = plugin.tag ? `@${plugin.tag}` : plugin.url ? ` (${plugin.url})` : ''
this.log(`✅ Reinstalled ${plugin.name}${tag} ${newVersion}`)
Expand Down
25 changes: 12 additions & 13 deletions src/commands/plugins/uninstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import {Args, Command, Flags, ux} from '@oclif/core'
import chalk from 'chalk'

import {determineLogLevel} from '../../log-level.js'
import Plugins from '../../plugins.js'
import {YarnMessagesCache} from '../../util.js'

function removeTags(plugin: string): string {
if (plugin.includes('@')) {
Expand Down Expand Up @@ -38,21 +38,20 @@ export default class PluginsUninstall extends Command {

static strict = false

static usage = 'plugins:uninstall PLUGIN...'

plugins = new Plugins(this.config)

// In this case we want these operations to happen
// sequentially so the `no-await-in-loop` rule is ignored
async run(): Promise<void> {
const {argv, flags} = await this.parse(PluginsUninstall)
this.plugins = new Plugins(this.config)
if (flags.verbose) this.plugins.verbose = true

const plugins = new Plugins({
config: this.config,
logLevel: determineLogLevel(this.config, flags, 'silent'),
})

if (argv.length === 0) argv.push('.')
for (const plugin of argv as string[]) {
const friendly = removeTags(this.plugins.friendlyName(plugin))
ux.action.start(`Uninstalling ${friendly}`)
const unfriendly = await this.plugins.hasPlugin(removeTags(plugin))
const friendly = removeTags(plugins.friendlyName(plugin))
const unfriendly = await plugins.hasPlugin(removeTags(plugin))
if (!unfriendly) {
const p = this.config.getPluginsList().find((p) => p.name === plugin)
if (p?.parent)
Expand All @@ -65,15 +64,15 @@ export default class PluginsUninstall extends Command {

try {
const {name} = unfriendly
await this.plugins.uninstall(name)
const displayName = friendly === '.' ? name : friendly ?? name
ux.action.start(`${this.config.name}: Uninstalling ${displayName}`)
await plugins.uninstall(name)
} catch (error) {
ux.action.stop(chalk.bold.red('failed'))
throw error
}

ux.action.stop()

YarnMessagesCache.getInstance().flush()
}
}
}
Loading
Loading