From 454c390dc8980cb508d1fedc3cd81edef47e8068 Mon Sep 17 00:00:00 2001 From: umair Date: Tue, 24 Mar 2026 10:35:15 +0000 Subject: [PATCH] remove deprecated set-apns-p12 command path, use promptForConfirmation helper, and fix clear FCM --- README.md | 4 +- src/commands/apps/index.ts | 1 - src/commands/apps/set-apns-p12.ts | 107 ------------ src/commands/push/channels/remove-where.ts | 13 +- src/commands/push/channels/remove.ts | 13 +- src/commands/push/config/clear-apns.ts | 13 +- src/commands/push/config/clear-fcm.ts | 14 +- src/commands/push/devices/remove-where.ts | 13 +- src/commands/push/devices/remove.ts | 13 +- test/unit/commands/apps/set-apns-p12.test.ts | 175 ------------------- 10 files changed, 27 insertions(+), 339 deletions(-) delete mode 100644 src/commands/apps/set-apns-p12.ts delete mode 100644 test/unit/commands/apps/set-apns-p12.test.ts diff --git a/README.md b/README.md index 22f37a3c..3418bb1c 100644 --- a/README.md +++ b/README.md @@ -433,8 +433,6 @@ EXAMPLES $ ably apps delete - $ ably apps set-apns-p12 - $ ably apps rules list $ ably apps switch my-app @@ -2037,6 +2035,8 @@ EXAMPLES $ ably channels presence update my-channel --data '{"status":"busy"}' --json + $ ably channels presence update my-channel --data '{"status":"busy"}' --pretty-json + $ ably channels presence update my-channel --data '{"status":"online"}' --duration 60 ``` diff --git a/src/commands/apps/index.ts b/src/commands/apps/index.ts index 143bac14..38b568a6 100644 --- a/src/commands/apps/index.ts +++ b/src/commands/apps/index.ts @@ -11,7 +11,6 @@ export default class AppsCommand extends BaseTopicCommand { "$ ably apps create", "$ ably apps update", "$ ably apps delete", - "$ ably apps set-apns-p12", "$ ably apps rules list", "$ ably apps switch my-app", ]; diff --git a/src/commands/apps/set-apns-p12.ts b/src/commands/apps/set-apns-p12.ts deleted file mode 100644 index c129ed0a..00000000 --- a/src/commands/apps/set-apns-p12.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Args, Flags } from "@oclif/core"; -import * as fs from "node:fs"; -import * as path from "node:path"; - -import { ControlBaseCommand } from "../../control-base-command.js"; -import { - formatLabel, - formatProgress, - formatResource, - formatSuccess, - formatWarning, -} from "../../utils/output.js"; - -export default class AppsSetApnsP12Command extends ControlBaseCommand { - static args = { - id: Args.string({ - description: "App ID to set the APNS certificate for", - required: true, - }), - }; - - static description = - "Upload Apple Push Notification Service P12 certificate for an app"; - - static hidden = true; - - static examples = [ - "$ ably apps set-apns-p12 app-id --certificate /path/to/certificate.p12", - '$ ably apps set-apns-p12 app-id --certificate /path/to/certificate.p12 --password "YOUR_CERTIFICATE_PASSWORD"', - "$ ably apps set-apns-p12 app-id --certificate /path/to/certificate.p12 --use-for-sandbox", - "$ ably apps set-apns-p12 app-id --certificate /path/to/certificate.p12 --json", - ]; - - static flags = { - ...ControlBaseCommand.globalFlags, - certificate: Flags.string({ - description: "Path to the P12 certificate file", - required: true, - }), - password: Flags.string({ - description: "Password for the P12 certificate", - }), - "use-for-sandbox": Flags.boolean({ - default: false, - description: - "Whether to use this certificate for the APNS sandbox environment", - }), - }; - - async run(): Promise { - const { args, flags } = await this.parse(AppsSetApnsP12Command); - - if (!this.shouldOutputJson(flags)) { - this.logToStderr( - formatWarning( - 'This command is deprecated. Use "ably push config set-apns" instead.', - ), - ); - } - - // Display authentication information - this.showAuthInfoIfNeeded(flags); - - try { - const controlApi = this.createControlApi(flags); - // Validate certificate file exists - const certificatePath = path.resolve(flags.certificate); - if (!fs.existsSync(certificatePath)) { - this.fail( - `Certificate file not found: ${certificatePath}`, - flags, - "appSetApnsP12", - ); - } - - this.log( - formatProgress( - `Uploading APNS P12 certificate for app ${formatResource(args.id)}`, - ), - ); - - // Read certificate file and encode as base64 - const certificateData = fs - .readFileSync(certificatePath) - .toString("base64"); - - const result = await controlApi.uploadApnsP12(args.id, certificateData, { - password: flags.password, - useForSandbox: flags["use-for-sandbox"], - }); - - if (this.shouldOutputJson(flags)) { - this.logJsonResult(result, flags); - } else { - this.log(formatSuccess("APNS P12 certificate uploaded.")); - this.log(`${formatLabel("Certificate ID")} ${result.id}`); - if (flags["use-for-sandbox"]) { - this.log(`${formatLabel("Environment")} Sandbox`); - } else { - this.log(`${formatLabel("Environment")} Production`); - } - } - } catch (error) { - this.fail(error, flags, "appSetApnsP12"); - } - } -} diff --git a/src/commands/push/channels/remove-where.ts b/src/commands/push/channels/remove-where.ts index 69f515bb..f2b970de 100644 --- a/src/commands/push/channels/remove-where.ts +++ b/src/commands/push/channels/remove-where.ts @@ -1,5 +1,4 @@ import { Flags } from "@oclif/core"; -import inquirer from "inquirer"; import { AblyBaseCommand } from "../../../base-command.js"; import { forceFlag, productApiFlags } from "../../../flags.js"; @@ -9,6 +8,7 @@ import { formatResource, formatSuccess, } from "../../../utils/output.js"; +import { promptForConfirmation } from "../../../utils/prompt-confirmation.js"; export default class PushChannelsRemoveWhere extends AblyBaseCommand { static override description = @@ -53,14 +53,9 @@ export default class PushChannelsRemoveWhere extends AblyBaseCommand { .map(([k, v]) => `${k}=${v}`) .join(", "); - const { confirmed } = await inquirer.prompt([ - { - type: "confirm", - name: "confirmed", - message: `Are you sure you want to remove all subscriptions matching: ${filterDesc}?`, - default: false, - }, - ]); + const confirmed = await promptForConfirmation( + `Are you sure you want to remove all subscriptions matching: ${filterDesc}?`, + ); if (!confirmed) { this.log("Operation cancelled."); diff --git a/src/commands/push/channels/remove.ts b/src/commands/push/channels/remove.ts index 93465640..21fd3103 100644 --- a/src/commands/push/channels/remove.ts +++ b/src/commands/push/channels/remove.ts @@ -1,5 +1,4 @@ import { Flags } from "@oclif/core"; -import inquirer from "inquirer"; import { AblyBaseCommand } from "../../../base-command.js"; import { forceFlag, productApiFlags } from "../../../flags.js"; @@ -9,6 +8,7 @@ import { formatResource, formatSuccess, } from "../../../utils/output.js"; +import { promptForConfirmation } from "../../../utils/prompt-confirmation.js"; export default class PushChannelsRemove extends AblyBaseCommand { static override description = "Remove a push channel subscription"; @@ -56,14 +56,9 @@ export default class PushChannelsRemove extends AblyBaseCommand { : `client ${flags["client-id"]}`; if (!flags.force && !this.shouldOutputJson(flags)) { - const { confirmed } = await inquirer.prompt([ - { - type: "confirm", - name: "confirmed", - message: `Are you sure you want to unsubscribe ${target} from channel ${flags.channel}?`, - default: false, - }, - ]); + const confirmed = await promptForConfirmation( + `Are you sure you want to unsubscribe ${target} from channel ${flags.channel}?`, + ); if (!confirmed) { this.log("Operation cancelled."); diff --git a/src/commands/push/config/clear-apns.ts b/src/commands/push/config/clear-apns.ts index 08310f5c..1f3b198d 100644 --- a/src/commands/push/config/clear-apns.ts +++ b/src/commands/push/config/clear-apns.ts @@ -1,5 +1,4 @@ import { Flags } from "@oclif/core"; -import inquirer from "inquirer"; import { ControlBaseCommand } from "../../../control-base-command.js"; import { forceFlag } from "../../../flags.js"; @@ -9,6 +8,7 @@ import { formatSuccess, formatWarning, } from "../../../utils/output.js"; +import { promptForConfirmation } from "../../../utils/prompt-confirmation.js"; export default class PushConfigClearApns extends ControlBaseCommand { static override description = @@ -37,14 +37,9 @@ export default class PushConfigClearApns extends ControlBaseCommand { const appId = await this.requireAppId(flags); if (!flags.force && !this.shouldOutputJson(flags)) { - const { confirmed } = await inquirer.prompt([ - { - default: false, - message: `Are you sure you want to clear APNs configuration for app ${formatResource(appId)}?`, - name: "confirmed", - type: "confirm", - }, - ]); + const confirmed = await promptForConfirmation( + `Are you sure you want to clear APNs configuration for app ${formatResource(appId)}?`, + ); if (!confirmed) { this.log("Operation cancelled."); diff --git a/src/commands/push/config/clear-fcm.ts b/src/commands/push/config/clear-fcm.ts index 3f886cab..e9743f7d 100644 --- a/src/commands/push/config/clear-fcm.ts +++ b/src/commands/push/config/clear-fcm.ts @@ -1,5 +1,4 @@ import { Flags } from "@oclif/core"; -import inquirer from "inquirer"; import { ControlBaseCommand } from "../../../control-base-command.js"; import { forceFlag } from "../../../flags.js"; @@ -9,6 +8,7 @@ import { formatSuccess, formatWarning, } from "../../../utils/output.js"; +import { promptForConfirmation } from "../../../utils/prompt-confirmation.js"; export default class PushConfigClearFcm extends ControlBaseCommand { static override description = @@ -37,14 +37,9 @@ export default class PushConfigClearFcm extends ControlBaseCommand { const appId = await this.requireAppId(flags); if (!flags.force && !this.shouldOutputJson(flags)) { - const { confirmed } = await inquirer.prompt([ - { - default: false, - message: `Are you sure you want to clear FCM configuration for app ${formatResource(appId)}?`, - name: "confirmed", - type: "confirm", - }, - ]); + const confirmed = await promptForConfirmation( + `Are you sure you want to clear FCM configuration for app ${formatResource(appId)}?`, + ); if (!confirmed) { this.log("Operation cancelled."); @@ -88,6 +83,7 @@ export default class PushConfigClearFcm extends ControlBaseCommand { } await controlApi.updateApp(appId, { + fcmProjectId: null, fcmServiceAccount: null, }); diff --git a/src/commands/push/devices/remove-where.ts b/src/commands/push/devices/remove-where.ts index 5352c344..d6e75a7c 100644 --- a/src/commands/push/devices/remove-where.ts +++ b/src/commands/push/devices/remove-where.ts @@ -1,10 +1,10 @@ import { Flags } from "@oclif/core"; -import inquirer from "inquirer"; import { AblyBaseCommand } from "../../../base-command.js"; import { forceFlag, productApiFlags } from "../../../flags.js"; import { BaseFlags } from "../../../types/cli.js"; import { formatProgress, formatSuccess } from "../../../utils/output.js"; +import { promptForConfirmation } from "../../../utils/prompt-confirmation.js"; export default class PushDevicesRemoveWhere extends AblyBaseCommand { static override description = @@ -51,14 +51,9 @@ export default class PushDevicesRemoveWhere extends AblyBaseCommand { .map(([k, v]) => `${k}=${v}`) .join(", "); - const { confirmed } = await inquirer.prompt([ - { - type: "confirm", - name: "confirmed", - message: `Are you sure you want to remove all devices matching: ${filterDesc}?`, - default: false, - }, - ]); + const confirmed = await promptForConfirmation( + `Are you sure you want to remove all devices matching: ${filterDesc}?`, + ); if (!confirmed) { this.log("Operation cancelled."); diff --git a/src/commands/push/devices/remove.ts b/src/commands/push/devices/remove.ts index f448cf29..ecadce31 100644 --- a/src/commands/push/devices/remove.ts +++ b/src/commands/push/devices/remove.ts @@ -1,5 +1,4 @@ import { Args } from "@oclif/core"; -import inquirer from "inquirer"; import { AblyBaseCommand } from "../../../base-command.js"; import { forceFlag, productApiFlags } from "../../../flags.js"; @@ -9,6 +8,7 @@ import { formatResource, formatSuccess, } from "../../../utils/output.js"; +import { promptForConfirmation } from "../../../utils/prompt-confirmation.js"; export default class PushDevicesRemove extends AblyBaseCommand { static override args = { @@ -40,14 +40,9 @@ export default class PushDevicesRemove extends AblyBaseCommand { if (!rest) return; if (!flags.force && !this.shouldOutputJson(flags)) { - const { confirmed } = await inquirer.prompt([ - { - type: "confirm", - name: "confirmed", - message: `Are you sure you want to remove device ${deviceId}?`, - default: false, - }, - ]); + const confirmed = await promptForConfirmation( + `Are you sure you want to remove device ${deviceId}?`, + ); if (!confirmed) { this.log("Operation cancelled."); diff --git a/test/unit/commands/apps/set-apns-p12.test.ts b/test/unit/commands/apps/set-apns-p12.test.ts deleted file mode 100644 index 6239c4e4..00000000 --- a/test/unit/commands/apps/set-apns-p12.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { runCommand } from "@oclif/test"; -import { - nockControl, - controlApiCleanup, -} from "../../../helpers/control-api-test-helpers.js"; -import { resolve } from "node:path"; -import { mkdirSync, writeFileSync, existsSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { getMockConfigManager } from "../../../helpers/mock-config-manager.js"; -import { - standardHelpTests, - standardArgValidationTests, - standardFlagTests, -} from "../../../helpers/standard-tests.js"; - -describe("apps:set-apns-p12 command", () => { - let testTempDir: string; - let testCertFile: string; - - beforeEach(() => { - // Create temp directory for test certificate file - testTempDir = resolve(tmpdir(), `ably-cli-test-apns-p12-${Date.now()}`); - mkdirSync(testTempDir, { recursive: true, mode: 0o700 }); - - // Create a fake certificate file - testCertFile = resolve(testTempDir, "test-cert.p12"); - writeFileSync(testCertFile, "fake-certificate-data"); - }); - - afterEach(() => { - controlApiCleanup(); - - if (existsSync(testTempDir)) { - rmSync(testTempDir, { recursive: true, force: true }); - } - }); - - describe("functionality", () => { - it("should upload APNS P12 certificate successfully", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl().post(`/v1/apps/${appId}/pkcs12`).reply(200, { - id: "cert-123", - appId, - }); - - const { stdout } = await runCommand( - ["apps:set-apns-p12", appId, "--certificate", testCertFile], - import.meta.url, - ); - - expect(stdout).toContain("APNS P12 certificate uploaded."); - }); - - it("should upload certificate with password", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl().post(`/v1/apps/${appId}/pkcs12`).reply(200, { - id: "cert-123", - appId, - }); - - const { stdout } = await runCommand( - [ - "apps:set-apns-p12", - appId, - "--certificate", - testCertFile, - "--password", - "test-password", - ], - import.meta.url, - ); - - expect(stdout).toContain("APNS P12 certificate uploaded."); - }); - - it("should upload certificate for sandbox environment", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl().post(`/v1/apps/${appId}/pkcs12`).reply(200, { - id: "cert-123", - appId, - }); - nockControl().patch(`/v1/apps/${appId}`).reply(200, { id: appId }); - - const { stdout } = await runCommand( - [ - "apps:set-apns-p12", - appId, - "--certificate", - testCertFile, - "--use-for-sandbox", - ], - import.meta.url, - ); - - expect(stdout).toContain("APNS P12 certificate uploaded."); - expect(stdout).toContain("Sandbox"); - }); - }); - - standardHelpTests("apps:set-apns-p12", import.meta.url); - standardArgValidationTests("apps:set-apns-p12", import.meta.url, { - requiredArgs: ["test-app-id"], - }); - standardFlagTests("apps:set-apns-p12", import.meta.url, ["--json"]); - - describe("error handling", () => { - it("should require app ID argument", async () => { - const { error } = await runCommand( - ["apps:set-apns-p12", "--certificate", testCertFile], - import.meta.url, - ); - - expect(error).toBeDefined(); - expect(error?.message).toMatch(/Missing 1 required arg/); - }); - - it("should require certificate flag", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - const { error } = await runCommand( - ["apps:set-apns-p12", appId], - import.meta.url, - ); - - expect(error).toBeDefined(); - expect(error?.message).toMatch(/Missing required flag.*certificate/); - }); - - it("should error when certificate file does not exist", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - const { error } = await runCommand( - [ - "apps:set-apns-p12", - appId, - "--certificate", - "/nonexistent/path/cert.p12", - ], - import.meta.url, - ); - - expect(error).toBeDefined(); - expect(error?.message).toMatch(/not found/); - }); - - it("should handle API errors", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl() - .post(`/v1/apps/${appId}/pkcs12`) - .reply(400, { error: "Invalid certificate" }); - - const { error } = await runCommand( - ["apps:set-apns-p12", appId, "--certificate", testCertFile], - import.meta.url, - ); - - expect(error).toBeDefined(); - expect(error?.message).toMatch(/400/); - }); - - it("should handle 401 authentication error", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl() - .post(`/v1/apps/${appId}/pkcs12`) - .reply(401, { error: "Unauthorized" }); - - const { error } = await runCommand( - ["apps:set-apns-p12", appId, "--certificate", testCertFile], - import.meta.url, - ); - - expect(error).toBeDefined(); - expect(error?.message).toMatch(/401/); - }); - }); -});