Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,7 @@ USAGE

FLAGS
-v, --verbose Output verbose logs
--app=<value> The app ID (defaults to current app)
--app=<value> The app ID or name (defaults to current app)
--json Output in JSON format
--pretty-json Output in colorized JSON format

Expand Down Expand Up @@ -1081,7 +1081,7 @@ USAGE

FLAGS
-v, --verbose Output verbose logs
--app=<value> The app ID (defaults to current app)
--app=<value> The app ID or name (defaults to current app)
--json Output in JSON format
--pretty-json Output in colorized JSON format

Expand Down Expand Up @@ -1113,7 +1113,7 @@ ARGUMENTS

FLAGS
-v, --verbose Output verbose logs
--app=<value> The app ID (defaults to current app)
--app=<value> The app ID or name (defaults to current app)
--force Skip confirmation prompt
--json Output in JSON format
--pretty-json Output in colorized JSON format
Expand Down Expand Up @@ -1181,7 +1181,7 @@ ARGUMENTS

FLAGS
-v, --verbose Output verbose logs
--app=<value> The app ID (defaults to current app)
--app=<value> The app ID or name (defaults to current app)
--capabilities=<value> New capabilities for the key (comma-separated list)
--json Output in JSON format
--name=<value> New name for the key
Expand Down Expand Up @@ -1421,8 +1421,8 @@ Delete an annotation from a channel message

