Repository: apify/apify-cli (ts)

Issue:
Subcommands not to be separated by `:` but space instead
Based on the Apify Actors specification, the sub-commands should not be separated by `:` as:

```
apify secrets:add
apify actor:get-input
```

But by the space instead:

```
apify secrets add
apify actor get-input
```

This is a breaking change mainly for the secrets, as actor sub-commands are not used much. With this, we should consider if there is anything we should improve with the `apify secrets` command to beware of more breaking changes in the future.

Pull request description:
feat: support spaces and `:` for subcommands
Closes #309 

Patch:
diff --git a/docs/vars.md b/docs/vars.md
index 991f6e5..d59c0aa 100644
--- a/docs/vars.md
+++ b/docs/vars.md
@@ -6,7 +6,9 @@ title: Environment variables
 There are two options how you can set up environment variables for actors.
 
 ### Set up environment variables in `.actor/actor.json`
+
 All keys from `env` will be set as environment variables into Apify platform after you push actor to Apify. Current values on Apify will be overridden.
+
 ```json
 {
     "actorSpecification": 1,
@@ -14,15 +16,17 @@ All keys from `env` will be set as environment variables into Apify platform aft
     "version": "0.1",
     "buildTag": "latest",
     "environmentVariables": {
-      "MYSQL_USER": "my_username",
-      "MYSQL_PASSWORD": "@mySecretPassword"
+        "MYSQL_USER": "my_username",
+        "MYSQL_PASSWORD": "@mySecretPassword"
     }
 }
 ```
 
 ### Set up environment variables in Apify Console
+
 In [Apify Console](https://console.apify.com/actors) select your actor, you can set up variables into Source tab.
 After setting up variables in the app, remove the `environmentVariables` from `.actor/actor.json`. Otherwise, variables from `.actor/actor.json` will override variables in the app.
+
 ```json
 {
     "actorSpecification": 1,
@@ -32,15 +36,17 @@ After setting up variables in the app, remove the `environmentVariables` from `.
 }
 ```
 
-
 #### How to set secret environment variables in `.actor/actor.json`
 
 CLI provides commands to manage secrets environment variables. Secrets are stored to the `~/.apify` directory.
 You can add a new secret using the command:
+
 ```bash
-apify secrets:add mySecretPassword pwd1234
+apify secrets add mySecretPassword pwd1234
 ```
+
 After adding a new secret you can use the secret in `.actor/actor.json`.
+
 ```text
 {
     "actorSpecification": 1,
diff --git a/package.json b/package.json
index d2e94d6..2782f11 100644
--- a/package.json
+++ b/package.json
@@ -138,7 +138,8 @@
         ],
         "additionalVersionFlags": [
             "-v"
-        ]
+        ],
+        "topicSeparator": " "
     },
     "volta": {
         "node": "20.11.1",
diff --git a/src/commands/secrets/index.ts b/src/commands/secrets/index.ts
index 4e7b968..7708af1 100644
--- a/src/commands/secrets/index.ts
+++ b/src/commands/secrets/index.ts
@@ -4,7 +4,7 @@ import { LOCAL_CONFIG_PATH } from '../../lib/consts.js';
 export class SecretsIndexCommand extends ApifyCommand<typeof SecretsIndexCommand> {
     static override description = 'Manages secret values for Actor environment variables.\n\n'
     + 'Example:\n'
-    + '$ apify secrets:add mySecret TopSecretValue123\n\n'
+    + '$ apify secrets add mySecret TopSecretValue123\n\n'
     + `Now the "mySecret" value can be used in an environment variable defined in "${LOCAL_CONFIG_PATH}" file by adding the "@" prefix:\n\n`
     + '{\n'
     + '  "actorSpecification": 1,\n'
diff --git a/src/lib/secrets.ts b/src/lib/secrets.ts
index dd62098..0e458ee 100644
--- a/src/lib/secrets.ts
+++ b/src/lib/secrets.ts
@@ -28,7 +28,7 @@ const writeSecretsFile = (secrets: Record<string, string>) => {
 export const addSecret = (name: string, value: string) => {
     const secrets = getSecretsFile();
 
-    if (secrets[name]) throw new Error(`Secret with name ${name} already exists. Call "apify secrets:rm ${name}" to remove it.`);
+    if (secrets[name]) throw new Error(`Secret with name ${name} already exists. Call "apify secrets rm ${name}" to remove it.`);
     if (!_.isString(name) || name.length > MAX_ENV_VAR_NAME_LENGTH) {
         throw new Error(`Secret name has to be string with maximum length ${MAX_ENV_VAR_NAME_LENGTH}.`);
     }
@@ -66,7 +66,7 @@ export const replaceSecretsValue = (env: Record<string, string>, secrets?: Recor
                 // @ts-expect-error - we are replacing the value
                 updatedEnv[key] = secrets[secretKey];
             } else {
-                warning(`Value for ${secretKey} not found in local secrets. Set it by calling "apify secrets:add ${secretKey} [SECRET_VALUE]"`);
+                warning(`Value for ${secretKey} not found in local secrets. Set it by calling "apify secrets add ${secretKey} [SECRET_VALUE]"`);
             }
         } else {
             // @ts-expect-error - we are replacing the value
@@ -100,7 +100,7 @@ export const transformEnvToEnvVars = (env: Record<string, string>, secrets?: Rec
                     isSecret: true,
                 });
             } else {
-                warning(`Value for ${secretKey} not found in local secrets. Set it by calling "apify secrets:add ${secretKey} [SECRET_VALUE]"`);
+                warning(`Value for ${secretKey} not found in local secrets. Set it by calling "apify secrets add ${secretKey} [SECRET_VALUE]"`);
             }
         } else {
             envVars.push({


Test patch:
diff --git a/test/commands/secrets/add.test.ts b/test/commands/secrets/add.test.ts
index 25d41a4..ff1e46c 100644
--- a/test/commands/secrets/add.test.ts
+++ b/test/commands/secrets/add.test.ts
@@ -5,6 +5,7 @@ import { getSecretsFile } from '../../../src/lib/secrets.js';
 import { useAuthSetup } from '../../__setup__/hooks/useAuthSetup.js';
 
 const SECRET_KEY = 'mySecret';
+const SECRET_KEY_2 = 'mySecret2';
 const SECRET_VALUE = 'mySecretValue';
 
 useAuthSetup({ perTest: false });
@@ -24,11 +25,22 @@ describe('apify secrets:add', () => {
             expect(secrets[SECRET_KEY]).to.eql(SECRET_VALUE);
         });
 
+    test
+        .command(['secrets add', SECRET_KEY_2, SECRET_VALUE])
+        .it('should work with alias', async () => {
+            const secrets = getSecretsFile();
+            expect(secrets[SECRET_KEY_2]).to.eql(SECRET_VALUE);
+        });
+
     afterAll(async () => {
         const secrets = getSecretsFile();
 
         if (secrets[SECRET_KEY]) {
             await SecretRmCommand.run([SECRET_KEY], import.meta.url);
         }
+
+        if (secrets[SECRET_KEY_2]) {
+            await SecretRmCommand.run([SECRET_KEY_2], import.meta.url);
+        }
     });
 });