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
4 changes: 2 additions & 2 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
env:
NO_COLOR: 1
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ABLY_ACCESS_TOKEN }}
TERMINAL_SERVER_SIGNING_SECRET: ${{ secrets.TERMINAL_SERVER_SIGNING_SECRET }}
E2E_TESTS: true

Expand Down Expand Up @@ -69,7 +69,7 @@ jobs:
run: |
echo "ABLY_API_KEY=${{ secrets.E2E_ABLY_API_KEY }}" > .env.test
echo "E2E_ABLY_API_KEY=${{ secrets.E2E_ABLY_API_KEY }}" >> .env.test
echo "E2E_ABLY_ACCESS_TOKEN=${{ secrets.E2E_ABLY_ACCESS_TOKEN }}" >> .env.test
echo "E2E_ABLY_ACCESS_TOKEN=${{ secrets.E2E_ABLY_ABLY_ACCESS_TOKEN }}" >> .env.test
echo "TERMINAL_SERVER_SIGNING_SECRET=${{ secrets.TERMINAL_SERVER_SIGNING_SECRET }}" >> .env.test

- name: Run All E2E CLI Tests
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/e2e-web-cli-parallel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
env:
NO_COLOR: 1
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ABLY_ACCESS_TOKEN }}
TERMINAL_SERVER_SIGNING_SECRET: ${{ secrets.TERMINAL_SERVER_SIGNING_SECRET }}
E2E_TESTS: true
HEADLESS: true
Expand Down Expand Up @@ -150,7 +150,7 @@ jobs:
env:
NO_COLOR: 1
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ABLY_ACCESS_TOKEN }}
TERMINAL_SERVER_SIGNING_SECRET: ${{ secrets.TERMINAL_SERVER_SIGNING_SECRET }}
E2E_TESTS: true
HEADLESS: true
Expand Down Expand Up @@ -216,7 +216,7 @@ jobs:
env:
NO_COLOR: 1
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ABLY_ACCESS_TOKEN }}
TERMINAL_SERVER_SIGNING_SECRET: ${{ secrets.TERMINAL_SERVER_SIGNING_SECRET }}
E2E_TESTS: true
HEADLESS: true
Expand Down Expand Up @@ -282,7 +282,7 @@ jobs:
env:
NO_COLOR: 1
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ABLY_ACCESS_TOKEN }}
# Terminal Server Signing Secret is intentionally not set
# Rate limit test should NOT use terminal server signing secret.
E2E_TESTS: true
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,16 @@ jobs:
run: pnpm test:unit
env:
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ABLY_ACCESS_TOKEN }}

- name: Run CLI Hooks Unit Tests
run: pnpm test:hooks
env:
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ABLY_ACCESS_TOKEN }}

