diff --git a/profiler-cli/guide.txt b/profiler-cli/guide.txt index 242c44b168..ec5c41e7c5 100644 --- a/profiler-cli/guide.txt +++ b/profiler-cli/guide.txt @@ -461,6 +461,9 @@ SCRIPTING profiler-cli profile info --session my-analysis profiler-cli thread select t-0 --session my-analysis + The --session option can also appear before the command: + profiler-cli --session my-analysis profile info + Always use --json for reliable output parsing. Plain text output is for human reading and may change; the JSON schema is stable. diff --git a/profiler-cli/src/commands/shared.ts b/profiler-cli/src/commands/shared.ts index 0119fd817f..60e0b6390e 100644 --- a/profiler-cli/src/commands/shared.ts +++ b/profiler-cli/src/commands/shared.ts @@ -133,16 +133,21 @@ export function wasExplicit(parent: string, subcommand: string): boolean { return idx !== -1 && args[idx + 1] === subcommand; } +/** + * Add the --session option to a command. + */ +export function addSessionOption(cmd: Command): Command { + return cmd.option( + '--session ', + 'Use a specific session (default: current session)' + ); +} + /** * Add --session and --json options to a command. */ export function addGlobalOptions(cmd: Command): Command { - return cmd - .option( - '--session ', - 'Use a specific session (default: current session)' - ) - .option('--json', 'Output results as JSON'); + return addSessionOption(cmd).option('--json', 'Output results as JSON'); } /** diff --git a/profiler-cli/src/index.ts b/profiler-cli/src/index.ts index ce6c0a2564..b3c6eea93f 100644 --- a/profiler-cli/src/index.ts +++ b/profiler-cli/src/index.ts @@ -32,7 +32,11 @@ import { startDaemon } from './daemon'; import { startNewDaemon, stopDaemon, sendCommand } from './client'; import { listSessions } from './session'; import { formatOutput } from './output'; -import { addGlobalOptions, runCommand } from './commands/shared'; +import { + addGlobalOptions, + addSessionOption, + runCommand, +} from './commands/shared'; import { VERSION } from './constants'; import { registerProfileCommand } from './commands/profile'; import { registerThreadCommand } from './commands/thread'; @@ -79,7 +83,12 @@ async function main(): Promise { return; } - const program = new Command(); + const program = addSessionOption(new Command()); + program.hook('preAction', (rootCommand, actionCommand) => { + if (actionCommand.opts().session === undefined) { + actionCommand.setOptionValue('session', rootCommand.opts().session); + } + }); program .name('profiler-cli') .description('Profiler CLI — query Firefox profiles from the terminal') diff --git a/profiler-cli/src/test/integration/sessions.test.ts b/profiler-cli/src/test/integration/sessions.test.ts index 258d8028ef..e776a967bf 100644 --- a/profiler-cli/src/test/integration/sessions.test.ts +++ b/profiler-cli/src/test/integration/sessions.test.ts @@ -58,6 +58,60 @@ describe('profiler-cli multiple concurrent sessions', () => { await cli(ctx, ['stop', '--session', session2]); }); + it('accepts --session before, between, and after subcommands', async () => { + const loaded = await cli(ctx, [ + '--session', + 'target-session', + 'load', + 'src/test/fixtures/upgrades/processed-1.json', + ]); + expect(loaded.stdout).toContain('Session started: target-session'); + + await cli(ctx, [ + 'load', + 'src/test/fixtures/upgrades/processed-2.json', + '--session', + 'current-session', + ]); + + const expected = await cli(ctx, [ + 'profile', + 'info', + '--session', + 'target-session', + '--json', + ]); + for (const args of [ + ['--session', 'target-session', 'profile', 'info'], + ['profile', '--session', 'target-session', 'info'], + ['--session=target-session', 'profile', 'info'], + [ + '--session', + 'missing-session', + 'profile', + 'info', + '--session', + 'target-session', + ], + ]) { + const result = await cli(ctx, [...args, '--json']); + expect(JSON.parse(result.stdout)).toEqual(JSON.parse(expected.stdout)); + } + + const missing = await cliFail(ctx, [ + '--session', + 'missing-session', + 'profile', + 'info', + ]); + expect(missing.stderr).toContain('missing-session'); + + await cli(ctx, ['--session', 'target-session', 'stop']); + const sessions = await cli(ctx, ['session', 'list']); + expect(sessions.stdout).not.toContain('target-session'); + expect(sessions.stdout).toContain('current-session'); + }); + it('session list shows running sessions and marks the current one', async () => { // Start two sessions await cli(ctx, [