@@ -2,13 +2,36 @@ import {runCommand} from '@oclif/test'
22import { dim } from 'ansis'
33import { expect } from 'chai'
44import { rm } from 'node:fs/promises'
5- import { join , resolve } from 'node:path'
5+ import { dirname , join , resolve } from 'node:path'
6+ import { fileURLToPath } from 'node:url'
7+
8+ const __dirname = dirname ( fileURLToPath ( import . meta. url ) )
69
710describe ( 'install/uninstall integration tests' , ( ) => {
811 const plugin = '@oclif/plugin-version'
912 const pluginShortName = 'version'
1013 const pluginGithubSlug = 'oclif/plugin-version'
1114 const pluginGithubUrl = 'https://github.com/oclif/plugin-version.git'
15+ let pluginLocalTarball = resolve ( __dirname , '..' , 'fixtures' , 'oclif-plugin-version-v2.2.41.tgz' )
16+ // Normalize the path to ensure Windows compatibility
17+ . replaceAll ( '\\' , '/' )
18+ // If the path starts with 'C:', that needs to be removed
19+ if ( pluginLocalTarball . startsWith ( 'C:' ) ) {
20+ pluginLocalTarball = pluginLocalTarball . slice ( 2 )
21+ }
22+
23+ const otherPlugin = '@oclif/plugin-search'
24+ const otherPluginShortName = 'search'
25+
26+ const yetAnotherPlugin = '@oclif/plugin-update'
27+ const yetAnotherPluginShortName = 'update'
28+ let yetAnotherLocalPluginTarball = resolve ( __dirname , '..' , 'fixtures' , 'oclif-plugin-update-v4.7.32.tgz' )
29+ // Normalize the path to ensure Windows compatibility
30+ . replaceAll ( '\\' , '/' )
31+ // If the path starts with 'C:', that needs to be removed
32+ if ( yetAnotherLocalPluginTarball . startsWith ( 'C:' ) ) {
33+ yetAnotherLocalPluginTarball = yetAnotherLocalPluginTarball . slice ( 2 )
34+ }
1235
1336 const tmp = resolve ( 'tmp' , 'install-integration' )
1437 const cacheDir = join ( tmp , 'plugin-plugins-tests' , 'cache' )
@@ -146,6 +169,63 @@ describe('install/uninstall integration tests', () => {
146169 } )
147170 } )
148171
172+ describe ( 'local tarball' , ( ) => {
173+ it ( 'should install plugin from locally hosted tarball' , async ( ) => {
174+ await runCommand ( `plugins install "file://${ pluginLocalTarball } ` )
175+ const { result, stdout} = await runCommand < Array < { name : string } > > ( 'plugins' )
176+ expect ( stdout ) . to . contain ( pluginShortName )
177+ expect ( result ?. some ( ( r ) => r . name === plugin ) ) . to . be . true
178+ } )
179+
180+ it ( 'should uninstall plugin from local tarball' , async ( ) => {
181+ await runCommand ( `plugins uninstall ${ plugin } ` )
182+ const { result, stdout} = await runCommand < Array < { name : string } > > ( 'plugins' )
183+ expect ( stdout ) . to . contain ( 'No plugins installed.' )
184+ expect ( result ?. some ( ( r ) => r . name === plugin ) ) . to . be . false
185+ } )
186+ } )
187+
188+ describe ( 'multiple plugins sequentially' , async ( ) => {
189+ /**
190+ * This is a test for @W-21915680@, a bizarre bug wherein if you installed a plugin from the registry by its true name,
191+ * and then installed a local tarball whose package name is alphabetically after the previous one, the local tarball
192+ * would silently fail to install.
193+ */
194+ it ( 'handles local tarballs installed after simple plugin name' , async ( ) => {
195+ // Install first plugin by its registered name
196+ await runCommand ( `plugins install ${ otherPlugin } ` )
197+ const { result : firstResult , stdout : firstStdout } = await runCommand < Array < { name : string } > > ( 'plugins' )
198+ expect ( firstStdout ) . to . contain ( otherPluginShortName )
199+ expect ( firstResult ?. some ( ( r ) => r . name === otherPlugin ) ) . to . be . true
200+
201+ // Install a second plugin by a local tarball. This one is alphabetically after the first one.
202+ await runCommand ( `plugins install "file://${ yetAnotherLocalPluginTarball } "` )
203+ const { result : secondResult , stdout : secondStdout } = await runCommand < Array < { name : string } > > ( 'plugins' )
204+ expect ( secondStdout ) . to . contain ( yetAnotherPluginShortName )
205+ expect ( secondResult ?. some ( ( r ) => r . name === otherPlugin ) ) . to . be . true
206+ expect ( secondResult ?. some ( ( r ) => r . name === yetAnotherPlugin ) ) . to . be . true
207+
208+ // Install a third plugin by a local tarball. This one is alphabetically after the second one.
209+ await runCommand ( `plugins install "file://${ pluginLocalTarball } ` )
210+ const { result : thirdResult , stdout : thirdStdout } = await runCommand < Array < { name : string } > > ( 'plugins' )
211+ expect ( thirdStdout ) . to . contain ( pluginShortName )
212+ expect ( thirdResult ?. some ( ( r ) => r . name === otherPlugin ) ) . to . be . true
213+ expect ( thirdResult ?. some ( ( r ) => r . name === yetAnotherPlugin ) ) . to . be . true
214+ expect ( thirdResult ?. some ( ( r ) => r . name === plugin ) ) . to . be . true
215+ } )
216+
217+ it ( 'uninstalls all plugins' , async ( ) => {
218+ await runCommand ( `plugins uninstall ${ plugin } ` )
219+ await runCommand ( `plugins uninstall ${ otherPlugin } ` )
220+ await runCommand ( `plugins uninstall ${ yetAnotherPlugin } ` )
221+ const { result, stdout} = await runCommand < Array < { name : string } > > ( 'plugins' )
222+ expect ( stdout ) . to . contain ( 'No plugins installed.' )
223+ expect ( result ?. some ( ( r ) => r . name === plugin ) ) . to . be . false
224+ expect ( result ?. some ( ( r ) => r . name === otherPlugin ) ) . to . be . false
225+ expect ( result ?. some ( ( r ) => r . name === yetAnotherPlugin ) ) . to . be . false
226+ } )
227+ } )
228+
149229 describe ( 'non-existent plugin' , ( ) => {
150230 it ( 'should not install non-existent plugin' , async ( ) => {
151231 await runCommand ( 'plugins install @oclif/DOES_NOT_EXIST' )
0 commit comments