diff --git a/README.md b/README.md index fb6390fe..e9d68553 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Elastic CLI -Interact with Elasticsearch, Elastic Serverless and Elastic Cloud APIs from the command line. +Interact with the Elastic Stack and Elastic Cloud from the command line. ## Installation @@ -169,15 +169,21 @@ elastic version elastic --json version ``` -### `es` - Elasticsearch API +### `stack` - Elastic Stack -Run Elasticsearch API calls. Commands map directly to Elasticsearch API endpoints. +Interact with Elastic Stack components. Today only `stack es` (Elasticsearch) is +wired up; `stack kibana` and `stack fleet` are reserved for future work. ```bash -elastic es --help +elastic stack --help +elastic stack es --help ``` -All `es` subcommands support: +#### `stack es` - Elasticsearch API + +Run Elasticsearch API calls. Commands map directly to Elasticsearch API endpoints. + +All `stack es` subcommands support: | Option | Description | |---|---| @@ -208,21 +214,21 @@ All `es` subcommands support: - `tasks` - task management - `transform` - transforms -**Top-level `es` commands** (examples): +**Top-level `stack es` commands** (examples): ```bash -elastic es search --index my-index -elastic es get --index my-index --id abc123 -elastic es index --index my-index --id abc123 -elastic es delete --index my-index --id abc123 -elastic es count --index my-index -elastic es info -elastic es bulk -elastic es reindex -elastic es update --index my-index --id abc123 +elastic stack es search --index my-index +elastic stack es get --index my-index --id abc123 +elastic stack es index --index my-index --id abc123 +elastic stack es delete --index my-index --id abc123 +elastic stack es count --index my-index +elastic stack es info +elastic stack es bulk +elastic stack es reindex +elastic stack es update --index my-index --id abc123 ``` -Run `elastic es --help` for all available options on any command. +Run `elastic stack es --help` for all available options on any command. ### `cloud` - Elastic Cloud (hosted) diff --git a/package.json b/package.json index d6c790d3..398f459f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@elastic/cli", "version": "0.1.0-alpha.1", - "description": "Interact with Elasticsearch, Elastic Serverless and Elastic Cloud APIs from the command line.", + "description": "Interact with the Elastic Stack and Elastic Cloud from the command line.", "main": "dist/cli.js", "bin": { "elastic": "dist/cli.js" diff --git a/src/cli.ts b/src/cli.ts index a8145c65..c4a23d90 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -17,7 +17,7 @@ const program = new Command() program .name('elastic') - .description('Interface with Elasticsearch, Elastic Serverless and Elastic Cloud APIs from the command line.') + .description('Interface with the Elastic Stack and Elastic Cloud from the command line.') .option('--config-file ', 'path to a config file (default: ~/.elasticrc.yml)') .option('--use-context ', 'override the active context from the config file') .option('--json', 'output as JSON') @@ -60,11 +60,14 @@ program.addCommand(versionCmd) const { operands } = program.parseOptions(process.argv.slice(2)) const firstArg = operands[0] -if (firstArg === 'es') { +if (firstArg === 'stack') { const { registerEsCommands } = await import('./es/register.ts') - program.addCommand(registerEsCommands()) + program.addCommand(defineGroup( + { name: 'stack', description: 'Interact with Elastic Stack components (Elasticsearch, Kibana, Fleet)' }, + registerEsCommands() + )) } else { - program.addCommand(defineGroup({ name: 'es', description: 'Interact with the Elasticsearch API' })) + program.addCommand(defineGroup({ name: 'stack', description: 'Interact with Elastic Stack components (Elasticsearch, Kibana, Fleet)' })) } if (firstArg === 'cloud') { diff --git a/src/es/register.ts b/src/es/register.ts index 00ff55bb..a90f1877 100644 --- a/src/es/register.ts +++ b/src/es/register.ts @@ -29,10 +29,11 @@ function buildLeafHandle ( } /** - * Registers all Elasticsearch API commands under a top-level `es` group. + * Registers all Elasticsearch API commands under an `es` group, intended to be + * nested under the top-level `stack` group by the CLI entrypoint. * - * Definitions with a `namespace` are grouped into a sub-group (`elastic es `). - * Definitions without a `namespace` are registered as direct leaves (`elastic es `). + * Definitions with a `namespace` are grouped into a sub-group (`elastic stack es `). + * Definitions without a `namespace` are registered as direct leaves (`elastic stack es `). * * For each definition: * 1. `def.input` is passed directly to `defineCommand` as the `input` schema, so the diff --git a/src/es/types.ts b/src/es/types.ts index 9c330888..ad5361f5 100644 --- a/src/es/types.ts +++ b/src/es/types.ts @@ -25,7 +25,7 @@ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' * * @example * ```ts - * // namespaced: registers as `elastic es indices create` + * // namespaced: registers as `elastic stack es indices create` * const createDef: EsApiDefinition = { * name: 'create', * namespace: 'indices', @@ -39,7 +39,7 @@ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' * }), * } * - * // namespace-less: registers as `elastic es search` + * // namespace-less: registers as `elastic stack es search` * const searchDef: EsApiDefinition = { * name: 'search', * description: 'Run a search', diff --git a/test/cli.test.ts b/test/cli.test.ts index ff1aa528..2391b39b 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -109,7 +109,7 @@ describe('elastic CLI -- preAction config error handling', () => { it('exits with error when no config file is found', async () => { const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-noconfig-')) try { - const { code, stderr } = await runCli(['es', 'info'], { cwd: dir, env: { HOME: dir, XDG_CONFIG_HOME: dir } }) + const { code, stderr } = await runCli(['stack', 'es', 'info'], { cwd: dir, env: { HOME: dir, XDG_CONFIG_HOME: dir } }) assert.equal(code, 1, `expected exit code 1, got ${code}`) assert.ok(stderr.includes('Error:'), `expected stderr to contain "Error:", got: ${stderr}`) assert.ok(stderr.includes('No configuration file found'), `expected config error message, got: ${stderr}`) @@ -122,7 +122,7 @@ describe('elastic CLI -- preAction config error handling', () => { const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-badconfig-')) try { const { code, stderr } = await runCli( - ['es', 'info', '--config-file', '/nonexistent/path.yml'], + ['stack', 'es', 'info', '--config-file', '/nonexistent/path.yml'], { cwd: dir, env: { HOME: dir, XDG_CONFIG_HOME: dir } } ) assert.equal(code, 1, `expected exit code 1, got ${code}`) @@ -146,3 +146,41 @@ describe('elastic CLI -- config-free commands', () => { } }) }) + +describe('elastic CLI -- stack command tree', () => { + it('top-level help lists `stack` and not `es`', async () => { + const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-help-')) + try { + const { code, stdout } = await runCli(['--help'], { cwd: dir, env: { HOME: dir } }) + assert.equal(code, 0, `expected exit code 0, got ${code}`) + assert.match(stdout, /^\s*stack\s/m, 'expected `stack` in top-level help') + assert.doesNotMatch(stdout, /^\s*es\s/m, '`es` must not appear as a top-level command') + } finally { + await rm(dir, { recursive: true }) + } + }) + + it('`elastic stack --help` lists the `es` sub-group', async () => { + const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-stack-help-')) + try { + const { code, stdout } = await runCli(['stack', '--help'], { cwd: dir, env: { HOME: dir } }) + assert.equal(code, 0, `expected exit code 0, got ${code}`) + assert.match(stdout, /^\s*es\s/m, 'expected `es` under stack') + } finally { + await rm(dir, { recursive: true }) + } + }) + + it('`elastic stack es --help` lists namespace groups (indices, cluster, ml)', async () => { + const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-stack-es-help-')) + try { + const { code, stdout } = await runCli(['stack', 'es', '--help'], { cwd: dir, env: { HOME: dir } }) + assert.equal(code, 0, `expected exit code 0, got ${code}`) + assert.match(stdout, /^\s*indices\s/m, 'expected `indices` group') + assert.match(stdout, /^\s*cluster\s/m, 'expected `cluster` group') + assert.match(stdout, /^\s*ml\s/m, 'expected `ml` group') + } finally { + await rm(dir, { recursive: true }) + } + }) +})