```
USAGE
$ ably channels annotations delete CHANNEL SERIAL TYPE [-v] [--json | --pretty-json] [--client-id <value>] [--count <value>]
[-n <value>]
$ ably channels annotations delete CHANNEL SERIAL TYPE [-v] [--json | --pretty-json] [--client-id <value>] [-n
<value>]

ARGUMENTS
CHANNEL The channel name
Expand All @@ -1434,7 +1434,6 @@ FLAGS
-v, --verbose Output verbose logs
--client-id=<value> Overrides any default client ID when using API authentication. Use "none" to explicitly set
no client ID. Not applicable when using token authentication.
--count=<value> The annotation count (for multiple.v1 types)
--json Output in JSON format
--pretty-json Output in colorized JSON format

Expand All @@ -1444,7 +1443,7 @@ DESCRIPTION
EXAMPLES
$ ably channels annotations delete my-channel "01234567890:0" "reactions:flag.v1" --name thumbsup

$ ably channels annotations delete my-channel "01234567890:0" "reactions:multiple.v1" --name thumbsup --count 2
$ ably channels annotations delete my-channel "01234567890:0" "reactions:multiple.v1" --name thumbsup

$ ably channels annotations delete my-channel "01234567890:0" "reactions:flag.v1" --json

Expand All @@ -1468,7 +1467,7 @@ ARGUMENTS
FLAGS
-v, --verbose Output verbose logs
--json Output in JSON format
--limit=<value> [default: 50] Maximum number of results to return (default: 50)
--limit=<value> [default: 100] Maximum number of results to return (default: 100)
--pretty-json Output in colorized JSON format

DESCRIPTION
Expand Down
10 changes: 1 addition & 9 deletions src/commands/auth/keys/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,7 @@ export default class KeysCreateCommand extends ControlBaseCommand {
async run(): Promise<void> {
const { flags } = await this.parse(KeysCreateCommand);

const appId = flags.app || this.configManager.getCurrentAppId();

if (!appId) {
this.fail(
'No app specified. Please provide --app flag or switch to an app with "ably apps switch".',
flags,
"keyCreate",
);
}
const appId = await this.requireAppId(flags);

let capabilities;
try {
Expand Down
18 changes: 5 additions & 13 deletions src/commands/auth/keys/current.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default class KeysCurrentCommand extends ControlBaseCommand {
static flags = {
...ControlBaseCommand.globalFlags,
app: Flags.string({
description: "The app ID (defaults to current app)",
description: "The app ID or name (defaults to current app)",
env: "ABLY_APP_ID",
}),
};
Expand All @@ -30,16 +30,8 @@ export default class KeysCurrentCommand extends ControlBaseCommand {
return this.handleWebCliMode(flags);
}

// Get app ID from flag or current config
const appId = flags.app || this.configManager.getCurrentAppId();

if (!appId) {
this.fail(
'No app specified. Please provide --app flag or switch to an app with "ably apps switch".',
flags,
"KeyCurrent",
);
}
// Get app ID from flag or current config (resolves app names to IDs)
const appId = await this.requireAppId(flags);

// Get the current key for this app
const apiKey = this.configManager.getApiKey(appId);
Expand All @@ -48,7 +40,7 @@ export default class KeysCurrentCommand extends ControlBaseCommand {
this.fail(
`No API key configured for app ${appId}. Use "ably auth keys switch" to select a key.`,
flags,
"KeyCurrent",
"keyCurrent",
);
}

Expand Down Expand Up @@ -105,7 +97,7 @@ export default class KeysCurrentCommand extends ControlBaseCommand {
this.fail(
"ABLY_API_KEY environment variable is not set",
flags,
"KeyCurrent",
"keyCurrent",
);
}

Expand Down
19 changes: 10 additions & 9 deletions src/commands/auth/keys/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ export default class KeysGetCommand extends ControlBaseCommand {
async run(): Promise<void> {
const { args, flags } = await this.parse(KeysGetCommand);

// Display authentication information
await this.showAuthInfoIfNeeded(flags);

let appId = flags.app || this.configManager.getCurrentAppId();
let appId: string | undefined;
const keyIdentifier = args.keyNameOrValue;

// If flags.app is set, resolve it (could be a name or ID)
if (flags.app) {
appId = await this.resolveAppIdFromNameOrId(flags.app, flags);
}

// If keyNameOrValue is in APP_ID.KEY_ID format (one period, no colon), extract appId.
// Only attempt this when no appId is already known (from --app flag or current app),
// to avoid misinterpreting labels containing periods (e.g. "v1.0") as APP_ID.KEY_ID.
Expand All @@ -51,13 +53,12 @@ export default class KeysGetCommand extends ControlBaseCommand {
}

if (!appId) {
this.fail(
'No app specified. Please provide --app flag, include APP_ID in the key name, or switch to an app with "ably apps switch".',
flags,
"keyGet",
);
appId = await this.requireAppId(flags);
}

// Display authentication information (after app resolution so name→ID is correct)
await this.showAuthInfoIfNeeded(flags);

try {
const controlApi = this.createControlApi(flags);
const key = await controlApi.getKey(appId, keyIdentifier);
Expand Down
19 changes: 6 additions & 13 deletions src/commands/auth/keys/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,20 @@ export default class KeysListCommand extends ControlBaseCommand {
static flags = {
...ControlBaseCommand.globalFlags,
app: Flags.string({
description: "The app ID (defaults to current app)",
description: "The app ID or name (defaults to current app)",
env: "ABLY_APP_ID",
}),
};

async run(): Promise<void> {
const { flags } = await this.parse(KeysListCommand);

// Display authentication information
await this.showAuthInfoIfNeeded(flags);

// Get app ID from flag or current config
const appId = flags.app || this.configManager.getCurrentAppId();
// Get app ID from flag or current config (resolves app names to IDs)
// Must resolve before showAuthInfoIfNeeded so --app names display correctly
const appId = await this.requireAppId(flags);

if (!appId) {
this.fail(
'No app specified. Please provide --app flag or switch to an app with "ably apps switch".',
flags,
"keyList",
);
}
// Display authentication information (after app resolution so name→ID is correct)
await this.showAuthInfoIfNeeded(flags);

try {
const controlApi = this.createControlApi(flags);
Expand Down
12 changes: 2 additions & 10 deletions src/commands/auth/keys/revoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default class KeysRevokeCommand extends ControlBaseCommand {
static flags = {
...ControlBaseCommand.globalFlags,
app: Flags.string({
description: "The app ID (defaults to current app)",
description: "The app ID or name (defaults to current app)",
env: "ABLY_APP_ID",
}),
force: Flags.boolean({
Expand All @@ -38,20 +38,12 @@ export default class KeysRevokeCommand extends ControlBaseCommand {
async run(): Promise<void> {
const { args, flags } = await this.parse(KeysRevokeCommand);

let appId = flags.app || this.configManager.getCurrentAppId();
let keyId = args.keyName;

const parsed = parseKeyIdentifier(args.keyName);
if (parsed.appId) appId = parsed.appId;
keyId = parsed.keyId;

if (!appId) {
this.fail(
'No app specified. Please provide --app flag, include APP_ID in the key name, or switch to an app with "ably apps switch".',
flags,
"keyRevoke",
);
}
const appId = parsed.appId ?? (await this.requireAppId(flags));

try {
const controlApi = this.createControlApi(flags);
Expand Down
13 changes: 3 additions & 10 deletions src/commands/auth/keys/switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,16 @@ export default class KeysSwitchCommand extends ControlBaseCommand {
async run(): Promise<void> {
const { args, flags } = await this.parse(KeysSwitchCommand);

// Get app ID from flag or current config
let appId = flags.app || this.configManager.getCurrentAppId();
let keyId: string | undefined = args.keyNameOrValue;
let extractedAppId: string | undefined;

if (args.keyNameOrValue) {
const parsed = parseKeyIdentifier(args.keyNameOrValue);
if (parsed.appId) appId = parsed.appId;
if (parsed.appId) extractedAppId = parsed.appId;
keyId = parsed.keyId;
}

if (!appId) {
this.fail(
'No app specified. Please provide --app flag, include APP_ID in the key name, or switch to an app with "ably apps switch".',
flags,
"keySwitch",
);
}
const appId = extractedAppId ?? (await this.requireAppId(flags));

try {
const controlApi = this.createControlApi(flags);
Expand Down
26 changes: 9 additions & 17 deletions src/commands/auth/keys/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default class KeysUpdateCommand extends ControlBaseCommand {
static flags = {
...ControlBaseCommand.globalFlags,
app: Flags.string({
description: "The app ID (defaults to current app)",
description: "The app ID or name (defaults to current app)",
env: "ABLY_APP_ID",
}),
capabilities: Flags.string({
Expand All @@ -40,22 +40,7 @@ export default class KeysUpdateCommand extends ControlBaseCommand {
async run(): Promise<void> {
const { args, flags } = await this.parse(KeysUpdateCommand);

let appId = flags.app || this.configManager.getCurrentAppId();
let keyId = args.keyName;

const parsed = parseKeyIdentifier(args.keyName);
if (parsed.appId) appId = parsed.appId;
keyId = parsed.keyId;

if (!appId) {
this.fail(
'No app specified. Please provide --app flag, include APP_ID in the key name, or switch to an app with "ably apps switch".',
flags,
"keyUpdate",
);
}

// Check if any update flags were provided
// Check if any update flags were provided before doing any API calls
if (!flags.name && !flags.capabilities) {
this.fail(
"No updates specified. Please provide at least one property to update (--name or --capabilities).",
Expand All @@ -64,6 +49,13 @@ export default class KeysUpdateCommand extends ControlBaseCommand {
);
}

let keyId = args.keyName;

const parsed = parseKeyIdentifier(args.keyName);
keyId = parsed.keyId;

const appId = parsed.appId ?? (await this.requireAppId(flags));

try {
const controlApi = this.createControlApi(flags);
// Get original key details
Expand Down
18 changes: 18 additions & 0 deletions test/helpers/control-api-test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ export function getControlApiContext() {
};
}

/**
* Mock the app resolution flow used by `requireAppId` / `resolveAppIdFromNameOrId`.
* Call this **before** other nock mocks in tests that pass `--app`.
* Mocks `GET /v1/me` and `GET /v1/accounts/{accountId}/apps`.
*/
export function mockAppResolution(appId: string): void {
const { accountId } = getControlApiContext();
nockControl()
.get("/v1/me")
.reply(200, {
account: { id: accountId, name: "Test Account" },
user: { email: "test@example.com" },
});
nockControl()
.get(`/v1/accounts/${accountId}/apps`)
.reply(200, [{ id: appId, name: "Test App", accountId }]);
}

/** Clean up all nock interceptors. Call in afterEach. */
export function controlApiCleanup(): void {
nock.cleanAll();
Expand Down
Loading
Loading