/** * What a command is, for `cleo run` and the provider hook (T12983): the * governor class it needs, whether it is heavy at all, and whether it may be * paused. Pure and dependency-free (type-only imports, plus `node:fs` for the * workspace-root check), so a hook that runs on every Bash call can import it * cheaply. * * Only the COMMAND WORD counts: the tool (`argv[0]` after `env`/`nice`/ * `time` and `NAME=value` prefixes), or for a package manager its script * (`pnpm build`, `npm run test:unit`) or exec'd tool (`npx vitest`, * `pnpm exec tsc`, `pnpm dlx`). A tool name inside an argument never counts * (`git commit -m "fix vitest"`, `grep -rn tsc .`, `cd packages/next`). * `--version`/`--help` and watch/dev/serve modes are never heavy: a watcher * would hold a slot forever. * * @module resources/run-class * @task T12979 * @epic T12978 */ import type { ResourceClass } from '@cleocode/contracts'; import type { CanonicalTool } from '../tasks/tool-resolver.js'; /** Short class names accepted by `cleo run --class`. */ export declare const RUN_CLASS_ALIASES: Readonly>; /** * The package manager a script is the entrypoint of, or `null` (T14090). * * @param script - A script path, as `node` was given it. * @returns `pnpm`, `npm` or `yarn`, or `null` for any other script. * * @example * ```ts * packageManagerOfScript('/home/u/.cache/node/corepack/v1/pnpm/10.34.5/bin/pnpm.cjs'); // 'pnpm' * packageManagerOfScript('scripts/gen.mjs'); // null * ``` */ export declare function packageManagerOfScript(script: string): string | null; /** What an argv runs: its tool, or a package manager's script / exec target. */ export interface CommandTarget { /** The program that runs the work (`vitest`, `tsc`, `cargo`, or the pm). */ readonly tool: string; /** The package-manager verb or script (`build`, `test:unit`, `install`), else null. */ readonly script: string | null; /** The package manager, when the command went through one. */ readonly pm: string | null; /** Arguments after the tool or script. */ readonly rest: readonly string[]; /** A workspace-scoping flag was given (`--filter`, `-C`, …). */ readonly scoped: boolean; /** A run-everywhere flag was given (`-r`, `--workspaces`, `yarn workspaces foreach`). */ readonly recursive: boolean; } /** * Resolve the command word of `argv`. * * @example * ```ts * commandTarget(['pnpm', '--filter', 'x', 'run', 'test:unit']).script; // 'test:unit' * commandTarget(['npx', '-y', 'vitest', 'run']).tool; // 'vitest' * commandTarget(['env', 'CI=1', 'tsc', '-b']).tool; // 'tsc' * ``` */ export declare function commandTarget(argv: readonly string[]): CommandTarget; /** * Whether a script or task name serves or watches: `dev`, `start` or * `preview` as its first segment (`dev`, `dev:web`), or `watch`/`serve` in * any segment (`test:watch`, `docs:serve`). `build:dev`, `build:preview` and * `test:dev` are one-shot runs. */ export declare function isWatchName(name: string): boolean; /** * Whether `argv` is a heavy command: a test runner, compiler, bundler or * linter as the command word, or a package-manager test/build/typecheck/lint/ * install script, or `cargo`/`go` build or test. Never `--version`/`--help`, * never a watch/dev/serve mode. * * @example * ```ts * looksHeavy(['npx', 'vitest', 'run']); // true * looksHeavy(['git', 'commit', '-m', 'fix vitest']); // false * looksHeavy(['pnpm', 'dev']); // false * looksHeavy(['tsc', '--version']); // false * ``` */ export declare function looksHeavy(argv: readonly string[]): boolean; /** * Whether `argv` is a watcher or server (`pnpm dev`, `turbo run dev`, * `vitest --ui`, `vite preview`): it never exits, so `cleo run` refuses it * rather than let it hold a slot forever (an explicit `--class` overrides). * Never `--version`/`--help`, a flag value (`pytest -k dev`, `vitest -t serve`) * or a `-w` that means something else (`jest -w 2`, `prettier -w .`). */ export declare function isWatchCommand(argv: readonly string[]): boolean; /** * The governor class for a command. * * An explicit `--class` wins (aliases above). Otherwise: a test runner, a * `test` script, `npm t`, `bun test`, `cargo|go test`, or turbo/nx running * only test tasks is `test-run`; a build that spans the workspace (`-r`, * `--workspaces`, `yarn workspaces foreach`, an unscoped build at a workspace * root, or any non-test turbo / `nx run-many|affected` run, `build test` * included) is `full-build`; anything else is `scoped-build`. * * @param explicit - the `--class` value, if given. * @param argv - the command. * @param cwd - where it runs (for the workspace-root check). * @throws {Error} when `explicit` is not a known class or alias. */ export declare function resolveRunClass(explicit: string | undefined, argv: readonly string[], cwd: string): ResourceClass; /** * How many test files a test run names (`vitest run a.test.ts b.test.ts` → 2), * or `null` when it names none (a whole suite, a filter or a script). Such a * run needs at most one worker per file, so `cleo run` plans and charges it * that many and spawns it with that worker cap: a single-file run takes one * worker's share of the machine budget, not a whole suite's (T13132). The * value of `--exclude`/`--ignore` is not counted, and a glob makes the count * unknown (`null`). A `--maxWorkers=N` of the command's own still outranks * the cap and the charge. * * @param cls - the run's class; only `test-run` names test files. * @param argv - the command. * * @example * ```ts * namedTestFileCount('test-run', ['pnpm', 'exec', 'vitest', 'run', 'src/a.test.ts']); // 1 * namedTestFileCount('test-run', ['pnpm', 'test']); // null * ``` */ export declare function namedTestFileCount(cls: ResourceClass, argv: readonly string[]): number | null; /** * Whether a command runs a WHOLE vitest suite without saying so (T13236): * * - `vitest run` (`pnpm exec`, `npx`, `pnpm vitest`) naming no test file, * directory or name filter and no `--project`, `-t`, `--changed` or * `--related`. An empty or blank word, `.` and `./` narrow nothing: an * empty generated list (`vitest run $files`, or quoted `"$files"`, which * passes `''` — a substring filter matching every file) is the usual * cause, and twice ran a whole package suite by accident. * - a package-manager script whose text runs vitest (`pnpm test`, * `pnpm run test`, `pnpm -r test`, `pnpm --filter x test`; every `test` * script in this repo is `vitest run …`), unless the caller's own * arguments narrow it (`pnpm test src/a.test.ts`, `pnpm test:pkg core`). * The script is read from the package.json nearest `cwd` (or `-C` dir); * for `-r` / `--filter` (T13277) from EACH matched workspace package, and a * script that itself runs `pnpm -r …` / `--filter …` is followed. One that does * not run vitest (or is missing) is not refused. * * `cleo run` refuses these unless `--whole-suite` says it is deliberate. * Only vitest's arguments narrow: a package-manager `--filter` still runs * that package's whole suite. An unknown flag's separate value counts as a * filter, so a doubtful command is let through, never refused. * * @param argv - the command. * @param cwd - where it runs (for a package script). @defaultValue process.cwd() * @returns `true` for an unnarrowed whole-suite run. * * @example * ```ts * isWholeSuiteTestRun(['pnpm', 'exec', 'vitest', 'run']); // true * isWholeSuiteTestRun(['pnpm', 'exec', 'vitest', 'run', '']); // true (quoted empty list) * isWholeSuiteTestRun(['pnpm', 'exec', 'vitest', 'run', 'src/a.test.ts']); // false * isWholeSuiteTestRun(['pnpm', 'test'], '/repo'); // true when its script is `vitest run` * ``` */ export declare function isWholeSuiteTestRun(argv: readonly string[], cwd?: string): boolean; /** The `heavyToolEnv` canonical tool a run class sizes its env from. */ export declare function canonicalForClass(cls: ResourceClass): CanonicalTool; /** * Why a run executes tests on wall-clock deadlines, so it must never be * SIGSTOPped (T14093), or `null`. A test's timeout keeps counting while the * process is stopped, so a pause longer than one (axiom-qa: a hooked push * paused 542 s against 60 s vitest timeouts) turns into a false failure. Such * a run is queued under pressure instead, as `--no-pause` runs are (T14075). * * Covers the `test-run` class, a command that is a test run whatever class it * was given (a test runner, a `test` script, `cargo|go test`, `node --test`), * and a `git push`/`git commit` whose hook is installed: a hook may run tests. * * @param cls - the run's class. * @param argv - the command. * @param hookInstalled - whether a git hook is installed (the caller probes the run's checkout). * @returns the reason, for the run envelope and stderr, or `null`. * * @example * ```ts * testPauseExemption('scoped-build', ['pnpm', 'test'], () => false); // 'it runs tests: …' * testPauseExemption('scoped-build', ['pnpm', 'run', 'build'], () => false); // null * ``` * * @task T14093 */ export declare function testPauseExemption(cls: ResourceClass, argv: readonly string[], hookInstalled: (hook: string, globals: readonly string[]) => boolean): string | null; /** * Whether a job may be SIGSTOPped under pressure. Installs, db-heavy work and * cargo (which holds the registry cache and target-dir locks) hold shared * locks the oldest job may need, so pausing them can stall the one job that * is meant to keep the machine moving. * * CLEO's own commands are never paused either (#1777 R7-1): `cleo verify * --evidence tool:test` holds the tool-cache lock and spawns its heavy tool * DETACHED, out of the paused group. A pause would freeze only the lock * holder while the test keeps running; its lock stops refreshing, goes stale * and is taken, and it crashes on resume. * * Nor is a run that executes tests on wall-clock deadlines * ({@link testPauseExemption}, T14093). * * @param cls - the run's class. * @param argv - the command. * @param hookInstalled - whether a git hook is installed. @defaultValue none is */ export declare function isPausable(cls: ResourceClass, argv: readonly string[], hookInstalled?: (hook: string, globals: readonly string[]) => boolean): boolean; /** * How big a run is, judged from its real scope rather than its class (T13367). * * - `light`: a native formatter/linter on named paths (`biome check a.ts b.ts`). * It needs well under a gigabyte, so it is charged the light footprint. * - `single-process`: one Node process on named paths (`eslint a.ts`, * `prettier --check a.ts`) or any `tsc` without `-b` (`tsc --noEmit`, * `tsc -p `; T13440). It is charged one process's heap plus overhead, * with no worker slots. * - `class`: everything else, sized by the class's heavy-run plan as before. * That includes a root `pnpm run build`, `tsc -b`, and a linter on the whole * tree. A test run that names its files keeps its per-file worker cap * (T13132). */ export interface RunFootprint { /** The size tier. */ readonly size: 'light' | 'single-process' | 'class'; /** Why, in a few words, for the ledger and `doctor tool-locks`. */ readonly reason: string; } /** * Size a run by what it actually touches (T13367). Pure; reads nothing. * * @param argv - the command. * @returns the size tier and its reason. * * @example * ```ts * runFootprint(['pnpm', 'biome', 'check', 'a.ts', 'b.ts']).size; // 'light' * runFootprint(['pnpm', 'exec', 'tsc', '-p', 'packages/core']).size; // 'single-process' * runFootprint(['pnpm', 'run', 'build']).size; // 'class' * runFootprint(['pnpm', 'install', '--lockfile-only']).size; // 'single-process' * ``` */ export declare function runFootprint(argv: readonly string[]): RunFootprint; /** * The script `node` runs, when the command is `node ` with no node flags, outside `node_modules/` and not CLEO's own CLI * (T14050). Leading wrappers are stripped as in {@link lightCommandReason}. * * @param argv - the command. * @returns the script path as written, or `null`. * * @example * ```ts * nodeScriptOf(['node', 'scripts/typecheck/full-program.cjs']); // 'scripts/typecheck/full-program.cjs' * nodeScriptOf(['node', '--test', 'a.test.mjs']); // null * ``` */ export declare function nodeScriptOf(argv: readonly string[]): string | null; /** * The interpreter and plain script a command runs (T14079): `node x.mjs` * (T14050), `python3 check.py`, `bash lint.sh`, with no interpreter flags, * outside `node_modules/` and not CLEO's own CLI. Leading wrappers are * stripped as in {@link lightCommandReason}. * * @param argv - the command. * @returns the interpreter's name and the script path as written, or `null`. * * @example * ```ts * scriptRunOf(['python3', 'scripts/check_meta.py', 'a.yaml']); // { runner: 'python3', script: 'scripts/check_meta.py' } * scriptRunOf(['python3', '-m', 'pytest']); // null * ``` */ export declare function scriptRunOf(argv: readonly string[]): { readonly runner: string; readonly script: string; } | null; /** * Why a `node` script is a heavy tool host by its path alone (T14050): its * path names a heavy job, so it may run a compiler or test runner in-process * (`node scripts/typecheck/full-program.cjs` builds a whole TypeScript program). * * @param script - the script path, as {@link nodeScriptOf} returns it. * @returns the reason, or `null` when the path names no heavy job. * * @example * ```ts * heavyScriptReason('scripts/typecheck/full-program.cjs'); // 'its path names typecheck' * heavyScriptReason('scripts/gen.mjs'); // null * ``` */ export declare function heavyScriptReason(script: string): string | null; /** * Why a command is known to run no heavy tool (T13452), from a small * allowlist: a file utility (`ls`, `cat`, `echo`, …) or an interpreter * running a plain script with no interpreter flags ({@link scriptRunOf}: * `node x.mjs`, `python3 check.py`, `bash lint.sh`, T14079), not CLEO's own * CLI, not a package's bin under `node_modules/` such as `vitest.mjs`, and not * a script whose path names a heavy job such as `typecheck` or `build` * (T14050). What the script's content runs is judged by the plan * (`planRunFootprint`), which reads it. Leading wrappers (`timeout`, `sudo`, `env`, `nice`, `command`, * `exec`) are stripped first; a `NODE_OPTIONS=` that raises the heap is never * light. Everything else (cargo, go, pytest, gradle, docker, `python3 -m`, * `node --test`) may be heavy and keeps its class plan. Git is sized by * {@link runFootprint} and its hooks, so it is never answered here. * * @param argv - the command. * @returns the reason, or `null` when the command may be heavy. * * @example * ```ts * lightCommandReason(['node', 'scripts/gen.mjs']); // 'node scripts/gen.mjs runs no heavy tool' * lightCommandReason(['python3', 'check.py', 'a.yaml']); // 'python3 check.py runs no heavy tool' * lightCommandReason(['timeout', '5', 'ls']); // 'ls runs no heavy tool' * lightCommandReason(['node', '--test', 'a.test.mjs']); // null * lightCommandReason(['node', 'scripts/typecheck/full-program.cjs']); // null * lightCommandReason(['cargo', 'clippy']); // null * ``` */ export declare function lightCommandReason(argv: readonly string[]): string | null; /** A git command line split into global options, subcommand and its arguments. */ export interface GitInvocation { /** Global options before the subcommand, values included (`-C`, `dir`, `-c`, `k=v`). */ readonly globals: readonly string[]; /** The subcommand, or `null` when none could be found. */ readonly sub: string | null; /** Arguments after the subcommand. */ readonly args: readonly string[]; } /** * Split a git command line (T13458): `git -C /repo -c core.hooksPath=.h push` * has the subcommand `push`, not `/repo`. Value-taking global options consume * their next word. * * @param argv - The command. * @returns The split, or `null` when argv is not git. */ export declare function parseGitInvocation(argv: readonly string[]): GitInvocation | null; /** * The hooks a git command may run that could do heavy work (T13452, T13458), * and the global options to probe them with (`-C`, `-c core.hooksPath=…`). * Whether a hook is installed is the caller's question. * * @param argv - The command. * @returns The hooks and probe options, or `null` when it runs none. * * @example * ```ts * gitHookFor(['git', '-C', '/repo', 'push']); // { hooks: ['pre-push'], globals: ['-C', '/repo'] } * gitHookFor(['git', 'push', '--no-verify']); // null * gitHookFor(['git', 'status']); // null * ``` */ export declare function gitHookFor(argv: readonly string[]): { hooks: readonly string[]; globals: readonly string[]; } | null; //# sourceMappingURL=run-class.d.ts.map