diff --git a/cli/README.md b/cli/README.md index b7998ad4..bd060af4 100644 --- a/cli/README.md +++ b/cli/README.md @@ -45,6 +45,17 @@ Operator commands (`platform`, `repo`, `github set-token`) use **operator AWS cr ## Commands +### `bgagent version` + +Print the installed CLI version (read from `package.json`). Equivalent to the root `bgagent --version` flag, but available as an explicit subcommand with a `--output json` mode for scripting. + +``` +bgagent version \ + --output Output format (default: text) +``` + +Text mode prints the bare version string (e.g. `0.0.0`); JSON mode prints `{ "version": "0.0.0" }`. + ### `bgagent configure` Save API endpoint and Cognito settings to `~/.bgagent/config.json`. diff --git a/cli/src/bin/bgagent.ts b/cli/src/bin/bgagent.ts index 5faf408f..1f029341 100644 --- a/cli/src/bin/bgagent.ts +++ b/cli/src/bin/bgagent.ts @@ -43,6 +43,7 @@ import { makeSlackCommand } from '../commands/slack'; import { makeStatusCommand } from '../commands/status'; import { makeSubmitCommand } from '../commands/submit'; import { makeTraceCommand } from '../commands/trace'; +import { getCliVersion, makeVersionCommand } from '../commands/version'; import { makeWatchCommand } from '../commands/watch'; import { makeWebhookCommand } from '../commands/webhook'; import { setVerbose } from '../debug'; @@ -53,7 +54,7 @@ const program = new Command(); program .name('bgagent') .description('Background Agent CLI — submit and manage coding tasks') - .version('0.0.0') + .version(getCliVersion()) .option('--verbose', 'Enable debug output') .hook('preAction', (_thisCommand, actionCommand) => { // Resolve --verbose from the root program, not the subcommand @@ -86,6 +87,7 @@ program.addCommand(makeRuntimeCommand()); program.addCommand(makeOpsCommand()); program.addCommand(makeWatchCommand()); program.addCommand(makeTraceCommand()); +program.addCommand(makeVersionCommand()); program.addCommand(makeWebhookCommand()); program.addCommand(makeAdminCommand()); diff --git a/cli/src/commands/version.ts b/cli/src/commands/version.ts new file mode 100644 index 00000000..bec76e0e --- /dev/null +++ b/cli/src/commands/version.ts @@ -0,0 +1,61 @@ +/** + * MIT No Attribution + * + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import { Command } from 'commander'; +import { formatJson } from '../format'; + +/** + * Resolve the CLI version from the package manifest. + * + * The manifest lives at ``cli/package.json``, two directories above this + * module in both the TypeScript source tree (``src/commands``) and the + * compiled output (``lib/commands``), so ``../../package.json`` resolves + * correctly under ts-jest and from the published ``lib`` bin alike. We read + * it at runtime rather than importing it so the version reported always + * matches the installed package and the compiled ``lib`` layout stays flat + * (no ``package.json`` copied under ``rootDir``). + */ +export function getCliVersion(): string { + const manifestPath = path.join(__dirname, '..', '..', 'package.json'); + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as { + version?: string; + }; + return manifest.version ?? '0.0.0'; +} + +/** + * `bgagent version [--output text|json]` — print the installed CLI version, + * read from ``package.json``. Mirrors the root ``--version`` flag as an + * explicit subcommand so scripts can query it uniformly (and in JSON). + */ +export function makeVersionCommand(): Command { + return new Command('version') + .description('Print the bgagent CLI version') + .option('--output ', 'Output format (text or json)', 'text') + .action((opts: { output: string }) => { + const version = getCliVersion(); + if (opts.output === 'json') { + console.log(formatJson({ version })); + return; + } + console.log(version); + }); +} diff --git a/cli/test/commands/version.test.ts b/cli/test/commands/version.test.ts new file mode 100644 index 00000000..dd61126c --- /dev/null +++ b/cli/test/commands/version.test.ts @@ -0,0 +1,64 @@ +/** + * MIT No Attribution + * + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import { getCliVersion, makeVersionCommand } from '../../src/commands/version'; + +describe('version command', () => { + let consoleSpy: jest.SpiedFunction; + + // The version reported must match the single source of truth: the + // package manifest. Reading it here (rather than hardcoding) keeps the + // test correct across version bumps. + const packageVersion = ( + JSON.parse( + fs.readFileSync( + path.join(__dirname, '..', '..', 'package.json'), + 'utf-8', + ), + ) as { version: string } + ).version; + + beforeEach(() => { + process.exitCode = undefined; + consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + }); + + afterEach(() => { + process.exitCode = undefined; + consoleSpy.mockRestore(); + }); + + test('getCliVersion returns the package.json version', () => { + expect(getCliVersion()).toBe(packageVersion); + }); + + test('prints the plain version in text mode (default)', async () => { + await makeVersionCommand().parseAsync(['node', 'version']); + expect(consoleSpy).toHaveBeenCalledTimes(1); + expect(consoleSpy).toHaveBeenCalledWith(packageVersion); + }); + + test('prints a JSON object with --output json', async () => { + await makeVersionCommand().parseAsync(['node', 'version', '--output', 'json']); + const out = consoleSpy.mock.calls.map((c) => c[0]).join('\n'); + expect(JSON.parse(out)).toEqual({ version: packageVersion }); + }); +});