Skip to content

Commit 90d54f5

Browse files
ruibabyCopilot
andcommitted
Add --app-id flag to plugin install for App Store installation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 677a153 commit 90d54f5

File tree

3 files changed

+89
-25
lines changed

3 files changed

+89
-25
lines changed

src/commands/moment/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ async function ensureMomentsPluginInstalled(clients: HaloClients): Promise<void>
6262
}
6363
if (axios.isAxiosError(error) && error.response?.status === 404) {
6464
throw new CliError(
65-
`The ${MOMENTS_PLUGIN_NAME} plugin is not installed. Install it from the App Store or via: halo plugin install`,
65+
`The ${MOMENTS_PLUGIN_NAME} plugin is not installed. Install it from the App Store with: halo plugin install --app-id app-SnwWD`,
6666
);
6767
}
6868
throw error;

src/commands/plugin/__test__/plugin.spec.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,39 @@ afterEach(() => {
1515

1616
test("resolvePluginInstallSource accepts urls", () => {
1717
expect(resolvePluginInstallSource({ url: " https://example.com/plugin.jar " })).toEqual({
18+
kind: "url",
1819
url: "https://example.com/plugin.jar",
19-
file: undefined,
2020
});
2121
});
2222

2323
test("resolvePluginInstallSource accepts files", () => {
2424
expect(resolvePluginInstallSource({ file: " ./plugin.jar " })).toEqual({
25-
url: undefined,
25+
kind: "file",
2626
file: "./plugin.jar",
2727
});
2828
});
2929

30+
test("resolvePluginInstallSource accepts app-id", () => {
31+
expect(resolvePluginInstallSource({ appId: " app-SnwWD " })).toEqual({
32+
kind: "app-store",
33+
appId: "app-SnwWD",
34+
});
35+
});
36+
3037
test("resolvePluginInstallSource requires exactly one source", () => {
31-
expect(() => resolvePluginInstallSource({})).toThrow(/Provide either --url or --file/);
38+
expect(() => resolvePluginInstallSource({})).toThrow(/Provide exactly one install source/);
3239
expect(() =>
3340
resolvePluginInstallSource({
3441
url: "https://example.com/plugin.jar",
3542
file: "./plugin.jar",
3643
}),
37-
).toThrow(/Use only one plugin source/);
44+
).toThrow(/Use only one plugin install source/);
45+
expect(() =>
46+
resolvePluginInstallSource({
47+
url: "https://example.com/plugin.jar",
48+
appId: "app-SnwWD",
49+
}),
50+
).toThrow(/Use only one plugin install source/);
3851
});
3952

4053
test("confirmPluginMutation skips prompting with --force", async () => {

src/commands/plugin/index.ts

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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

211225
function 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

Comments
 (0)