Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions src/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ export default class Plugins {
const normalizedUrl = npa(url)
const matches = Object.entries(dependencies ?? {}).find(([, npmVersion]) => {
const normalized = npa(npmVersion)
if (normalized.type !== normalizedUrl.type) {
return false
}

// for local file paths
if (normalized.type === 'file' && normalized.raw) {
return parse(url).base === parse(normalized.raw).base
Expand Down
Binary file added test/fixtures/oclif-plugin-update-v4.7.31.tgz
Binary file not shown.
Binary file added test/fixtures/oclif-plugin-version-v2.2.41.tgz
Binary file not shown.
82 changes: 81 additions & 1 deletion test/integration/install.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,36 @@ import {runCommand} from '@oclif/test'
import {dim} from 'ansis'
import {expect} from 'chai'
import {rm} from 'node:fs/promises'
import {join, resolve} from 'node:path'
import {dirname, join, resolve} from 'node:path'
import {fileURLToPath} from 'node:url'

const __dirname = dirname(fileURLToPath(import.meta.url))

describe('install/uninstall integration tests', () => {
const plugin = '@oclif/plugin-version'
const pluginShortName = 'version'
const pluginGithubSlug = 'oclif/plugin-version'
const pluginGithubUrl = 'https://github.com/oclif/plugin-version.git'
let pluginLocalTarball = resolve(__dirname, '..', 'fixtures', 'oclif-plugin-version-v2.2.41.tgz')
// Normalize the path to ensure Windows compatibility
.replaceAll('\\', '/')
// If the path starts with 'C:', that needs to be removed
if (pluginLocalTarball.startsWith('C:')) {
pluginLocalTarball = pluginLocalTarball.slice(2)
}

const otherPlugin = '@oclif/plugin-search'
const otherPluginShortName = 'search'

const yetAnotherPlugin = '@oclif/plugin-update'
const yetAnotherPluginShortName = 'update'
let yetAnotherLocalPluginTarball = resolve(__dirname, '..', 'fixtures', 'oclif-plugin-update-v4.7.31.tgz')
// Normalize the path to ensure Windows compatibility
.replaceAll('\\', '/')
// If the path starts with 'C:', that needs to be removed
if (yetAnotherLocalPluginTarball.startsWith('C:')) {
yetAnotherLocalPluginTarball = yetAnotherLocalPluginTarball.slice(2)
}

const tmp = resolve('tmp', 'install-integration')
const cacheDir = join(tmp, 'plugin-plugins-tests', 'cache')
Expand Down Expand Up @@ -146,6 +169,63 @@ describe('install/uninstall integration tests', () => {
})
})

describe('local tarball', () => {
it('should install plugin from locally hosted tarball', async () => {
await runCommand(`plugins install "file://${pluginLocalTarball}`)
const {result, stdout} = await runCommand<Array<{name: string}>>('plugins')
expect(stdout).to.contain(pluginShortName)
expect(result?.some((r) => r.name === plugin)).to.be.true
})

it('should uninstall plugin from local tarball', async () => {
await runCommand(`plugins uninstall ${plugin}`)
const {result, stdout} = await runCommand<Array<{name: string}>>('plugins')
expect(stdout).to.contain('No plugins installed.')
expect(result?.some((r) => r.name === plugin)).to.be.false
})
})

describe('multiple plugins sequentially', async () => {
/**
* This is a test for @W-21915680@, a bizarre bug wherein if you installed a plugin from the registry by its true name,
* and then installed a local tarball whose package name is alphabetically after the previous one, the local tarball
* would silently fail to install.
*/
it('handles local tarballs installed after simple plugin name', async () => {
// Install first plugin by its registered name
await runCommand(`plugins install ${otherPlugin}`)
const {result: firstResult, stdout: firstStdout} = await runCommand<Array<{name: string}>>('plugins')
expect(firstStdout).to.contain(otherPluginShortName)
expect(firstResult?.some((r) => r.name === otherPlugin)).to.be.true

// Install a second plugin by a local tarball. This one is alphabetically after the first one.
await runCommand(`plugins install "file://${yetAnotherLocalPluginTarball}"`)
const {result: secondResult, stdout: secondStdout} = await runCommand<Array<{name: string}>>('plugins')
expect(secondStdout).to.contain(yetAnotherPluginShortName)
expect(secondResult?.some((r) => r.name === otherPlugin)).to.be.true
expect(secondResult?.some((r) => r.name === yetAnotherPlugin)).to.be.true

// Install a third plugin by a local tarball. This one is alphabetically after the second one.
await runCommand(`plugins install "file://${pluginLocalTarball}`)
const {result: thirdResult, stdout: thirdStdout} = await runCommand<Array<{name: string}>>('plugins')
expect(thirdStdout).to.contain(pluginShortName)
expect(thirdResult?.some((r) => r.name === otherPlugin)).to.be.true
expect(thirdResult?.some((r) => r.name === yetAnotherPlugin)).to.be.true
expect(thirdResult?.some((r) => r.name === plugin)).to.be.true
})

it('uninstalls all plugins', async () => {
await runCommand(`plugins uninstall ${plugin}`)
await runCommand(`plugins uninstall ${otherPlugin}`)
await runCommand(`plugins uninstall ${yetAnotherPlugin}`)
const {result, stdout} = await runCommand<Array<{name: string}>>('plugins')
expect(stdout).to.contain('No plugins installed.')
expect(result?.some((r) => r.name === plugin)).to.be.false
expect(result?.some((r) => r.name === otherPlugin)).to.be.false
expect(result?.some((r) => r.name === yetAnotherPlugin)).to.be.false
})
})

describe('non-existent plugin', () => {
it('should not install non-existent plugin', async () => {
await runCommand('plugins install @oclif/DOES_NOT_EXIST')
Expand Down
Loading