From 8483e862e67c729dcf28677a48d281bde5300a9d Mon Sep 17 00:00:00 2001 From: umair Date: Mon, 30 Mar 2026 17:13:38 +0100 Subject: [PATCH 1/3] - Renames Channel Rules to Rules - Makes ID formatting consistent - Hides exposeTimeserial in Rules - Deletes deprecated Rules alias --- README.md | 46 ++-- docs/Project-Structure.md | 4 +- src/commands/apps/channel-rules/create.ts | 19 -- src/commands/apps/channel-rules/delete.ts | 19 -- src/commands/apps/channel-rules/index.ts | 17 -- src/commands/apps/channel-rules/list.ts | 19 -- src/commands/apps/channel-rules/update.ts | 19 -- src/commands/apps/rules/create.ts | 13 +- src/commands/apps/rules/delete.ts | 21 +- src/commands/apps/rules/index.ts | 4 +- src/commands/apps/rules/list.ts | 15 +- src/commands/apps/rules/update.ts | 30 +-- src/commands/channel-rule/create.ts | 19 -- src/commands/channel-rule/delete.ts | 19 -- src/commands/channel-rule/index.ts | 17 -- src/commands/channel-rule/list.ts | 19 -- src/commands/channel-rule/update.ts | 19 -- src/utils/channel-rule-display.ts | 6 - test/e2e/control-api.test.ts | 4 +- .../e2e/control/control-api-workflows.test.ts | 243 +++++++++--------- .../apps/channel-rules/create.test.ts | 31 --- .../apps/channel-rules/delete.test.ts | 37 --- .../commands/apps/channel-rules/list.test.ts | 36 --- .../apps/channel-rules/update.test.ts | 44 ---- test/unit/commands/apps/rules/create.test.ts | 16 +- test/unit/commands/apps/rules/delete.test.ts | 4 +- test/unit/commands/apps/rules/index.test.ts | 10 - test/unit/commands/apps/rules/list.test.ts | 8 +- test/unit/commands/apps/rules/update.test.ts | 8 +- .../unit/commands/channel-rule/create.test.ts | 28 -- .../unit/commands/channel-rule/delete.test.ts | 30 --- test/unit/commands/channel-rule/list.test.ts | 26 -- .../unit/commands/channel-rule/update.test.ts | 33 --- 33 files changed, 184 insertions(+), 699 deletions(-) delete mode 100644 src/commands/apps/channel-rules/create.ts delete mode 100644 src/commands/apps/channel-rules/delete.ts delete mode 100644 src/commands/apps/channel-rules/index.ts delete mode 100644 src/commands/apps/channel-rules/list.ts delete mode 100644 src/commands/apps/channel-rules/update.ts delete mode 100644 src/commands/channel-rule/create.ts delete mode 100644 src/commands/channel-rule/delete.ts delete mode 100644 src/commands/channel-rule/index.ts delete mode 100644 src/commands/channel-rule/list.ts delete mode 100644 src/commands/channel-rule/update.ts delete mode 100644 test/unit/commands/apps/channel-rules/create.test.ts delete mode 100644 test/unit/commands/apps/channel-rules/delete.test.ts delete mode 100644 test/unit/commands/apps/channel-rules/list.test.ts delete mode 100644 test/unit/commands/apps/channel-rules/update.test.ts delete mode 100644 test/unit/commands/channel-rule/create.test.ts delete mode 100644 test/unit/commands/channel-rule/delete.test.ts delete mode 100644 test/unit/commands/channel-rule/list.test.ts delete mode 100644 test/unit/commands/channel-rule/update.test.ts diff --git a/README.md b/README.md index f12115b4..6aa6cb22 100644 --- a/README.md +++ b/README.md @@ -441,7 +441,7 @@ COMMANDS ably apps current Show the currently selected app ably apps delete Delete an app ably apps list List all apps in the current account - ably apps rules Manage Ably channel rules (namespaces) + ably apps rules Manage Ably rules (namespaces) ably apps switch Switch to a different Ably app ably apps update Update an app ``` @@ -572,14 +572,14 @@ _See code: [src/commands/apps/list.ts](https://github.com/ably/ably-cli/blob/v0. ## `ably apps rules` -Manage Ably channel rules (namespaces) +Manage Ably rules (namespaces) ``` USAGE $ ably apps rules DESCRIPTION - Manage Ably channel rules (namespaces) + Manage Ably rules (namespaces) EXAMPLES $ ably apps rules list @@ -591,24 +591,24 @@ EXAMPLES $ ably apps rules delete chat COMMANDS - ably apps rules create Create a channel rule - ably apps rules delete Delete a channel rule - ably apps rules list List channel rules for an app - ably apps rules update Update a channel rule + ably apps rules create Create a rule + ably apps rules delete Delete a rule + ably apps rules list List rules for an app + ably apps rules update Update a rule ``` _See code: [src/commands/apps/rules/index.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/apps/rules/index.ts)_ ## `ably apps rules create` -Create a channel rule +Create a rule ``` USAGE $ ably apps rules create --name [-v] [--json | --pretty-json] [--app ] [--authenticated] [--batching-enabled] [--batching-interval ] [--conflation-enabled] [--conflation-interval ] - [--conflation-key ] [--expose-time-serial] [--mutable-messages] [--persist-last] [--persisted] - [--populate-channel-registry] [--push-enabled] [--tls-only] + [--conflation-key ] [--mutable-messages] [--persist-last] [--persisted] [--populate-channel-registry] + [--push-enabled] [--tls-only] FLAGS -v, --verbose Output verbose logs @@ -619,11 +619,10 @@ FLAGS --conflation-enabled Whether to enable conflation for messages on channels matching this rule --conflation-interval= The conflation interval for messages on channels matching this rule --conflation-key= The conflation key for messages on channels matching this rule - --expose-time-serial Whether to expose the time serial for messages on channels matching this rule --json Output in JSON format --mutable-messages Whether messages on channels matching this rule can be updated or deleted after publishing. Automatically enables message persistence. - --name= (required) Name of the channel rule + --name= (required) Name of the rule --persist-last Whether to persist only the last message on channels matching this rule --persisted Whether messages on channels matching this rule should be persisted --populate-channel-registry Whether to populate the channel registry for channels matching this rule @@ -632,7 +631,7 @@ FLAGS --tls-only Whether to enforce TLS for channels matching this rule DESCRIPTION - Create a channel rule + Create a rule EXAMPLES $ ably apps rules create --name "chat" --persisted @@ -650,14 +649,14 @@ _See code: [src/commands/apps/rules/create.ts](https://github.com/ably/ably-cli/ ## `ably apps rules delete NAMEORID` -Delete a channel rule +Delete a rule ``` USAGE $ ably apps rules delete NAMEORID [-v] [--json | --pretty-json] [--app ] [-f] ARGUMENTS - NAMEORID Name or ID of the channel rule to delete + NAMEORID Name or ID of the rule to delete FLAGS -f, --force Force deletion without confirmation @@ -667,7 +666,7 @@ FLAGS --pretty-json Output in colorized JSON format DESCRIPTION - Delete a channel rule + Delete a rule EXAMPLES $ ably apps rules delete chat @@ -685,7 +684,7 @@ _See code: [src/commands/apps/rules/delete.ts](https://github.com/ably/ably-cli/ ## `ably apps rules list` -List channel rules for an app +List rules for an app ``` USAGE @@ -699,7 +698,7 @@ FLAGS --pretty-json Output in colorized JSON format DESCRIPTION - List channel rules for an app + List rules for an app EXAMPLES $ ably apps rules list @@ -715,17 +714,17 @@ _See code: [src/commands/apps/rules/list.ts](https://github.com/ably/ably-cli/bl ## `ably apps rules update NAMEORID` -Update a channel rule +Update a rule ``` USAGE $ ably apps rules update NAMEORID [-v] [--json | --pretty-json] [--app ] [--authenticated] [--batching-enabled] [--batching-interval ] [--conflation-enabled] [--conflation-interval ] - [--conflation-key ] [--expose-time-serial] [--mutable-messages] [--persist-last] [--persisted] - [--populate-channel-registry] [--push-enabled] [--tls-only] + [--conflation-key ] [--mutable-messages] [--persist-last] [--persisted] [--populate-channel-registry] + [--push-enabled] [--tls-only] ARGUMENTS - NAMEORID Name or ID of the channel rule to update + NAMEORID Name or ID of the rule to update FLAGS -v, --verbose Output verbose logs @@ -736,7 +735,6 @@ FLAGS --[no-]conflation-enabled Whether to enable conflation for messages on channels matching this rule --conflation-interval= The conflation interval for messages on channels matching this rule --conflation-key= The conflation key for messages on channels matching this rule - --[no-]expose-time-serial Whether to expose the time serial for messages on channels matching this rule --json Output in JSON format --[no-]mutable-messages Whether messages on channels matching this rule can be updated or deleted after publishing. Automatically enables message persistence. @@ -748,7 +746,7 @@ FLAGS --[no-]tls-only Whether to enforce TLS for channels matching this rule DESCRIPTION - Update a channel rule + Update a rule EXAMPLES $ ably apps rules update chat --persisted diff --git a/docs/Project-Structure.md b/docs/Project-Structure.md index 7578c039..45d0dcb4 100644 --- a/docs/Project-Structure.md +++ b/docs/Project-Structure.md @@ -38,11 +38,9 @@ This document outlines the directory structure of the Ably CLI project. │ ├── commands/ # CLI commands (oclif) │ │ ├── accounts/ # Account management (login, logout, list, switch, current) │ │ ├── apps/ # App management (create, list, delete, switch, current, etc.) -│ │ │ ├── rules/ # Channel rules / namespaces (primary path) -│ │ │ └── channel-rules/ # Hidden alias → apps/rules/ +│ │ │ └── rules/ # Rules / namespaces │ │ ├── auth/ # Authentication (keys, tokens) │ │ ├── bench/ # Benchmarking (publisher, subscriber) -│ │ ├── channel-rule/ # Hidden alias → apps/rules/ │ │ ├── channels/ # Pub/Sub channels (publish, subscribe, presence, history, annotations, etc.) │ │ ├── config/ # CLI config management (show, path) │ │ ├── connections/ # Client connections (test) diff --git a/src/commands/apps/channel-rules/create.ts b/src/commands/apps/channel-rules/create.ts deleted file mode 100644 index f83eadc7..00000000 --- a/src/commands/apps/channel-rules/create.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Command } from "@oclif/core"; - -import RulesCreate from "../rules/create.js"; - -export default class ChannelRulesCreateCommand extends Command { - static override args = RulesCreate.args; - static override description = 'Alias for "ably apps rules create"'; - static override flags = RulesCreate.flags; - static override hidden = true; - static isAlias = true; - - async run(): Promise { - this.warn( - '"apps channel-rules create" is deprecated. Use "apps rules create" instead.', - ); - const command = new RulesCreate(this.argv, this.config); - await command.run(); - } -} diff --git a/src/commands/apps/channel-rules/delete.ts b/src/commands/apps/channel-rules/delete.ts deleted file mode 100644 index 2b372c3a..00000000 --- a/src/commands/apps/channel-rules/delete.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Command } from "@oclif/core"; - -import RulesDelete from "../rules/delete.js"; - -export default class ChannelRulesDeleteCommand extends Command { - static override args = RulesDelete.args; - static override description = 'Alias for "ably apps rules delete"'; - static override flags = RulesDelete.flags; - static override hidden = true; - static isAlias = true; - - async run(): Promise { - this.warn( - '"apps channel-rules delete" is deprecated. Use "apps rules delete" instead.', - ); - const command = new RulesDelete(this.argv, this.config); - await command.run(); - } -} diff --git a/src/commands/apps/channel-rules/index.ts b/src/commands/apps/channel-rules/index.ts deleted file mode 100644 index 11c3e920..00000000 --- a/src/commands/apps/channel-rules/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Command } from "@oclif/core"; - -import RulesIndex from "../rules/index.js"; - -export default class ChannelRulesIndexCommand extends Command { - static override args = RulesIndex.args; - static override description = 'Alias for "ably apps rules"'; - static override flags = RulesIndex.flags; - static override hidden = true; - static isAlias = true; - - async run(): Promise { - this.warn('"apps channel-rules" is deprecated. Use "apps rules" instead.'); - const command = new RulesIndex(this.argv, this.config); - await command.run(); - } -} diff --git a/src/commands/apps/channel-rules/list.ts b/src/commands/apps/channel-rules/list.ts deleted file mode 100644 index be74395a..00000000 --- a/src/commands/apps/channel-rules/list.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Command } from "@oclif/core"; - -import RulesList from "../rules/list.js"; - -export default class ChannelRulesListCommand extends Command { - static override args = RulesList.args; - static override description = 'Alias for "ably apps rules list"'; - static override flags = RulesList.flags; - static override hidden = true; - static isAlias = true; - - async run(): Promise { - this.warn( - '"apps channel-rules list" is deprecated. Use "apps rules list" instead.', - ); - const command = new RulesList(this.argv, this.config); - await command.run(); - } -} diff --git a/src/commands/apps/channel-rules/update.ts b/src/commands/apps/channel-rules/update.ts deleted file mode 100644 index 79fd98f0..00000000 --- a/src/commands/apps/channel-rules/update.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Command } from "@oclif/core"; - -import RulesUpdate from "../rules/update.js"; - -export default class ChannelRulesUpdateCommand extends Command { - static override args = RulesUpdate.args; - static override description = 'Alias for "ably apps rules update"'; - static override flags = RulesUpdate.flags; - static override hidden = true; - static isAlias = true; - - async run(): Promise { - this.warn( - '"apps channel-rules update" is deprecated. Use "apps rules update" instead.', - ); - const command = new RulesUpdate(this.argv, this.config); - await command.run(); - } -} diff --git a/src/commands/apps/rules/create.ts b/src/commands/apps/rules/create.ts index 94178807..3e7ab1ec 100644 --- a/src/commands/apps/rules/create.ts +++ b/src/commands/apps/rules/create.ts @@ -10,7 +10,7 @@ import { } from "../../../utils/output.js"; export default class RulesCreateCommand extends ControlBaseCommand { - static description = "Create a channel rule"; + static description = "Create a rule"; static examples = [ '$ ably apps rules create --name "chat" --persisted', @@ -56,18 +56,13 @@ export default class RulesCreateCommand extends ControlBaseCommand { "The conflation key for messages on channels matching this rule", required: false, }), - "expose-time-serial": Flags.boolean({ - description: - "Whether to expose the time serial for messages on channels matching this rule", - required: false, - }), "mutable-messages": Flags.boolean({ description: "Whether messages on channels matching this rule can be updated or deleted after publishing. Automatically enables message persistence.", required: false, }), name: Flags.string({ - description: "Name of the channel rule", + description: "Name of the rule", required: true, }), "persist-last": Flags.boolean({ @@ -129,7 +124,6 @@ export default class RulesCreateCommand extends ControlBaseCommand { conflationEnabled: flags["conflation-enabled"], conflationInterval: flags["conflation-interval"], conflationKey: flags["conflation-key"], - exposeTimeSerial: flags["expose-time-serial"], mutableMessages, persistLast: flags["persist-last"], persisted, @@ -155,7 +149,6 @@ export default class RulesCreateCommand extends ControlBaseCommand { conflationInterval: createdNamespace.conflationInterval, conflationKey: createdNamespace.conflationKey, created: new Date(createdNamespace.created).toISOString(), - exposeTimeSerial: createdNamespace.exposeTimeSerial, id: createdNamespace.id, mutableMessages: createdNamespace.mutableMessages, name: flags.name, @@ -172,7 +165,7 @@ export default class RulesCreateCommand extends ControlBaseCommand { } else { this.log( formatSuccess( - "Channel rule " + formatResource(createdNamespace.id) + " created.", + "Rule " + formatResource(createdNamespace.id) + " created.", ), ); this.log(`${formatLabel("ID")} ${formatResource(createdNamespace.id)}`); diff --git a/src/commands/apps/rules/delete.ts b/src/commands/apps/rules/delete.ts index 9c05b38e..c7d8c2fa 100644 --- a/src/commands/apps/rules/delete.ts +++ b/src/commands/apps/rules/delete.ts @@ -12,12 +12,12 @@ import { promptForConfirmation } from "../../../utils/prompt-confirmation.js"; export default class RulesDeleteCommand extends ControlBaseCommand { static args = { nameOrId: Args.string({ - description: "Name or ID of the channel rule to delete", + description: "Name or ID of the rule to delete", required: true, }), }; - static description = "Delete a channel rule"; + static description = "Delete a rule"; static examples = [ "$ ably apps rules delete chat", @@ -53,17 +53,14 @@ export default class RulesDeleteCommand extends ControlBaseCommand { const namespace = namespaces.find((n) => n.id === args.nameOrId); if (!namespace) { - this.fail( - `Channel rule "${args.nameOrId}" not found`, - flags, - "ruleDelete", - { appId }, - ); + this.fail(`Rule "${args.nameOrId}" not found`, flags, "ruleDelete", { + appId, + }); } // If not using force flag or JSON mode, prompt for confirmation if (!flags.force && !this.shouldOutputJson(flags)) { - this.log(`\nYou are about to delete the following channel rule:`); + this.log(`\nYou are about to delete the following rule:`); this.log(`${formatLabel("ID")} ${formatResource(namespace.id)}`); for (const line of formatChannelRuleDetails(namespace, { formatDate: (t) => this.formatDate(t), @@ -73,7 +70,7 @@ export default class RulesDeleteCommand extends ControlBaseCommand { } const confirmed = await promptForConfirmation( - `\nAre you sure you want to delete channel rule with ID "${namespace.id}"?`, + `\nAre you sure you want to delete rule with ID "${namespace.id}"?`, ); if (!confirmed) { @@ -99,9 +96,7 @@ export default class RulesDeleteCommand extends ControlBaseCommand { ); } else { this.log( - formatSuccess( - `Channel rule ${formatResource(namespace.id)} deleted.`, - ), + formatSuccess(`Rule ${formatResource(namespace.id)} deleted.`), ); } } catch (error) { diff --git a/src/commands/apps/rules/index.ts b/src/commands/apps/rules/index.ts index 7b15c952..927a2a7a 100644 --- a/src/commands/apps/rules/index.ts +++ b/src/commands/apps/rules/index.ts @@ -2,9 +2,9 @@ import { BaseTopicCommand } from "../../../base-topic-command.js"; export default class RulesIndexCommand extends BaseTopicCommand { protected topicName = "apps:rules"; - protected commandGroup = "channel rules"; + protected commandGroup = "rules"; - static description = "Manage Ably channel rules (namespaces)"; + static description = "Manage Ably rules (namespaces)"; static examples = [ "$ ably apps rules list", diff --git a/src/commands/apps/rules/list.ts b/src/commands/apps/rules/list.ts index 0af9ca99..1481d61a 100644 --- a/src/commands/apps/rules/list.ts +++ b/src/commands/apps/rules/list.ts @@ -7,6 +7,7 @@ import { formatCountLabel, formatHeading, formatLimitWarning, + formatResource, } from "../../../utils/output.js"; interface ChannelRuleOutput { @@ -17,7 +18,6 @@ interface ChannelRuleOutput { conflationInterval: null | number; conflationKey: null | string; created: string; - exposeTimeSerial: boolean; id: string; modified: string; mutableMessages: boolean; @@ -29,7 +29,7 @@ interface ChannelRuleOutput { } export default class RulesListCommand extends ControlBaseCommand { - static description = "List channel rules for an app"; + static description = "List rules for an app"; static examples = [ "$ ably apps rules list", @@ -75,7 +75,6 @@ export default class RulesListCommand extends ControlBaseCommand { conflationInterval: rule.conflationInterval ?? null, conflationKey: rule.conflationKey ?? null, created: new Date(rule.created).toISOString(), - exposeTimeSerial: rule.exposeTimeSerial || false, id: rule.id, modified: new Date(rule.modified).toISOString(), mutableMessages: rule.mutableMessages || false, @@ -93,16 +92,14 @@ export default class RulesListCommand extends ControlBaseCommand { ); } else { if (namespaces.length === 0) { - this.log("No channel rules found"); + this.log("No rules found"); return; } - this.log( - `Found ${formatCountLabel(namespaces.length, "channel rule")}:\n`, - ); + this.log(`Found ${formatCountLabel(namespaces.length, "rule")}:\n`); namespaces.forEach((namespace: Namespace) => { - this.log(formatHeading(`Channel Rule ID: ${namespace.id}`)); + this.log(`${formatHeading("ID")} ${formatResource(namespace.id)}`); for (const line of formatChannelRuleDetails(namespace, { bold: true, formatDate: (t) => this.formatDate(t), @@ -119,7 +116,7 @@ export default class RulesListCommand extends ControlBaseCommand { const warning = formatLimitWarning( namespaces.length, flags.limit, - "channel rules", + "rules", ); if (warning) this.log(warning); } diff --git a/src/commands/apps/rules/update.ts b/src/commands/apps/rules/update.ts index 8be33ad6..95315bba 100644 --- a/src/commands/apps/rules/update.ts +++ b/src/commands/apps/rules/update.ts @@ -12,12 +12,12 @@ import { export default class RulesUpdateCommand extends ControlBaseCommand { static args = { nameOrId: Args.string({ - description: "Name or ID of the channel rule to update", + description: "Name or ID of the rule to update", required: true, }), }; - static description = "Update a channel rule"; + static description = "Update a rule"; static examples = [ "$ ably apps rules update chat --persisted", @@ -66,12 +66,6 @@ export default class RulesUpdateCommand extends ControlBaseCommand { "The conflation key for messages on channels matching this rule", required: false, }), - "expose-time-serial": Flags.boolean({ - allowNo: true, - description: - "Whether to expose the time serial for messages on channels matching this rule", - required: false, - }), "mutable-messages": Flags.boolean({ allowNo: true, description: @@ -121,12 +115,9 @@ export default class RulesUpdateCommand extends ControlBaseCommand { const namespace = namespaces.find((n) => n.id === args.nameOrId); if (!namespace) { - this.fail( - `Channel rule "${args.nameOrId}" not found`, - flags, - "ruleUpdate", - { appId }, - ); + this.fail(`Rule "${args.nameOrId}" not found`, flags, "ruleUpdate", { + appId, + }); } // Prepare update data @@ -178,10 +169,6 @@ export default class RulesUpdateCommand extends ControlBaseCommand { updateData.persistLast = flags["persist-last"]; } - if (flags["expose-time-serial"] !== undefined) { - updateData.exposeTimeSerial = flags["expose-time-serial"]; - } - if (flags["populate-channel-registry"] !== undefined) { updateData.populateChannelRegistry = flags["populate-channel-registry"]; } @@ -213,7 +200,7 @@ export default class RulesUpdateCommand extends ControlBaseCommand { // Check if there's anything to update if (Object.keys(updateData).length === 0) { this.fail( - "No update parameters provided. Use one of the flag options to update the channel rule.", + "No update parameters provided. Use one of the flag options to update the rule.", flags, "ruleUpdate", { appId, ruleId: namespace.id }, @@ -238,7 +225,6 @@ export default class RulesUpdateCommand extends ControlBaseCommand { conflationInterval: updatedNamespace.conflationInterval, conflationKey: updatedNamespace.conflationKey, created: new Date(updatedNamespace.created).toISOString(), - exposeTimeSerial: updatedNamespace.exposeTimeSerial, id: updatedNamespace.id, modified: new Date(updatedNamespace.modified).toISOString(), mutableMessages: updatedNamespace.mutableMessages, @@ -254,9 +240,7 @@ export default class RulesUpdateCommand extends ControlBaseCommand { ); } else { this.log( - formatSuccess( - `Channel rule ${formatResource(updatedNamespace.id)} updated.`, - ), + formatSuccess(`Rule ${formatResource(updatedNamespace.id)} updated.`), ); this.log(`${formatLabel("ID")} ${formatResource(updatedNamespace.id)}`); for (const line of formatChannelRuleDetails(updatedNamespace, { diff --git a/src/commands/channel-rule/create.ts b/src/commands/channel-rule/create.ts deleted file mode 100644 index e724c19a..00000000 --- a/src/commands/channel-rule/create.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Command } from "@oclif/core"; - -import RulesCreate from "../apps/rules/create.js"; - -export default class ChannelRuleCreate extends Command { - static override args = RulesCreate.args; - static override description = 'Alias for "ably apps rules create"'; - static override flags = RulesCreate.flags; - static override hidden = true; - static isAlias = true; - - async run(): Promise { - this.warn( - '"channel-rule create" is deprecated. Use "apps rules create" instead.', - ); - const command = new RulesCreate(this.argv, this.config); - await command.run(); - } -} diff --git a/src/commands/channel-rule/delete.ts b/src/commands/channel-rule/delete.ts deleted file mode 100644 index 0acb1582..00000000 --- a/src/commands/channel-rule/delete.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Command } from "@oclif/core"; - -import RulesDelete from "../apps/rules/delete.js"; - -export default class ChannelRuleDelete extends Command { - static override args = RulesDelete.args; - static override description = 'Alias for "ably apps rules delete"'; - static override flags = RulesDelete.flags; - static override hidden = true; - static isAlias = true; - - async run(): Promise { - this.warn( - '"channel-rule delete" is deprecated. Use "apps rules delete" instead.', - ); - const command = new RulesDelete(this.argv, this.config); - await command.run(); - } -} diff --git a/src/commands/channel-rule/index.ts b/src/commands/channel-rule/index.ts deleted file mode 100644 index 11c860de..00000000 --- a/src/commands/channel-rule/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Command } from "@oclif/core"; - -import RulesIndex from "../apps/rules/index.js"; - -export default class ChannelRule extends Command { - static override args = RulesIndex.args; - static override description = 'Alias for "ably apps rules"'; - static override flags = RulesIndex.flags; - static override hidden = true; - static isAlias = true; - - async run(): Promise { - this.warn('"channel-rule" is deprecated. Use "apps rules" instead.'); - const command = new RulesIndex(this.argv, this.config); - await command.run(); - } -} diff --git a/src/commands/channel-rule/list.ts b/src/commands/channel-rule/list.ts deleted file mode 100644 index 4e75dc77..00000000 --- a/src/commands/channel-rule/list.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Command } from "@oclif/core"; - -import RulesList from "../apps/rules/list.js"; - -export default class ChannelRuleList extends Command { - static override args = RulesList.args; - static override description = 'Alias for "ably apps rules list"'; - static override flags = RulesList.flags; - static override hidden = true; - static isAlias = true; - - async run(): Promise { - this.warn( - '"channel-rule list" is deprecated. Use "apps rules list" instead.', - ); - const command = new RulesList(this.argv, this.config); - await command.run(); - } -} diff --git a/src/commands/channel-rule/update.ts b/src/commands/channel-rule/update.ts deleted file mode 100644 index 12121c8f..00000000 --- a/src/commands/channel-rule/update.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Command } from "@oclif/core"; - -import RulesUpdate from "../apps/rules/update.js"; - -export default class ChannelRuleUpdate extends Command { - static override args = RulesUpdate.args; - static override description = 'Alias for "ably apps rules update"'; - static override flags = RulesUpdate.flags; - static override hidden = true; - static isAlias = true; - - async run(): Promise { - this.warn( - '"channel-rule update" is deprecated. Use "apps rules update" instead.', - ); - const command = new RulesUpdate(this.argv, this.config); - await command.run(); - } -} diff --git a/src/utils/channel-rule-display.ts b/src/utils/channel-rule-display.ts index 07d5e72b..02cdb958 100644 --- a/src/utils/channel-rule-display.ts +++ b/src/utils/channel-rule-display.ts @@ -62,12 +62,6 @@ export function formatChannelRuleDetails( ); } - if (rule.exposeTimeSerial !== undefined) { - lines.push( - `${indent}${formatLabel("Expose Time Serial")} ${bool(rule.exposeTimeSerial)}`, - ); - } - if (rule.populateChannelRegistry !== undefined) { lines.push( `${indent}${formatLabel("Populate Channel Registry")} ${bool(rule.populateChannelRegistry)}`, diff --git a/test/e2e/control-api.test.ts b/test/e2e/control-api.test.ts index 36b33bb4..304b8eb4 100644 --- a/test/e2e/control-api.test.ts +++ b/test/e2e/control-api.test.ts @@ -62,7 +62,7 @@ describe.skipIf(!process.env.E2E_ABLY_ACCESS_TOKEN)( } } - // 2. Delete namespaces (channel rules) + // 2. Delete namespaces (rules) for (const namespaceId of createdResources.namespaces) { try { if (testAppId) { @@ -335,7 +335,7 @@ describe.skipIf(!process.env.E2E_ABLY_ACCESS_TOKEN)( }); }); - describe("Namespace/Channel Rules Management", () => { + describe("Namespace/Rules Management", () => { let testNamespaceId: string; it("should create a new namespace", async () => { diff --git a/test/e2e/control/control-api-workflows.test.ts b/test/e2e/control/control-api-workflows.test.ts index c65acacc..74797a57 100644 --- a/test/e2e/control/control-api-workflows.test.ts +++ b/test/e2e/control/control-api-workflows.test.ts @@ -90,7 +90,7 @@ describe("Control API E2E Workflow Tests", () => { } } - // 2. Delete namespaces (channel rules) + // 2. Delete namespaces (rules) for (const namespaceId of createdResources.namespaces) { try { const appId = createdResources.apps[0]; @@ -500,14 +500,14 @@ describe("Control API E2E Workflow Tests", () => { }); }); - describe("Channel Rules Workflow", () => { + describe("Rules Workflow", () => { let testAppId: string; beforeAll(async () => { if (shouldSkip) return; // Create a test app first - const appName = `E2E Channel Rules Test App ${Date.now()}`; + const appName = `E2E Rules Test App ${Date.now()}`; const createResult = await runCommand( ["apps", "create", "--name", appName, "--json"], { @@ -533,21 +533,20 @@ describe("Control API E2E Workflow Tests", () => { }); it( - "should create and manage channel rules through CLI", + "should create and manage rules through CLI", { timeout: 20000 }, async () => { - setupTestFailureHandler( - "should create and manage channel rules through CLI", - ); + setupTestFailureHandler("should create and manage rules through CLI"); if (shouldSkip) return; - const ruleName = `e2e-channel-rule-${Date.now()}`; + const ruleName = `e2e-rule-${Date.now()}`; - // 1. Create channel rule + // 1. Create rule const createResult = await runCommand( [ - "channel-rule", + "apps", + "rules", "create", "--app", testAppId, @@ -575,9 +574,9 @@ describe("Control API E2E Workflow Tests", () => { const namespaceId = createOutput.rule.id; createdResources.namespaces.push(namespaceId); - // 2. List channel rules and verify our rule is included + // 2. List rules and verify our rule is included const listResult = await runCommand( - ["channel-rule", "list", "--app", testAppId, "--json"], + ["apps", "rules", "list", "--app", testAppId, "--json"], { env: { ABLY_ACCESS_TOKEN: process.env.E2E_ABLY_ACCESS_TOKEN }, }, @@ -597,138 +596,126 @@ describe("Control API E2E Workflow Tests", () => { }, ); - it( - "should update a channel rule through CLI", - { timeout: 20000 }, - async () => { - setupTestFailureHandler("should update a channel rule through CLI"); + it("should update a rule through CLI", { timeout: 20000 }, async () => { + setupTestFailureHandler("should update a rule through CLI"); - if (shouldSkip) return; + if (shouldSkip) return; - const ruleName = `e2e-update-rule-${Date.now()}`; + const ruleName = `e2e-update-rule-${Date.now()}`; - // 1. Create channel rule - const createResult = await runCommand( - [ - "channel-rule", - "create", - "--app", - testAppId, - "--name", - ruleName, - "--persisted", - "--json", - ], - { - env: { ABLY_ACCESS_TOKEN: process.env.E2E_ABLY_ACCESS_TOKEN }, - }, - ); + // 1. Create rule + const createResult = await runCommand( + [ + "apps", + "rules", + "create", + "--app", + testAppId, + "--name", + ruleName, + "--persisted", + "--json", + ], + { + env: { ABLY_ACCESS_TOKEN: process.env.E2E_ABLY_ACCESS_TOKEN }, + }, + ); - expect(createResult.stderr).toBe(""); - const createOutput = JSON.parse(createResult.stdout); - expect(createOutput).toHaveProperty("success", true); - expect(createOutput.rule).toHaveProperty("persisted", true); - expect(createOutput.rule).toHaveProperty("pushEnabled", false); + expect(createResult.stderr).toBe(""); + const createOutput = JSON.parse(createResult.stdout); + expect(createOutput).toHaveProperty("success", true); + expect(createOutput.rule).toHaveProperty("persisted", true); + expect(createOutput.rule).toHaveProperty("pushEnabled", false); - const namespaceId = createOutput.rule.id; - createdResources.namespaces.push(namespaceId); + const namespaceId = createOutput.rule.id; + createdResources.namespaces.push(namespaceId); - // 2. Update channel rule - enable push, disable persisted - const updateResult = await runCommand( - [ - "channel-rule", - "update", - namespaceId, - "--app", - testAppId, - "--push-enabled", - "--no-persisted", - "--json", - ], - { - env: { ABLY_ACCESS_TOKEN: process.env.E2E_ABLY_ACCESS_TOKEN }, - }, - ); + // 2. Update rule - enable push, disable persisted + const updateResult = await runCommand( + [ + "apps", + "rules", + "update", + namespaceId, + "--app", + testAppId, + "--push-enabled", + "--no-persisted", + "--json", + ], + { + env: { ABLY_ACCESS_TOKEN: process.env.E2E_ABLY_ACCESS_TOKEN }, + }, + ); - expect(updateResult.stderr).toBe(""); - const updateOutput = JSON.parse(updateResult.stdout); - expect(updateOutput).toHaveProperty("success", true); - expect(updateOutput.rule).toHaveProperty("id", namespaceId); - expect(updateOutput.rule).toHaveProperty("pushEnabled", true); - expect(updateOutput.rule).toHaveProperty("persisted", false); - - // Verify null batchingInterval/conflationInterval don't cause errors - // These fields may be null in the response - expect(updateOutput.rule).toHaveProperty("batchingInterval"); - expect(updateOutput.rule).toHaveProperty("conflationInterval"); - }, - ); + expect(updateResult.stderr).toBe(""); + const updateOutput = JSON.parse(updateResult.stdout); + expect(updateOutput).toHaveProperty("success", true); + expect(updateOutput.rule).toHaveProperty("id", namespaceId); + expect(updateOutput.rule).toHaveProperty("pushEnabled", true); + expect(updateOutput.rule).toHaveProperty("persisted", false); + + // Verify null batchingInterval/conflationInterval don't cause errors + // These fields may be null in the response + expect(updateOutput.rule).toHaveProperty("batchingInterval"); + expect(updateOutput.rule).toHaveProperty("conflationInterval"); + }); - it( - "should delete a channel rule through CLI", - { timeout: 20000 }, - async () => { - setupTestFailureHandler("should delete a channel rule through CLI"); + it("should delete a rule through CLI", { timeout: 20000 }, async () => { + setupTestFailureHandler("should delete a rule through CLI"); - if (shouldSkip) return; + if (shouldSkip) return; - const ruleName = `e2e-delete-rule-${Date.now()}`; + const ruleName = `e2e-delete-rule-${Date.now()}`; - // 1. Create channel rule - const createResult = await runCommand( - [ - "channel-rule", - "create", - "--app", - testAppId, - "--name", - ruleName, - "--json", - ], - { - env: { ABLY_ACCESS_TOKEN: process.env.E2E_ABLY_ACCESS_TOKEN }, - }, - ); + // 1. Create rule + const createResult = await runCommand( + [ + "apps", + "rules", + "create", + "--app", + testAppId, + "--name", + ruleName, + "--json", + ], + { + env: { ABLY_ACCESS_TOKEN: process.env.E2E_ABLY_ACCESS_TOKEN }, + }, + ); - expect(createResult.stderr).toBe(""); - const createOutput = JSON.parse(createResult.stdout); - expect(createOutput).toHaveProperty("success", true); - const namespaceId = createOutput.rule.id; - createdResources.namespaces.push(namespaceId); + expect(createResult.stderr).toBe(""); + const createOutput = JSON.parse(createResult.stdout); + expect(createOutput).toHaveProperty("success", true); + const namespaceId = createOutput.rule.id; + createdResources.namespaces.push(namespaceId); - // 2. Delete channel rule with --force - const deleteResult = await runCommand( - [ - "channel-rule", - "delete", - namespaceId, - "--app", - testAppId, - "--force", - ], - { - env: { ABLY_ACCESS_TOKEN: process.env.E2E_ABLY_ACCESS_TOKEN }, - }, - ); + // 2. Delete rule with --force + const deleteResult = await runCommand( + ["apps", "rules", "delete", namespaceId, "--app", testAppId, "--force"], + { + env: { ABLY_ACCESS_TOKEN: process.env.E2E_ABLY_ACCESS_TOKEN }, + }, + ); - expect(deleteResult.stderr).toBe(""); - expect(deleteResult.stdout).toContain("deleted successfully"); + expect(deleteResult.stderr).toBe(""); + expect(deleteResult.stdout).toContain("deleted successfully"); - // 3. Verify the rule is gone by listing - const listResult = await runCommand( - ["channel-rule", "list", "--app", testAppId, "--json"], - { - env: { ABLY_ACCESS_TOKEN: process.env.E2E_ABLY_ACCESS_TOKEN }, - }, - ); + // 3. Verify the rule is gone by listing + const listResult = await runCommand( + ["apps", "rules", "list", "--app", testAppId, "--json"], + { + env: { ABLY_ACCESS_TOKEN: process.env.E2E_ABLY_ACCESS_TOKEN }, + }, + ); - const listOutput = JSON.parse(listResult.stdout); - const deletedRule = listOutput.rules.find( - (ns: { id: string }) => ns.id === namespaceId, - ); - expect(deletedRule).toBeUndefined(); - }, - ); + const listOutput = JSON.parse(listResult.stdout); + const deletedRule = listOutput.rules.find( + (ns: any) => ns.id === namespaceId, + ); + expect(deletedRule).toBeUndefined(); + }); }); describe("Error Handling and Edge Cases", () => { diff --git a/test/unit/commands/apps/channel-rules/create.test.ts b/test/unit/commands/apps/channel-rules/create.test.ts deleted file mode 100644 index b181f7ab..00000000 --- a/test/unit/commands/apps/channel-rules/create.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { describe, it, expect, afterEach } from "vitest"; -import { runCommand } from "@oclif/test"; -import { - nockControl, - controlApiCleanup, -} from "../../../../helpers/control-api-test-helpers.js"; -import { getMockConfigManager } from "../../../../helpers/mock-config-manager.js"; - -describe("apps:channel-rules:create alias", () => { - afterEach(() => { - controlApiCleanup(); - }); - - it("should forward to apps:rules:create and produce the same output", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl().post(`/v1/apps/${appId}/namespaces`).reply(201, { - id: "chat", - persisted: false, - pushEnabled: false, - created: Date.now(), - modified: Date.now(), - }); - - const { stdout } = await runCommand( - ["apps:channel-rules:create", "--name", "chat"], - import.meta.url, - ); - - expect(stdout).toContain("Channel rule chat created."); - }); -}); diff --git a/test/unit/commands/apps/channel-rules/delete.test.ts b/test/unit/commands/apps/channel-rules/delete.test.ts deleted file mode 100644 index 0772bc70..00000000 --- a/test/unit/commands/apps/channel-rules/delete.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { describe, it, expect, afterEach } from "vitest"; -import { runCommand } from "@oclif/test"; -import { - nockControl, - controlApiCleanup, -} from "../../../../helpers/control-api-test-helpers.js"; -import { getMockConfigManager } from "../../../../helpers/mock-config-manager.js"; - -describe("apps:channel-rules:delete alias", () => { - afterEach(() => { - controlApiCleanup(); - }); - - it("should forward to apps:rules:delete and produce the same output", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl() - .get(`/v1/apps/${appId}/namespaces`) - .reply(200, [ - { - id: "chat", - persisted: false, - pushEnabled: false, - created: Date.now(), - modified: Date.now(), - }, - ]); - - nockControl().delete(`/v1/apps/${appId}/namespaces/chat`).reply(204); - - const { stdout } = await runCommand( - ["apps:channel-rules:delete", "chat", "--force"], - import.meta.url, - ); - - expect(stdout).toContain("deleted"); - }); -}); diff --git a/test/unit/commands/apps/channel-rules/list.test.ts b/test/unit/commands/apps/channel-rules/list.test.ts deleted file mode 100644 index b6e83e9c..00000000 --- a/test/unit/commands/apps/channel-rules/list.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { describe, it, expect, afterEach } from "vitest"; -import { runCommand } from "@oclif/test"; -import { - nockControl, - controlApiCleanup, -} from "../../../../helpers/control-api-test-helpers.js"; -import { getMockConfigManager } from "../../../../helpers/mock-config-manager.js"; - -describe("apps:channel-rules:list alias", () => { - afterEach(() => { - controlApiCleanup(); - }); - - it("should forward to apps:rules:list and produce the same output", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl() - .get(`/v1/apps/${appId}/namespaces`) - .reply(200, [ - { - id: "chat", - persisted: true, - pushEnabled: false, - created: Date.now(), - modified: Date.now(), - }, - ]); - - const { stdout } = await runCommand( - ["apps:channel-rules:list"], - import.meta.url, - ); - - expect(stdout).toContain("Found 1 channel rule"); - expect(stdout).toContain("chat"); - }); -}); diff --git a/test/unit/commands/apps/channel-rules/update.test.ts b/test/unit/commands/apps/channel-rules/update.test.ts deleted file mode 100644 index 30bfbb8a..00000000 --- a/test/unit/commands/apps/channel-rules/update.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { describe, it, expect, afterEach } from "vitest"; -import { runCommand } from "@oclif/test"; -import { - nockControl, - controlApiCleanup, -} from "../../../../helpers/control-api-test-helpers.js"; -import { getMockConfigManager } from "../../../../helpers/mock-config-manager.js"; - -describe("apps:channel-rules:update alias", () => { - afterEach(() => { - controlApiCleanup(); - }); - - it("should forward to apps:rules:update and produce the same output", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl() - .get(`/v1/apps/${appId}/namespaces`) - .reply(200, [ - { - id: "chat", - persisted: false, - pushEnabled: false, - created: Date.now(), - modified: Date.now(), - }, - ]); - - nockControl().patch(`/v1/apps/${appId}/namespaces/chat`).reply(200, { - id: "chat", - persisted: true, - pushEnabled: false, - created: Date.now(), - modified: Date.now(), - }); - - const { stdout } = await runCommand( - ["apps:channel-rules:update", "chat", "--persisted"], - import.meta.url, - ); - - expect(stdout).toContain("updated"); - expect(stdout).toContain("Persisted: Yes"); - }); -}); diff --git a/test/unit/commands/apps/rules/create.test.ts b/test/unit/commands/apps/rules/create.test.ts index 9c4516dc..151bb1f9 100644 --- a/test/unit/commands/apps/rules/create.test.ts +++ b/test/unit/commands/apps/rules/create.test.ts @@ -33,7 +33,7 @@ describe("apps:rules:create command", () => { ]); describe("functionality", () => { - it("should create a channel rule successfully", async () => { + it("should create a rule successfully", async () => { const appId = getMockConfigManager().getCurrentAppId()!; nockControl() .post(`/v1/apps/${appId}/namespaces`) @@ -44,10 +44,10 @@ describe("apps:rules:create command", () => { import.meta.url, ); - expect(stdout).toContain("Channel rule chat created."); + expect(stdout).toContain("Rule chat created."); }); - it("should create a channel rule with persisted flag", async () => { + it("should create a rule with persisted flag", async () => { const appId = getMockConfigManager().getCurrentAppId()!; nockControl() .post(`/v1/apps/${appId}/namespaces`, (body) => { @@ -60,11 +60,11 @@ describe("apps:rules:create command", () => { import.meta.url, ); - expect(stdout).toContain("Channel rule chat created."); + expect(stdout).toContain("Rule chat created."); expect(stdout).toContain("Persisted: Yes"); }); - it("should create a channel rule with mutable-messages flag and auto-enable persistence", async () => { + it("should create a rule with mutable-messages flag and auto-enable persistence", async () => { const appId = getMockConfigManager().getCurrentAppId()!; nockControl() .post(`/v1/apps/${appId}/namespaces`, (body) => { @@ -84,13 +84,13 @@ describe("apps:rules:create command", () => { import.meta.url, ); - expect(stdout).toContain("Channel rule chat created."); + expect(stdout).toContain("Rule chat created."); expect(stdout).toContain("Persisted: Yes"); expect(stdout).toContain("Mutable Messages: Yes"); expect(stderr).toContain("persistence is automatically enabled"); }); - it("should create a channel rule with push-enabled flag", async () => { + it("should create a rule with push-enabled flag", async () => { const appId = getMockConfigManager().getCurrentAppId()!; nockControl() .post(`/v1/apps/${appId}/namespaces`, (body) => { @@ -103,7 +103,7 @@ describe("apps:rules:create command", () => { import.meta.url, ); - expect(stdout).toContain("Channel rule chat created."); + expect(stdout).toContain("Rule chat created."); expect(stdout).toContain("Push Enabled: Yes"); }); diff --git a/test/unit/commands/apps/rules/delete.test.ts b/test/unit/commands/apps/rules/delete.test.ts index 441a27a7..44f1ffff 100644 --- a/test/unit/commands/apps/rules/delete.test.ts +++ b/test/unit/commands/apps/rules/delete.test.ts @@ -31,7 +31,7 @@ describe("apps:rules:delete command", () => { ]); describe("functionality", () => { - it("should delete a channel rule with force flag", async () => { + it("should delete a rule with force flag", async () => { const appId = getMockConfigManager().getCurrentAppId()!; nockControl() .get(`/v1/apps/${appId}/namespaces`) @@ -85,7 +85,7 @@ describe("apps:rules:delete command", () => { }, }); - it("should handle channel rule not found", async () => { + it("should handle rule not found", async () => { const appId = getMockConfigManager().getCurrentAppId()!; nockControl().get(`/v1/apps/${appId}/namespaces`).reply(200, []); diff --git a/test/unit/commands/apps/rules/index.test.ts b/test/unit/commands/apps/rules/index.test.ts index e4e1b40b..f7d9cba5 100644 --- a/test/unit/commands/apps/rules/index.test.ts +++ b/test/unit/commands/apps/rules/index.test.ts @@ -27,16 +27,6 @@ describe("apps:rules topic command", () => { expect(stdout).toContain("update"); expect(stdout).toContain("delete"); }); - - it("should not show hidden channel-rules alias in apps help", async () => { - const { stdout } = await runCommand(["apps", "--help"], import.meta.url); - expect(stdout).not.toContain("channel-rules"); - }); - - it("should not show hidden channel-rule alias in top-level help", async () => { - const { stdout } = await runCommand(["--help"], import.meta.url); - expect(stdout).not.toContain("channel-rule"); - }); }); describe("flags", () => { diff --git a/test/unit/commands/apps/rules/list.test.ts b/test/unit/commands/apps/rules/list.test.ts index 57e06499..46eebd96 100644 --- a/test/unit/commands/apps/rules/list.test.ts +++ b/test/unit/commands/apps/rules/list.test.ts @@ -23,7 +23,7 @@ describe("apps:rules:list command", () => { standardFlagTests("apps:rules:list", import.meta.url, ["--json", "--app"]); describe("functionality", () => { - it("should list channel rules successfully", async () => { + it("should list rules successfully", async () => { const appId = getMockConfigManager().getCurrentAppId()!; nockControl() .get(`/v1/apps/${appId}/namespaces`) @@ -34,7 +34,7 @@ describe("apps:rules:list command", () => { const { stdout } = await runCommand(["apps:rules:list"], import.meta.url); - expect(stdout).toContain("Found 2 channel rules"); + expect(stdout).toContain("Found 2 rules"); expect(stdout).toContain("chat"); expect(stdout).toContain("events"); }); @@ -45,7 +45,7 @@ describe("apps:rules:list command", () => { const { stdout } = await runCommand(["apps:rules:list"], import.meta.url); - expect(stdout).toContain("No channel rules found"); + expect(stdout).toContain("No rules found"); }); it("should display rule details correctly", async () => { @@ -62,7 +62,7 @@ describe("apps:rules:list command", () => { const { stdout } = await runCommand(["apps:rules:list"], import.meta.url); - expect(stdout).toContain("Found 1 channel rule"); + expect(stdout).toContain("Found 1 rule"); expect(stdout).toContain("chat"); expect(stdout).toContain("Persisted: ✓ Yes"); expect(stdout).toContain("Push Enabled: ✓ Yes"); diff --git a/test/unit/commands/apps/rules/update.test.ts b/test/unit/commands/apps/rules/update.test.ts index 82834d16..2cf8a0b1 100644 --- a/test/unit/commands/apps/rules/update.test.ts +++ b/test/unit/commands/apps/rules/update.test.ts @@ -33,7 +33,7 @@ describe("apps:rules:update command", () => { ]); describe("functionality", () => { - it("should update a channel rule with persisted flag", async () => { + it("should update a rule with persisted flag", async () => { const appId = getMockConfigManager().getCurrentAppId()!; nockControl() .get(`/v1/apps/${appId}/namespaces`) @@ -52,7 +52,7 @@ describe("apps:rules:update command", () => { expect(stdout).toContain("Persisted: Yes"); }); - it("should update a channel rule with mutable-messages flag and auto-enable persistence", async () => { + it("should update a rule with mutable-messages flag and auto-enable persistence", async () => { const appId = getMockConfigManager().getCurrentAppId()!; nockControl() .get(`/v1/apps/${appId}/namespaces`) @@ -141,7 +141,7 @@ describe("apps:rules:update command", () => { expect(stdout).toContain("Persisted: No"); }); - it("should update a channel rule with push-enabled flag", async () => { + it("should update a rule with push-enabled flag", async () => { const appId = getMockConfigManager().getCurrentAppId()!; nockControl() .get(`/v1/apps/${appId}/namespaces`) @@ -217,7 +217,7 @@ describe("apps:rules:update command", () => { }, }); - it("should handle channel rule not found", async () => { + it("should handle rule not found", async () => { const appId = getMockConfigManager().getCurrentAppId()!; nockControl().get(`/v1/apps/${appId}/namespaces`).reply(200, []); diff --git a/test/unit/commands/channel-rule/create.test.ts b/test/unit/commands/channel-rule/create.test.ts deleted file mode 100644 index 9e6efdd3..00000000 --- a/test/unit/commands/channel-rule/create.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { describe, it, expect, afterEach } from "vitest"; -import { runCommand } from "@oclif/test"; -import { - nockControl, - controlApiCleanup, -} from "../../../helpers/control-api-test-helpers.js"; -import { getMockConfigManager } from "../../../helpers/mock-config-manager.js"; -import { mockNamespace } from "../../../fixtures/control-api.js"; - -describe("channel-rule:create alias", () => { - afterEach(() => { - controlApiCleanup(); - }); - - it("should forward to apps:rules:create and produce the same output", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl() - .post(`/v1/apps/${appId}/namespaces`) - .reply(201, mockNamespace({ id: "test-rule", persisted: true })); - - const { stdout } = await runCommand( - ["channel-rule:create", "--name=test-rule", "--persisted"], - import.meta.url, - ); - - expect(stdout).toContain("Channel rule test-rule created."); - }); -}); diff --git a/test/unit/commands/channel-rule/delete.test.ts b/test/unit/commands/channel-rule/delete.test.ts deleted file mode 100644 index 442b62d7..00000000 --- a/test/unit/commands/channel-rule/delete.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { describe, it, expect, afterEach } from "vitest"; -import { runCommand } from "@oclif/test"; -import { - nockControl, - controlApiCleanup, -} from "../../../helpers/control-api-test-helpers.js"; -import { getMockConfigManager } from "../../../helpers/mock-config-manager.js"; -import { mockNamespace } from "../../../fixtures/control-api.js"; - -describe("channel-rule:delete alias", () => { - afterEach(() => { - controlApiCleanup(); - }); - - it("should forward to apps:rules:delete and produce the same output", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl() - .get(`/v1/apps/${appId}/namespaces`) - .reply(200, [mockNamespace({ id: "test-rule" })]); - - nockControl().delete(`/v1/apps/${appId}/namespaces/test-rule`).reply(204); - - const { stdout } = await runCommand( - ["channel-rule:delete", "test-rule", "--force"], - import.meta.url, - ); - - expect(stdout).toContain("deleted"); - }); -}); diff --git a/test/unit/commands/channel-rule/list.test.ts b/test/unit/commands/channel-rule/list.test.ts deleted file mode 100644 index bb75d86f..00000000 --- a/test/unit/commands/channel-rule/list.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { describe, it, expect, afterEach } from "vitest"; -import { runCommand } from "@oclif/test"; -import { - nockControl, - controlApiCleanup, -} from "../../../helpers/control-api-test-helpers.js"; -import { getMockConfigManager } from "../../../helpers/mock-config-manager.js"; -import { mockNamespace } from "../../../fixtures/control-api.js"; - -describe("channel-rule:list alias", () => { - afterEach(() => { - controlApiCleanup(); - }); - - it("should forward to apps:rules:list and produce the same output", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl() - .get(`/v1/apps/${appId}/namespaces`) - .reply(200, [mockNamespace({ id: "rule1", persisted: true })]); - - const { stdout } = await runCommand(["channel-rule:list"], import.meta.url); - - expect(stdout).toContain("Found 1 channel rule"); - expect(stdout).toContain("rule1"); - }); -}); diff --git a/test/unit/commands/channel-rule/update.test.ts b/test/unit/commands/channel-rule/update.test.ts deleted file mode 100644 index 40b55e05..00000000 --- a/test/unit/commands/channel-rule/update.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, it, expect, afterEach } from "vitest"; -import { runCommand } from "@oclif/test"; -import { - nockControl, - controlApiCleanup, -} from "../../../helpers/control-api-test-helpers.js"; -import { getMockConfigManager } from "../../../helpers/mock-config-manager.js"; -import { mockNamespace } from "../../../fixtures/control-api.js"; - -describe("channel-rule:update alias", () => { - afterEach(() => { - controlApiCleanup(); - }); - - it("should forward to apps:rules:update and produce the same output", async () => { - const appId = getMockConfigManager().getCurrentAppId()!; - nockControl() - .get(`/v1/apps/${appId}/namespaces`) - .reply(200, [mockNamespace({ id: "test-rule" })]); - - nockControl() - .patch(`/v1/apps/${appId}/namespaces/test-rule`) - .reply(200, mockNamespace({ id: "test-rule", persisted: true })); - - const { stdout } = await runCommand( - ["channel-rule:update", "test-rule", "--persisted"], - import.meta.url, - ); - - expect(stdout).toContain("updated"); - expect(stdout).toContain("Persisted: Yes"); - }); -}); From c07b49090a7506c831465929a1d2b81c601cc9ec Mon Sep 17 00:00:00 2001 From: umair Date: Tue, 31 Mar 2026 13:01:38 +0100 Subject: [PATCH 2/3] fixes inconsistent formatting across rules --- src/commands/apps/rules/create.ts | 1 + src/commands/apps/rules/delete.ts | 1 + src/commands/apps/rules/update.ts | 1 + test/e2e/control/control-api-workflows.test.ts | 2 +- test/unit/commands/apps/rules/create.test.ts | 8 ++++---- test/unit/commands/apps/rules/update.test.ts | 4 ++-- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/commands/apps/rules/create.ts b/src/commands/apps/rules/create.ts index 3e7ab1ec..acb96cdb 100644 --- a/src/commands/apps/rules/create.ts +++ b/src/commands/apps/rules/create.ts @@ -170,6 +170,7 @@ export default class RulesCreateCommand extends ControlBaseCommand { ); this.log(`${formatLabel("ID")} ${formatResource(createdNamespace.id)}`); for (const line of formatChannelRuleDetails(createdNamespace, { + bold: true, formatDate: (t) => this.formatDate(t), })) { this.log(line); diff --git a/src/commands/apps/rules/delete.ts b/src/commands/apps/rules/delete.ts index c7d8c2fa..2378b4fe 100644 --- a/src/commands/apps/rules/delete.ts +++ b/src/commands/apps/rules/delete.ts @@ -63,6 +63,7 @@ export default class RulesDeleteCommand extends ControlBaseCommand { this.log(`\nYou are about to delete the following rule:`); this.log(`${formatLabel("ID")} ${formatResource(namespace.id)}`); for (const line of formatChannelRuleDetails(namespace, { + bold: true, formatDate: (t) => this.formatDate(t), showTimestamps: true, })) { diff --git a/src/commands/apps/rules/update.ts b/src/commands/apps/rules/update.ts index 95315bba..b4ae8b57 100644 --- a/src/commands/apps/rules/update.ts +++ b/src/commands/apps/rules/update.ts @@ -244,6 +244,7 @@ export default class RulesUpdateCommand extends ControlBaseCommand { ); this.log(`${formatLabel("ID")} ${formatResource(updatedNamespace.id)}`); for (const line of formatChannelRuleDetails(updatedNamespace, { + bold: true, formatDate: (t) => this.formatDate(t), showTimestamps: true, })) { diff --git a/test/e2e/control/control-api-workflows.test.ts b/test/e2e/control/control-api-workflows.test.ts index 74797a57..1ceba445 100644 --- a/test/e2e/control/control-api-workflows.test.ts +++ b/test/e2e/control/control-api-workflows.test.ts @@ -712,7 +712,7 @@ describe("Control API E2E Workflow Tests", () => { const listOutput = JSON.parse(listResult.stdout); const deletedRule = listOutput.rules.find( - (ns: any) => ns.id === namespaceId, + (ns: { id: string }) => ns.id === namespaceId, ); expect(deletedRule).toBeUndefined(); }); diff --git a/test/unit/commands/apps/rules/create.test.ts b/test/unit/commands/apps/rules/create.test.ts index 151bb1f9..1be713f7 100644 --- a/test/unit/commands/apps/rules/create.test.ts +++ b/test/unit/commands/apps/rules/create.test.ts @@ -61,7 +61,7 @@ describe("apps:rules:create command", () => { ); expect(stdout).toContain("Rule chat created."); - expect(stdout).toContain("Persisted: Yes"); + expect(stdout).toContain("Persisted: ✓ Yes"); }); it("should create a rule with mutable-messages flag and auto-enable persistence", async () => { @@ -85,8 +85,8 @@ describe("apps:rules:create command", () => { ); expect(stdout).toContain("Rule chat created."); - expect(stdout).toContain("Persisted: Yes"); - expect(stdout).toContain("Mutable Messages: Yes"); + expect(stdout).toContain("Persisted: ✓ Yes"); + expect(stdout).toContain("Mutable Messages: ✓ Yes"); expect(stderr).toContain("persistence is automatically enabled"); }); @@ -104,7 +104,7 @@ describe("apps:rules:create command", () => { ); expect(stdout).toContain("Rule chat created."); - expect(stdout).toContain("Push Enabled: Yes"); + expect(stdout).toContain("Push Enabled: ✓ Yes"); }); it("should output JSON format when --json flag is used", async () => { diff --git a/test/unit/commands/apps/rules/update.test.ts b/test/unit/commands/apps/rules/update.test.ts index 2cf8a0b1..ad19632d 100644 --- a/test/unit/commands/apps/rules/update.test.ts +++ b/test/unit/commands/apps/rules/update.test.ts @@ -49,7 +49,7 @@ describe("apps:rules:update command", () => { ); expect(stdout).toContain("updated"); - expect(stdout).toContain("Persisted: Yes"); + expect(stdout).toContain("Persisted: ✓ Yes"); }); it("should update a rule with mutable-messages flag and auto-enable persistence", async () => { @@ -157,7 +157,7 @@ describe("apps:rules:update command", () => { ); expect(stdout).toContain("updated"); - expect(stdout).toContain("Push Enabled: Yes"); + expect(stdout).toContain("Push Enabled: ✓ Yes"); }); it("should output JSON format when --json flag is used", async () => { From c89f2e1cba24f6f14ae0e53fab7e51a4a61f87f0 Mon Sep 17 00:00:00 2001 From: umair Date: Tue, 31 Mar 2026 17:59:26 +0100 Subject: [PATCH 3/3] Fix rules JSON output consistency: remove redundant name field from create, add null-handling fallbacks and modified timestamp to create/update, align list ID heading with CLI convention --- src/commands/apps/rules/create.ts | 27 ++++++++++++++------------- src/commands/apps/rules/list.ts | 3 +-- src/commands/apps/rules/update.ts | 25 +++++++++++++------------ 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/commands/apps/rules/create.ts b/src/commands/apps/rules/create.ts index acb96cdb..277390d5 100644 --- a/src/commands/apps/rules/create.ts +++ b/src/commands/apps/rules/create.ts @@ -142,21 +142,22 @@ export default class RulesCreateCommand extends ControlBaseCommand { { appId, rule: { - authenticated: createdNamespace.authenticated, - batchingEnabled: createdNamespace.batchingEnabled, - batchingInterval: createdNamespace.batchingInterval, - conflationEnabled: createdNamespace.conflationEnabled, - conflationInterval: createdNamespace.conflationInterval, - conflationKey: createdNamespace.conflationKey, + authenticated: createdNamespace.authenticated || false, + batchingEnabled: createdNamespace.batchingEnabled || false, + batchingInterval: createdNamespace.batchingInterval ?? null, + conflationEnabled: createdNamespace.conflationEnabled || false, + conflationInterval: createdNamespace.conflationInterval ?? null, + conflationKey: createdNamespace.conflationKey ?? null, created: new Date(createdNamespace.created).toISOString(), id: createdNamespace.id, - mutableMessages: createdNamespace.mutableMessages, - name: flags.name, - persistLast: createdNamespace.persistLast, - persisted: createdNamespace.persisted, - populateChannelRegistry: createdNamespace.populateChannelRegistry, - pushEnabled: createdNamespace.pushEnabled, - tlsOnly: createdNamespace.tlsOnly, + modified: new Date(createdNamespace.modified).toISOString(), + mutableMessages: createdNamespace.mutableMessages || false, + persistLast: createdNamespace.persistLast || false, + persisted: createdNamespace.persisted || false, + populateChannelRegistry: + createdNamespace.populateChannelRegistry || false, + pushEnabled: createdNamespace.pushEnabled || false, + tlsOnly: createdNamespace.tlsOnly || false, }, timestamp: new Date().toISOString(), }, diff --git a/src/commands/apps/rules/list.ts b/src/commands/apps/rules/list.ts index 1481d61a..e74020d1 100644 --- a/src/commands/apps/rules/list.ts +++ b/src/commands/apps/rules/list.ts @@ -7,7 +7,6 @@ import { formatCountLabel, formatHeading, formatLimitWarning, - formatResource, } from "../../../utils/output.js"; interface ChannelRuleOutput { @@ -99,7 +98,7 @@ export default class RulesListCommand extends ControlBaseCommand { this.log(`Found ${formatCountLabel(namespaces.length, "rule")}:\n`); namespaces.forEach((namespace: Namespace) => { - this.log(`${formatHeading("ID")} ${formatResource(namespace.id)}`); + this.log(formatHeading(`ID: ${namespace.id}`)); for (const line of formatChannelRuleDetails(namespace, { bold: true, formatDate: (t) => this.formatDate(t), diff --git a/src/commands/apps/rules/update.ts b/src/commands/apps/rules/update.ts index b4ae8b57..8c5f93ec 100644 --- a/src/commands/apps/rules/update.ts +++ b/src/commands/apps/rules/update.ts @@ -218,21 +218,22 @@ export default class RulesUpdateCommand extends ControlBaseCommand { { appId, rule: { - authenticated: updatedNamespace.authenticated, - batchingEnabled: updatedNamespace.batchingEnabled, - batchingInterval: updatedNamespace.batchingInterval, - conflationEnabled: updatedNamespace.conflationEnabled, - conflationInterval: updatedNamespace.conflationInterval, - conflationKey: updatedNamespace.conflationKey, + authenticated: updatedNamespace.authenticated || false, + batchingEnabled: updatedNamespace.batchingEnabled || false, + batchingInterval: updatedNamespace.batchingInterval ?? null, + conflationEnabled: updatedNamespace.conflationEnabled || false, + conflationInterval: updatedNamespace.conflationInterval ?? null, + conflationKey: updatedNamespace.conflationKey ?? null, created: new Date(updatedNamespace.created).toISOString(), id: updatedNamespace.id, modified: new Date(updatedNamespace.modified).toISOString(), - mutableMessages: updatedNamespace.mutableMessages, - persistLast: updatedNamespace.persistLast, - persisted: updatedNamespace.persisted, - populateChannelRegistry: updatedNamespace.populateChannelRegistry, - pushEnabled: updatedNamespace.pushEnabled, - tlsOnly: updatedNamespace.tlsOnly, + mutableMessages: updatedNamespace.mutableMessages || false, + persistLast: updatedNamespace.persistLast || false, + persisted: updatedNamespace.persisted || false, + populateChannelRegistry: + updatedNamespace.populateChannelRegistry || false, + pushEnabled: updatedNamespace.pushEnabled || false, + tlsOnly: updatedNamespace.tlsOnly || false, }, timestamp: new Date().toISOString(), },