nest-profiler-commander
Profile nest-commander CLI runs alongside HTTP requests.
@eleven-labs/nest-profiler-commander
@eleven-labs/nest-profiler-commander profiles CLI commands built with nest-commander — the console equivalent of Symfony's command profiling. Every command run produces a profile that shows up in the web profiler at /_profiler, in a dedicated Commands table and with a built-in Command tab, plus any HTTP, cache, or database activity the command triggered.


Installation
pnpm add -D @eleven-labs/nest-profiler-commander
# your app already owns this one (regular dependency):
pnpm add nest-commanderPeer dependencies: nest-commander ^3.20.0
Setup
The collector wraps every discovered command automatically — you do not change your command classes. The CLI follows the same split as the web app: cli.ts runs your profiler-free CliModule in production, and a dev-only cli-dev.ts runs a CliDevModule that adds a CLI profiling bundle on top:
import { Module } from '@nestjs/common';
import { ProfilerModule } from '@eleven-labs/nest-profiler';
import { CommanderCollectorModule } from '@eleven-labs/nest-profiler-commander';
@Module({
imports: [
// File storage, so the web server reads the profiles this process writes.
ProfilerModule.forRoot({ isGlobal: true, storageType: 'file' }),
CommanderCollectorModule.forRoot(),
],
})
export class ProfilingCliModule {}
ProfilingCliModuleis a dev-only bundle loaded bycli-dev.ts— see Enabling and disabling the profiler. If the profiler is installed as a production dependency behind the runtime gate, wrap the same calls inConditionalModule.registerWhen(..., isProfilerEnabled)in yourCliModule— and importConfigModule.forRoot()there, or registration hangs and the CLI exits silently.
import { Module } from '@nestjs/common';
import { CliModule } from './cli.module';
import { ProfilingCliModule } from './profiling/profiling-cli.module';
@Module({ imports: [CliModule, ProfilingCliModule] })
export class CliDevModule {}import { ConsoleLogger } from '@nestjs/common';
import { createProfilerLogger } from '@eleven-labs/nest-profiler';
import { CommandFactory } from 'nest-commander';
import { CliDevModule } from './cli.dev.module';
void CommandFactory.run(CliDevModule, {
logger: createProfilerLogger(new ConsoleLogger('Cli', { logLevels: ['log', 'warn', 'error'] })),
});The wrapped logger captures what your commands log through new Logger(MyCommand.name). Production keeps its profiler-free entry:
import { CommandFactory } from 'nest-commander';
import { CliModule } from './cli.module';
async function bootstrap(): Promise<void> {
await CommandFactory.run(CliModule, { logger: ['error', 'warn'] });
}
void bootstrap();Run the dev entry with a script such as "cli:dev": "nest start --entryFile cli-dev -p tsconfig.dev.json --". Add CommanderCollectorModule.forRoot() to your web app's ProfilingModule too, so the web UI renders command profiles.
Run a command, then open /_profiler on your HTTP app (pointed at the same storagePath) to inspect it.
Cross-process storage required. The CLI and the web server are separate processes, so command profiles are only visible in the server when both share the backing store — use
storageType: 'file'(or a Redis/DB adapter). In-memory storage is per-process; the profiler logs a warning if you profile a command against it.
What it collects
Each command run sets a command entrypoint on the profile (entrypoint.type = 'command', with this payload on entrypoint.data):
| Field | Description |
|---|---|
name | Command name from @Command({ name }) |
arguments | Positional operands (run(passedParams)) |
options | Parsed flags (run(_, options)) |
success | Whether the command completed without throwing |
arguments and options are two distinct things, and the Command tab shows them as two sections: for mycli demo:greet Grace --name Ada, arguments is ['Grace'] (the positional operands declared by @Command({ arguments })) and options is { name: 'Ada' } (the flags declared by @Option(), with their defaults applied). Both are redacted before storage — CLI values routinely carry secrets (--token=…).
A command that never reached run() is profiled too. nest-commander evaluates the @Option() value parsers while commander parses the argv, so an option parser that rejects its input (throw new Error('Unknown site parameter')) aborts the invocation before the command body runs: the profile records the command with success: false, the options commander had resolved so far (declared defaults included) plus the raw value the rejected flag was given, and the thrown error in the Exceptions tab. Its arguments are empty — commander assigns the positional operands only once every option has parsed.
Not every parse failure can be recorded. Commander handles its own argv errors — an unknown option, a missing required option, an invalid
choicesvalue, or an option parser that throws commander'sInvalidArgumentError— by printing a CLI error and callingprocess.exit()itself. The process is gone before any profile can be written, so those runs leave no profile. Throw a plainErrorfrom an option parser to have the failure profiled.
No exit code is collected: the profiler wraps run() from inside the process, so it never observes the code the CLI eventually exits with — it only knows whether the command threw, which success already says. Read success (or the profile's response.statusCode, 200 / 500) instead. Duration and timing come from the profile's standard performance data, and a thrown error appears in the Exceptions tab. Because the command body runs inside the profiler's CLS context, profile-scoped collectors (e.g. @eleven-labs/nest-profiler-http, @eleven-labs/nest-profiler-cache) capture the work a command performs and contribute their own panels.
How it works
At application bootstrap the module discovers every provider that is an instance of nest-commander's CommandRunner and wraps its run() method, plus the value parser of each of its @Option() flags (they run earlier, during commander's parse phase, and a throwing parser would otherwise abort the run without producing any profile — a parse failure is persisted through the profiler core's deferred queue, drained at application shutdown, since commander's parse phase is synchronous and cannot await a save). The wrapper synthesises a profile with a command entrypoint (entrypoint.type = 'command', the command details on entrypoint.data), opens a CLS context, runs the original command, then runs all collectors and saves the profile through the profiler's shared storage. The module registers the command entrypoint type with the profiler core, which renders command profiles in a dedicated Commands table and a built-in Command tab — register the module in your HTTP app's profiling bundle too so cross-process command profiles render there.
It also contributes the Discover / Commands view (@eleven-labs/nest-profiler-routes), listing every @Command() class with its description and, per command, its positional Arguments (from @Command({ arguments, argsDescription })) and its Options (from @Option({ flags, description, defaultValue, required })) — so the view documents the CLI the same way --help does. nest-commander is a required peer dependency of this package (it imports CommandRunner statically).