- name: Run CLI Integration Tests
run: pnpm test:integration
env:
E2E_ABLY_API_KEY: ${{ secrets.E2E_ABLY_API_KEY }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ACCESS_TOKEN }}
E2E_ABLY_ACCESS_TOKEN: ${{ secrets.E2E_ABLY_ABLY_ACCESS_TOKEN }}
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,10 @@ USAGE
FLAGS
-v, --verbose Output verbose logs
--app=<value> [env: ABLY_APP_ID] The app ID or name (defaults to current app)
--capabilities=<value> [default: {"*":["*"]}] Capability object as a JSON string. Example:
--capabilities=<value> [default: {"[*]*":["subscribe","publish","presence","history","channel-metadata","privileg
ed-headers","statistics","push-subscribe","push-admin","annotation-publish","annotation-su
bscribe","message-update-own","message-update-any","message-delete-own","message-delete-an
y","object-publish","object-subscribe"]}] Capability object as a JSON string. Example:
'{"channel:*":["publish"]}'
--json Output in JSON format
--name=<value> (required) Name of the key
Expand All @@ -1000,7 +1003,7 @@ EXAMPLES

$ ably auth keys create --name "My New Key" --app APP_ID

$ ably auth keys create --name "My New Key" --capabilities '{"*":["*"]}'
$ ably auth keys create --name "My New Key" --capabilities '{"*":["publish","subscribe"]}'

$ ably auth keys create --name "My New Key" --capabilities '{"channel1":["publish","subscribe"],"channel2":["history"]}'

Expand Down
5 changes: 5 additions & 0 deletions src/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ export abstract class AblyBaseCommand extends InteractiveBaseCommand {
let appId = flags.app || this.configManager.getCurrentAppId();
let apiKey = this.configManager.getApiKey(appId);

// When apiKey comes from ABLY_API_KEY env var but appId is missing, extract it from the key
if (apiKey && !appId) {
appId = apiKey.split(".")[0] || "";
}

// If we have both, return them
if (appId && apiKey) {
return { apiKey, appId };
Expand Down
16 changes: 9 additions & 7 deletions src/commands/apps/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,16 @@ export default class AppsCreateCommand extends ControlBaseCommand {
this.log(`${formatLabel("Updated")} ${this.formatDate(app.modified)}`);
}

// Automatically switch to the newly created app
this.configManager.setCurrentApp(app.id);
this.configManager.storeAppInfo(app.id, { appName: app.name });
// Automatically switch to the newly created app if a local account exists
if (this.configManager.getCurrentAccount()) {
this.configManager.setCurrentApp(app.id);
this.configManager.storeAppInfo(app.id, { appName: app.name });

this.logSuccessMessage(
`Automatically switched to app ${formatResource(app.name)} (${app.id}).`,
flags,
);
this.logSuccessMessage(
`Automatically switched to app ${formatResource(app.name)} (${app.id}).`,
flags,
);
}
} catch (error) {
this.fail(error, flags, "appCreate");
}
Expand Down
5 changes: 3 additions & 2 deletions src/commands/auth/keys/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default class KeysCreateCommand extends ControlBaseCommand {
static examples = [
`$ ably auth keys create --name "My New Key"`,
`$ ably auth keys create --name "My New Key" --app APP_ID`,
`$ ably auth keys create --name "My New Key" --capabilities '{"*":["*"]}'`,
`$ ably auth keys create --name "My New Key" --capabilities '{"*":["publish","subscribe"]}'`,
`$ ably auth keys create --name "My New Key" --capabilities '{"channel1":["publish","subscribe"],"channel2":["history"]}'`,
`$ ably auth keys create --name "My New Key" --json`,
`$ ably auth keys create --name "My New Key" --pretty-json`,
Expand All @@ -26,7 +26,8 @@ export default class KeysCreateCommand extends ControlBaseCommand {
env: "ABLY_APP_ID",
}),
capabilities: Flags.string({
default: '{"*":["*"]}',
default:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this being caused by the control API, this feels like something we should be fixing there (as * is completely valid), rather than patching here (and inevitably forgetting to add in more things capabilities expand/contract)

'{"[*]*":["subscribe","publish","presence","history","channel-metadata","privileged-headers","statistics","push-subscribe","push-admin","annotation-publish","annotation-subscribe","message-update-own","message-update-any","message-delete-own","message-delete-any","object-publish","object-subscribe"]}',
description: `Capability object as a JSON string. Example: '{"channel:*":["publish"]}'`,
}),
name: Flags.string({
Expand Down
9 changes: 0 additions & 9 deletions src/commands/logs/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,6 @@ export default class LogsSubscribe extends AblyBaseCommand {
includeUserFriendlyMessages: true,
});

// Get the logs channel
const appConfig = await this.ensureAppAndKey(flags);
if (!appConfig) {
this.fail(
"Unable to determine app configuration",
flags,
"logSubscribe",
);
}
const logsChannelName = `[meta]log`;

// Configure channel options for rewind if specified
Expand Down
27 changes: 15 additions & 12 deletions src/commands/rooms/typing/keystroke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,20 +158,23 @@ export default class TypingKeystroke extends ChatBaseCommand {
);
});
}, KEYSTROKE_INTERVAL);
}

this.logCliEvent(
flags,
"typing",
"listening",
"Maintaining typing status...",
);
this.logCliEvent(
flags,
"typing",
"listening",
"Maintaining typing status...",
);

// Wait until the user interrupts, duration elapses, or the room fails
await Promise.race([
this.waitAndTrackCleanup(flags, "typing", flags.duration),
failurePromise,
]);
// Wait until the user interrupts, duration elapses, or the room fails
await Promise.race([
this.waitAndTrackCleanup(flags, "typing", flags.duration),
failurePromise,
]);
} else {
// Suppress unhandled rejection — failurePromise exists from setupRoomStatusHandler
failurePromise.catch(() => {});
}
} catch (error) {
this.fail(error, flags, "roomTypingKeystroke", { room: args.room });
}
Expand Down
1 change: 1 addition & 0 deletions src/commands/spaces/cursors/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default class SpacesCursorsGet extends SpacesBaseCommand {
flags,
);

