/** The npm package this CLI is published as. */ export declare const PACKAGE_NAME = "@bitmagic/cli"; /** Does this path exist? Injected so classification is testable without a filesystem. */ export type Exists = (candidate: string) => boolean; /** * Where the running build lives, and what may be done about it. * * A discriminated union rather than one shape with optional fields, because the difference that * matters is precisely "is there an install to act on": the three inert kinds have no prefix, no * bin directory and no reinstall, and a caller that forgets to check would otherwise be handed * `undefined` to pass to a package manager. * * `verifyDir` is deliberately separate from `packageDir`. They are the same directory for npm, * but pnpm writes each version into its own store directory and moves a symlink — so the path * that holds the NEW version after an update is the link, not the resolved directory this process * is executing out of. Reading the wrong one would report every successful pnpm update as a * failure. */ export type InstallLocation = { kind: 'npm-global'; /** The resolved @bitmagic/cli directory. */ packageDir: string; /** Read back after an update to confirm the version actually moved. */ verifyDir: string; /** The node_modules that must be writable for an install to succeed. */ modulesRoot: string; /** npm's `--prefix`. */ prefix: string; } | { kind: 'pnpm-global'; packageDir: string; verifyDir: string; modulesRoot: string; /** pnpm's `--global-dir`. */ globalDir: string; /** pnpm's `--global-bin-dir`, or undefined when it could not be proven. */ binDir: string | undefined; } /** Run through `npx`: nothing is installed, so there is nothing to update. */ | { kind: 'npx'; packageDir: string; } /** A checkout or a `pnpm link` — updated with git and a build, not with a registry install. */ | { kind: 'linked-dev'; packageDir: string; } /** A shape this does not recognise. Refused rather than guessed at. */ | { kind: 'unknown'; packageDir: string; }; /** The kinds that name a real install a package manager can be pointed at. */ export type UpdatableLocation = Extract; export declare function isUpdatable(location: InstallLocation): location is UpdatableLocation; /** * This package's own directory on disk. * * `../../package.json` resolves to this package's manifest from both `dist/update/` and * `src/update/`, which is the same specifier — and the same reason — as * `cli-version.ts`'s `currentCliVersion`. Node resolves symlinks here, so under pnpm this is the * store directory rather than the linked one; `classifyInstall` is written for that. */ export declare function packageDirFrom(moduleUrl: string): string; /** * Read the install shape straight off the resolved package path. * * Order matters: an npx cache and a checkout both sit under directory names that would otherwise * be read as an ordinary install, so they are excluded first. The remaining two are told apart by * the store layout each package manager writes, not by asking which manager is on PATH — that * question has the same wrong answer as the one this module exists to stop asking. */ export declare function classifyInstall(packageDir: string, exists?: Exists): InstallLocation; /** * pnpm refuses `add -g` without a global bin directory it can write, and there is no way to read * back the one an install used — `PNPM_HOME` is a shell variable, and the whole point here is that * the shell may tell us nothing. So it is recovered from the disk instead: the directory holding * the `bitmagic` command that this install produced. * * `/bin` is the README's two-root recipe (`--global-dir ~/.bitmagic-cli/prod * --global-bin-dir ~/.bitmagic-cli/prod/bin`); the parent of `globalDir` is pnpm's own default, * where `~/Library/pnpm/global` pairs with `~/Library/pnpm`. * * Undefined when neither holds the command — the caller then refuses rather than installing into * a guessed root, which is the failure mode this module exists to prevent. */ export declare function pnpmBinDir(globalDir: string, exists?: Exists): string | undefined; /** An executable and the arguments that must come before the package manager's own subcommand. */ export interface ManagerCommand { command: string; leadingArgs: string[]; /** * True when nothing could be found on disk and the name was left for PATH to resolve — i.e. the * one case where this is back to guessing, and the messages say so. */ fromPath: boolean; } /** * npm, found without consulting PATH. * * npm ships inside the node installation, so the node binary executing this process names it — * and running that script with `process.execPath` is the whole fix: it is by construction the same * node, and therefore the same global prefix, this CLI was installed into, whatever nvm did or did * not do to PATH. * * The install's own prefix is tried first, because that is where the package demonstrably lives; * the node's own tree is the fallback for the ordinary case where a global prefix carries no npm * of its own. */ export declare function npmCommand(execPath: string, installPrefix: string | undefined, exists?: Exists): ManagerCommand; /** * pnpm, which — unlike npm — is not bundled with node and so cannot be derived from `execPath`. * * Searching this widely is safe in a way the npm equivalent is not, and the difference is worth * stating: `installArgs` pins pnpm's target with `--global-dir` and `--global-bin-dir`, so WHICH * pnpm runs cannot change WHERE it writes. Any pnpm on the disk is therefore a correct one, and * the only wrong answer is not finding one — which is why the search widens rather than stopping * at the two directories that imply the install. * * The order still runs from most-implied to least: the bin directory this install's own * `bitmagic` came from, then pnpm's default root beside the global dir, then the places a pnpm * lives on a machine where it was installed independently of either. * * Failing all of them the name is left to PATH, flagged — and a shell with no pnpm then gets a * clear refusal naming the resolved global dir, which is still strictly better than an install * silently landing elsewhere. */ export declare function pnpmCommand(globalDir: string, binDir: string | undefined, exists?: Exists, execPath?: string): ManagerCommand; /** The manager that owns `location`, resolved without PATH wherever that is possible. */ export declare function managerFor(location: UpdatableLocation, execPath: string, exists?: Exists): ManagerCommand; /** * Install `spec` into exactly this location. * * The target is named explicitly (`--prefix` / `--global-dir`) even though the executable resolved * above already implies it, because the two say different things: the executable decides which * npm runs, the flag decides where it writes. The README's side-by-side recipe puts two installs * under one node, so only the flag can tell them apart. * * `--prefer-online` is not belt-and-braces. The CLI pins its three `@bitmagic/*` dependencies to * exact versions and publishes last, so for a few minutes after a release an installer holding a * stale packument resolves the new CLI and then fails ETARGET on a dependency that is already * there — see docs/npm-publishing.md, "The first minutes after a release". Forcing the refetch is * the documented fix. */ export declare function installArgs(location: UpdatableLocation, spec: string): string[]; /** The mirror of `installArgs`, for `bitmagic reset-account`'s "start over like a new creator". */ export declare function uninstallArgs(location: UpdatableLocation): string[]; /** What a spawned package manager did. Shaped so a test can supply one without a subprocess. */ export interface SpawnOutcome { status: number | null; stdout: string; stderr: string; /** Set when the process could not be started at all — e.g. no `pnpm` on PATH. */ error: Error | undefined; } /** * @param pathEntry a directory to put at the FRONT of the spawned process's `PATH`, or undefined. * Only ever pnpm's global bin directory; see `environmentFor`. */ export type SpawnManager = (command: string, args: string[], pathEntry?: string) => SpawnOutcome; /** The `PATH` entry a spawned manager needs, or undefined when it needs none. */ export declare function pathEntryFor(location: UpdatableLocation): string | undefined; /** `entry` first, then whatever `PATH` already held, without duplicating it. */ export declare function prependToPath(entry: string, current: string | undefined): string; /** * Output is CAPTURED, not inherited — the same rule, for the same reason, as * `reset-account`'s uninstall: under `--json` this CLI's stdout is contractually one document, and * a subprocess writing its own progress there would corrupt it. Nothing is printed unless the * install fails, and then it goes out through the `Output` seam like every other line. */ export declare const realSpawnManager: SpawnManager; /** * The version sitting in `dir` right now, read off the disk. * * Deliberately not `currentCliVersion()`: that reports the manifest this process loaded at start, * which after an update is the OLD one. Reading the file again is the only way to tell an install * that moved from one that reported success and did nothing — the silent no-op that this whole * module exists to make impossible. */ export declare function readInstalledVersion(dir: string, readFile?: (p: string) => string): string | null; /** Whether `dir` can be written to, checked before spawning an install that would fail on it. */ export declare function isWritable(dir: string, access?: (p: string) => void): boolean; /** * npm's answer when a version it can see on the registry is not yet in its cached packument. The * documented remedy is to retry, which is what the caller does — once, and only for this. */ export declare function isStalePackumentFailure(outcome: SpawnOutcome): boolean; /** Where this build actually is. The one impure entry point; everything above it is a pure rule. */ export declare function resolveInstall(moduleUrl: string, exists?: Exists): InstallLocation;