diff --git a/.git2gus/config.json b/.git2gus/config.json index 5314c7e8..55503790 100644 --- a/.git2gus/config.json +++ b/.git2gus/config.json @@ -5,6 +5,6 @@ "enhancement": "USER STORY", "bug": "BUG P3" }, - "hideWorkItemUrl": true, + "hideWorkItemUrl": "true", "statusWhenClosed": "CLOSED" } diff --git a/src/plugins.ts b/src/plugins.ts index 8bcfd7d2..75d98a0d 100644 --- a/src/plugins.ts +++ b/src/plugins.ts @@ -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 diff --git a/test/fixtures/oclif-plugin-update-v4.7.32.tgz b/test/fixtures/oclif-plugin-update-v4.7.32.tgz new file mode 100644 index 00000000..70e40fde Binary files /dev/null and b/test/fixtures/oclif-plugin-update-v4.7.32.tgz differ diff --git a/test/fixtures/oclif-plugin-version-v2.2.41.tgz b/test/fixtures/oclif-plugin-version-v2.2.41.tgz new file mode 100644 index 00000000..c84079ff Binary files /dev/null and b/test/fixtures/oclif-plugin-version-v2.2.41.tgz differ diff --git a/test/integration/install.integration.ts b/test/integration/install.integration.ts index 69e9c5d4..82ad76a0 100644 --- a/test/integration/install.integration.ts +++ b/test/integration/install.integration.ts @@ -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.32.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') @@ -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>('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>('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>('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>('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>('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>('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')