await this.waitForCursorsChannelAttachment(flags);
const allCursors = await this.space!.cursors.getAll();

const cursors: CursorUpdate[] = Object.values(allCursors).filter(
Expand Down
9 changes: 8 additions & 1 deletion src/services/control-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,14 @@ export class ControlApi {
}

async getNamespace(appId: string, namespaceId: string): Promise<Namespace> {
return this.request<Namespace>(`/apps/${appId}/namespaces/${namespaceId}`);
// Individual namespace GET endpoint is no longer available;
// list all namespaces and filter by ID instead.
const namespaces = await this.listNamespaces(appId);
const namespace = namespaces.find((ns) => ns.id === namespaceId);
if (!namespace) {
throw new Error(`Namespace with ID "${namespaceId}" not found`);
}
return namespace;
}

async getRule(appId: string, ruleId: string): Promise<Rule> {
Expand Down
68 changes: 68 additions & 0 deletions test/e2e/accounts/accounts-e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
describe,
it,
beforeEach,
afterEach,
beforeAll,
afterAll,
expect,
} from "vitest";
import {
E2E_ACCESS_TOKEN,
SHOULD_SKIP_CONTROL_E2E,
forceExit,
cleanupTrackedResources,
setupTestFailureHandler,
resetTestTracking,
} from "../../helpers/e2e-test-helper.js";
import { runCommand } from "../../helpers/command-helpers.js";

describe.skipIf(SHOULD_SKIP_CONTROL_E2E)("Accounts E2E Tests", () => {
beforeAll(() => {
process.on("SIGINT", forceExit);
});

afterAll(() => {
process.removeListener("SIGINT", forceExit);
});

beforeEach(() => {
resetTestTracking();
});

afterEach(async () => {
await cleanupTrackedResources();
});

it(
"should list locally configured accounts",
{ timeout: 15000 },
async () => {
setupTestFailureHandler("should list locally configured accounts");

// accounts list reads from local config, not the API directly.
// In E2E environment, there may or may not be configured accounts.
// We just verify the command runs without crashing.
const listResult = await runCommand(["accounts", "list"], {
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
});

// The command may exit 0 (accounts found) or non-zero (no accounts configured).
// Either way, it should produce output and not crash.
const combinedOutput = listResult.stdout + listResult.stderr;
expect(combinedOutput.length).toBeGreaterThan(0);
},
);

it("should show help for accounts current", { timeout: 10000 }, async () => {
setupTestFailureHandler("should show help for accounts current");

const helpResult = await runCommand(["accounts", "current", "--help"], {
env: { ABLY_ACCESS_TOKEN: E2E_ACCESS_TOKEN || "" },
});

expect(helpResult.exitCode).toBe(0);
const output = helpResult.stdout + helpResult.stderr;
expect(output).toContain("USAGE");
});
});
Loading
Loading