@@ -32,6 +32,7 @@ interface PluginCommandOptions {
3232 enabled ?: string ;
3333 url ?: string ;
3434 file ?: string ;
35+ appId ?: string ;
3536 online ?: boolean ;
3637 all ?: boolean ;
3738 yes ?: boolean ;
@@ -190,22 +191,35 @@ function createSpinnerReporter(json = false): SpinnerReporter {
190191 return new SpinnerReporter ( process . stdout . isTTY && ! json ) ;
191192}
192193
193- export function resolvePluginInstallSource ( options : PluginCommandOptions ) : {
194- url ?: string ;
195- file ?: string ;
196- } {
194+ export type PluginInstallSource =
195+ | { kind : "url" ; url : string }
196+ | { kind : "file" ; file : string }
197+ | { kind : "app-store" ; appId : string } ;
198+
199+ export function resolvePluginInstallSource ( options : PluginCommandOptions ) : PluginInstallSource {
197200 const url = options . url ?. trim ( ) ;
198201 const file = options . file ?. trim ( ) ;
202+ const appId = options . appId ?. trim ( ) ;
203+
204+ const sourceCount = Number ( Boolean ( url ) ) + Number ( Boolean ( file ) ) + Number ( Boolean ( appId ) ) ;
205+
206+ if ( sourceCount === 0 ) {
207+ throw new CliError ( "Provide exactly one install source: --url, --file, or --app-id." ) ;
208+ }
209+
210+ if ( sourceCount > 1 ) {
211+ throw new CliError ( "Use only one plugin install source: --url, --file, or --app-id." ) ;
212+ }
199213
200- if ( ! url && ! file ) {
201- throw new CliError ( "Provide either -- url or --file." ) ;
214+ if ( url ) {
215+ return { kind : "url" , url } ;
202216 }
203217
204- if ( url && file ) {
205- throw new CliError ( "Use only one plugin source: --url or -- file." ) ;
218+ if ( file ) {
219+ return { kind : " file" , file } ;
206220 }
207221
208- return { url , file } ;
222+ return { kind : "app-store" , appId : appId ! } ;
209223}
210224
211225function getPluginMutationVerb ( action : "enable" | "disable" | "uninstall" ) : string {
@@ -669,15 +683,17 @@ function buildPluginCli(runtime: RuntimeContext): CAC {
669683 . option ( "--json" , "Output JSON" )
670684 . option ( "--url <url>" , "Remote JAR URL" )
671685 . option ( "--file <path>" , "Local JAR file path" )
672- . option ( "-y, --yes" , "Skip third-party URL confirmation" )
686+ . option ( "--app-id <id>" , "Halo App Store application ID" )
687+ . option ( "-y, --yes" , "Skip confirmation prompts" )
673688 . action ( async ( options : PluginCommandOptions ) => {
674689 if ( options . online ) {
675- throw new CliError ( "`halo plugin install` does not support --online. Use --url or --file ." ) ;
690+ throw new CliError ( "`halo plugin install` does not support --online. Use --app-id ." ) ;
676691 }
677692
678693 const source = resolvePluginInstallSource ( options ) ;
694+
679695 if (
680- source . url &&
696+ source . kind === " url" &&
681697 ! ( await confirmThirdPartyPackageSource (
682698 source . url ,
683699 {
@@ -691,20 +707,54 @@ function buildPluginCli(runtime: RuntimeContext): CAC {
691707 }
692708
693709 const { clients } = await runtime . getClientsForOptions ( options ) ;
694- const response = source . url
695- ? await clients . console . plugin . plugin . installPluginFromUri ( {
710+ const spinner = createSpinnerReporter ( options . json ) ;
711+ let response ;
712+
713+ try {
714+ if ( source . kind === "url" ) {
715+ spinner . start ( "Installing plugin from remote URL..." ) ;
716+ response = await clients . console . plugin . plugin . installPluginFromUri ( {
696717 installFromUriRequest : { uri : source . url } ,
697- } )
698- : await clients . console . plugin . plugin . installPlugin ( {
699- file : await loadFileAsJar ( source . file ! ) ,
700718 } ) ;
719+ spinner . succeed ( `Installed plugin ${ response . data . metadata . name } .` ) ;
720+ } else if ( source . kind === "file" ) {
721+ spinner . start ( "Uploading local plugin package..." ) ;
722+ response = await clients . console . plugin . plugin . installPlugin ( {
723+ file : await loadFileAsJar ( source . file ) ,
724+ } ) ;
725+ spinner . succeed ( `Installed plugin ${ response . data . metadata . name } .` ) ;
726+ } else {
727+ spinner . start ( `Resolving App Store package for ${ source . appId } ...` ) ;
728+ const appStoreClient = await createAppStoreClient ( clients ) ;
729+ const release = await resolveLatestAppStoreRelease ( appStoreClient , source . appId ) ;
730+ spinner . stop ( ) ;
731+
732+ const confirmed = await confirmAppStoreReleaseReview (
733+ {
734+ commandPath : "halo plugin install" ,
735+ actionLabel : `installing plugin from App Store` ,
736+ items : [ { name : source . appId , releaseUrl : release . releaseUrl } ] ,
737+ } ,
738+ options ,
739+ ) ;
740+
741+ if ( ! confirmed ) {
742+ return ;
743+ }
744+
745+ spinner . start ( `Installing plugin ${ source . appId } from Halo App Store...` ) ;
746+ response = await clients . console . plugin . plugin . installPluginFromUri ( {
747+ installFromUriRequest : { uri : release . downloadUrl } ,
748+ } ) ;
749+ spinner . succeed ( `Installed plugin ${ response . data . metadata . name } .` ) ;
750+ }
751+ } finally {
752+ spinner . stop ( ) ;
753+ }
701754
702755 if ( options . json ) {
703756 printJson ( response . data ) ;
704- return ;
705757 }
706-
707- process . stdout . write ( `Installed plugin ${ response . data . metadata . name } .\n` ) ;
708758 } ) ;
709759
710760 pluginCli
@@ -818,6 +868,7 @@ function buildPluginCli(runtime: RuntimeContext): CAC {
818868 pluginCli . example ( ( bin ) => `${ bin } disable PluginName --force` ) ;
819869 pluginCli . example ( ( bin ) => `${ bin } uninstall PluginName --force` ) ;
820870 pluginCli . example ( ( bin ) => `${ bin } install --url https://example.com/plugin.jar` ) ;
871+ pluginCli . example ( ( bin ) => `${ bin } install --app-id app-SnwWD` ) ;
821872 pluginCli . example ( ( bin ) => `${ bin } upgrade PluginName --online` ) ;
822873 pluginCli . example ( ( bin ) => `${ bin } upgrade --all --online --yes` ) ;
823874 pluginCli . help ( ) ;
0 commit comments