@packall/cli
The command itself — the parser, the surface as data, and the reporting.
import { … } from "@packall/cli";Services and classes
Npmrc
Service tag for .npmrc resolution.
class Npmrc {
readonly Service: {
readonly load: (explicitPath: string | undefined) => Effect<LoadedNpmrc, never, FileSystem | Path>;
}
readonly key: "@packall/cli/Npmrc"
}Npmrc.Service
type Npmrc.Service = {
readonly load: (explicitPath: string | undefined) => Effect<LoadedNpmrc, never, FileSystem | Path>;
}ProgressRenderer
Consumes progress events and renders them.
class ProgressRenderer {
new (options: {
readonly interactive: boolean;
readonly quiet: boolean;
readonly write: (text: string) => void;
readonly now?: (() => number) | undefined;
readonly columns?: number | undefined;
readonly frameIntervalMs?: number | undefined;
readonly tickIntervalMs?: number | undefined;
readonly preflightTimeoutMs?: number | undefined;
readonly ticker?: Ticker | undefined;
}): ProgressRenderer
readonly options: Required<Omit<Options, "now" | "write">> & {
readonly write: (text: string) => void;
readonly now: () => number;
}
readonly phase: "" | Phase
readonly phaseStartedAt: number
readonly lastFrameAt: number
readonly lineIsDirty: boolean
readonly ticks: number
readonly stopTicking: (() => void) | undefined
readonly state: {
readonly resolved: number;
readonly downloaded: number;
readonly downloadTotal: number;
readonly bytes: number;
readonly current: string;
}
readonly warnings: string[]
readonly collectedWarnings: readonly string[]
readonly handle: (event: ProgressEvent) => void
readonly finish: () => void
readonly tick: () => void
readonly startTicker: (phase: Phase) => void
readonly stopTicker: () => void
readonly announcePhase: (phase: Phase, total: number | undefined) => void
readonly summarizePhase: (phase: Phase) => void
readonly draw: (force?: boolean) => void
readonly frame: () => string
readonly endLine: () => void
readonly log: (line: string) => void
}ProgressRenderer.Ticker
Drives the animation between events, returning a function that stops it.
Injected rather than called directly so a test can step the spinner by
hand; the default is a setInterval that is unref'd, since an animation
must never be the reason a finished process is still running.
type ProgressRenderer.Ticker = (tick: () => void, intervalMs: number) => () => voidProgressRenderer.Options
type ProgressRenderer.Options = {
readonly interactive: boolean;
readonly quiet: boolean;
readonly write: (text: string) => void;
readonly now?: (() => number) | undefined;
readonly columns?: number | undefined;
readonly frameIntervalMs?: number | undefined;
readonly tickIntervalMs?: number | undefined;
readonly preflightTimeoutMs?: number | undefined;
readonly ticker?: Ticker | undefined;
}Transport
Service tag for the HTTP backend.
class Transport {
readonly Service: {
readonly layerFor: (options: Options) => Layer<HttpClient, never, never>;
readonly canProbeRoot: boolean;
}
readonly key: "@packall/cli/Transport"
}Transport.Options
Everything that has to be settled before a transport can exist.
Both members are here for the same reason: they are properties of the
connection, not of a request, so neither can be applied after the fact by
a caller holding a client. tls in particular is the whole reason this
takes an object — strict-ssl and cafile in an .npmrc are worth
nothing unless they reach the thing that opens the socket.
type Transport.Options = { readonly requestTimeoutMs: number; readonly tls: TlsSettings; }Transport.Service
The HTTP backend.
type Transport.Service = {
readonly layerFor: (options: Options) => Layer<HttpClient, never, never>;
readonly canProbeRoot: boolean;
}Tty
Service tag for the host.
class Tty {
readonly Service: {
readonly isInteractive: boolean;
readonly canPrompt: boolean;
readonly platform: string;
readonly columns: number;
readonly write: (text: string) => void;
readonly writeOut: (text: string) => void;
readonly exit: (code: number) => void;
readonly cwd: string;
readonly path: Path;
}
readonly key: "@packall/cli/Tty"
}Tty.Service
The host the CLI is attached to.
type Tty.Service = {
readonly isInteractive: boolean;
readonly canPrompt: boolean;
readonly platform: string;
readonly columns: number;
readonly write: (text: string) => void;
readonly writeOut: (text: string) => void;
readonly exit: (code: number) => void;
readonly cwd: string;
readonly path: Path;
}Functions
announcePlatform
The one reader of process that a service cannot be put in front of.
Effect's prompts choose between unicode and ASCII glyphs by reading
process.platform themselves, in an Effect.sync, with no typeof guard —
and there is nothing to intercept it with: Prompt.Environment is
FileSystem | Path | Terminal, and the glyphs are not an option on the
prompt. So in a page the first question the CLI asks dies on a bare
ReferenceError: process is not defined, after the resolution it was asking
about has already been paid for.
Every host layer calls this while it is being built, which is why the answer
is a field on the service rather than a constant here: only the layer knows
which host this is, and there is exactly one layer per environment. Node
finds the question already answered and this leaves it alone. A browser has
no process to leave alone, so it gets the smallest object that answers this
one question — nothing else about it is true, and nothing else should look
true either.
A build-time define in the page's bundler would also silence the crash, and
was rejected: it puts the answer somewhere no reader of this package would
think to look, and it is per-bundle rather than per-host, so a second
embedder inherits the bug.
const announcePlatform: (platform: string) => voidavailablePlatforms
The os-arch pairs the resolved packages declare.
Negations and wildcards are skipped — a package saying "not win32" tells you nothing about which platforms exist to choose between.
const availablePlatforms: (resolution: Resolution) => readonly string[]bar
A fixed-width progress bar.
Uses block-drawing characters, which every terminal that reports itself as a TTY has handled for a decade.
const bar: (fraction: number, width?: number) => stringbytes
1536 -> "1.5 KB". Decimal units, because that is what registries report.
const bytes: (value: number) => stringchooseLayout
const chooseLayout: (resolution: Resolution, config: Config, current: Layout, interactive: boolean) => Effect<Layout, never, Environment>choosePlatforms
Offers the platforms this resolution contains, and narrows to the chosen set.
Only when --interactive is on, --platform was not already given, and
optional dependencies are being followed — there is nothing to ask about
otherwise. Answering with everything selected is the same as not narrowing.
const choosePlatforms: (resolution: Resolution, config: Config, current: PlatformFilter, interactive: boolean) => Effect<PlatformFilter, never, Environment>collectInput
Merges positional specs with anything from --file, and settles pinning.
const collectInput: (config: Config, interactive: boolean) => Effect<ResolvedInput, Error, Tty | Environment>decideLayout
const decideLayout: (input: LayoutInput) => LayoutDecisionduration
93000 -> "1m 33s".
Duration.format does the decomposition — there is no reason to
reimplement "how many minutes are in 93 seconds" — but it renders every
non-zero part it finds, so 3,700,000 ms comes out as 1h 1m 40s. Two units
is as much as a progress line can spend on a number nobody is reading
precisely, and the seconds place on an hour-long run is noise that changes
every frame. So the value is quantised first and formatted after.
Sub-second values keep their own branch: Duration.format renders zero as
"0", and Done in 0 reads like a bug.
const duration: (ms: number) => stringequivalentCommand
The command that reproduces this run non-interactively.
Printed after anything was decided by a prompt, and on every --dry-run.
Exploring is how you find the right flags; this is what lets you keep them —
paste it into a script, a ticket, or a CI job and get the identical bundle
without answering a single question.
Only non-default values are emitted, so the line stays short enough to read.
const equivalentCommand: (echo: CommandEcho, tty: Service) => stringeta
Estimates remaining time from a completion rate.
const eta: (completed: number, total: number, elapsedMs: number) => string | undefinedfit
Pads or truncates to exactly width, for stable single-line redraws.
const fit: (value: string, width: number) => stringformatError
Renders a failure.
Our own errors already carry a full, actionable message — including the
suggested .npmrc line for an auth failure and the host name for an
unreachable registry — so the job here is mostly to not get in the way.
const formatError: (error: unknown) => stringimporterPath
Where the package.json sits relative to the lockfile's directory.
Separators are normalised because the lockfile formats all spell their workspace keys POSIX-style regardless of the platform that wrote them.
const importerPath: (lockfileDir: string, packageJsonDir: string, path: Path) => stringlockfileCandidates
The paths a policy would consider for a package.json, nearest first.
The walk up the directory tree is what makes this useful in a monorepo, where
packages/core/package.json has no lockfile of its own and the one that pins
it sits at the repository root. Each level records how to get back down, so
the parse can scope to that member rather than bundling every sibling's
dependencies too.
Pure, and exported so the ordering is testable without a filesystem.
const lockfileCandidates: (policy: LockfilePolicy, directory: string, path: Path) => readonly LockfileCandidate[]parseForce
Reads --force, which is deliberately overloaded.
true / false / prompt are the whole-run policies. Anything else is read
as a spec: --force tsdown esbuild@0.21.5 replaces exactly those two and
still asks about the rest, which is what you want in a directory holding a
dozen bundles when only one is stale.
Separators are commas or whitespace, for the same PowerShell reason as
--platform.
const parseForce: (force: Option<boolean>, spec: readonly string[]) => ForcePolicyparseLockfilePolicy
Reads --lockfile.
Overloaded the same way --force is, and for the same reason: the values are
a small closed set of policies plus one open-ended case, and splitting them
across two flags would make the common form longer without making anything
clearer. Anything unrecognised is a path, so a lockfile called
ci/locked.json needs no extra ceremony.
const parseLockfilePolicy: (value: string) => LockfilePolicyparsePlatforms
Parses --platform win32,linux, --platform "win32 linux", and
--platform win32 --platform linux — all to the same thing.
Whitespace counts as a separator because PowerShell turns an unquoted
win32,linux into the single argument "win32 linux": a comma builds an
array literal there, and arrays are space-joined on their way to a native
command. Accepting that costs nothing — no platform token contains a space —
and it means the obvious command works in the shell most of these users are
sitting in, rather than failing with a confusing quoting error.
const parsePlatforms: (inputs: readonly string[]) => Effect<PlatformFilter, Error, never>pluralize
pluralize(1, "package") -> "1 package".
const pluralize: (count: number, singular: string, plural?: string) => stringquoteArgument
Shell-quotes an argument.
Specs beginning with @ are quoted unconditionally: PowerShell reads a
leading @ as the array/splat operator, so npmb @babel/core is a parse
error there. A quoted spec is correct in every shell involved, and a
copy-pasteable command that only works in bash is not much use to somebody
on Windows — which, for this tool, is most people.
const quoteArgument: (argument: string) => stringrelativeToCwd
Renders an absolute path the way you would have typed it.
Flag.directory and Flag.file resolve their values before the handler sees
them, which is right for doing the work and wrong for showing it back: an
echoed command reading --out ./temp is what you meant, and it stays correct
when pasted somewhere else in the same tree.
Paths outside the working directory keep their absolute form — a ../../..
chain is longer than what it replaces and silently depends on where you run
it. Separators are normalised to /, which every shell on Windows accepts.
The host arrives as a value rather than being read from process, so this is
testable without chdir'ing the test process — and so the identical function
renders virtual paths in a browser, where there is no process to read.
const relativeToCwd: (target: string, tty: Service) => stringresolveOverwrites
const resolveOverwrites: (resolution: Resolution, options: BundleContext, config: Config, interactive: boolean) => Effect<OverwritePlan, never, Environment>seconds
4200 -> "4s".
Whole seconds, unlike duration, because this is for a counter that is
redrawn ten times a second: rounding to the nearest millisecond would make
every frame differ, and a number that never stops changing is harder to read
than one that ticks once a second.
const seconds: (ms: number) => stringspecsWithLockfileError
Explains why positional specs and a lockfile cannot be one run.
Both ways out are given as commands rather than described, because the useful
thing at this point is the line to run next — the same reason --dry-run
prints the command that reproduces it.
Which way out comes first depends on how the lockfile got here. --lockfile off governs detection, so it un-pins a file that was found and does
nothing at all to one that was named with --file; offering it there would
send you round the same error a second time.
const specsWithLockfileError: (config: Pick<Config, "out" | "specs">, lockfilePath: string | undefined, filePath: string | undefined, tty: Service) => ErrortoJson
The machine-readable summary.
const toJson: (result: BundleResult, options: BundleContext, elapsedMs: number) => stringtoText
The human-readable summary.
const toText: (input: TextInput) => stringtruncateStart
Truncates to width, keeping the end of the string.
Package names are most distinctive at the tail (@babel/plugin-transform-…
tells you nothing; …-modules-commonjs tells you everything), so the head is
what gets dropped.
const truncateStart: (value: string, width: number) => stringConstants
bundleCommand
The root command.
const bundleCommand: Command<"packall", {
readonly file: Option<string>;
readonly prod: boolean;
readonly lockfile: string;
readonly out: string;
readonly layout: Option<"dir" | "flat" | "per-spec" | "single">;
readonly layoutThreshold: Option<number>;
readonly archiveName: Option<string>;
readonly allVersions: boolean;
readonly maxVersions: Option<number>;
readonly includePrerelease: boolean;
readonly optional: boolean;
readonly peer: boolean;
readonly platform: readonly string[];
readonly registry: Option<string>;
readonly npmrc: Option<string>;
readonly rewriteTarballHost: boolean;
readonly timeout: number;
readonly retries: number;
readonly concurrency: number;
readonly dryRun: boolean;
readonly verify: boolean;
readonly force: Option<boolean>;
readonly forceSpec: readonly string[];
readonly printAbsolutePath: boolean;
readonly json: boolean;
readonly quiet: boolean;
readonly interactive: boolean;
readonly yes: boolean;
readonly specs: readonly unknown[];
}, {
}, never, Archiver | Crypto | Npmrc | Transport | Tty | Environment>cli
The runnable CLI.
const cli: Effect<void, CliError, Archiver | Crypto | Npmrc | Transport | Tty | Environment>DEFAULT_LAYOUT_PROMPT_THRESHOLD
Above this many archives, per-spec means importing a lot of separate tarballs, which is worth a second thought before writing them.
const DEFAULT_LAYOUT_PROMPT_THRESHOLD: 20descriptors
const descriptors: {
readonly file: {
readonly name: "file";
readonly alias: "f";
readonly metavar: "PATH";
readonly description: "Read specs from a file: a package.json (bundles its dependencies), a lockfile (bundles the versions it pins), or one spec per line with # and // comments. Detected by content, not extension";
readonly group: "input";
readonly type: {
readonly _tag: "File";
readonly mustExist: true;
};
};
readonly prod: {
readonly name: "prod";
readonly description: "With --file <package.json>, skip devDependencies";
readonly group: "input";
readonly type: {
readonly _tag: "Boolean";
readonly default: false;
};
readonly dependsOn: {
readonly flag: "file";
};
};
readonly lockfile: {
readonly name: "lockfile";
readonly metavar: "MODE";
readonly description: "With --file <package.json>: detect finds a sibling lockfile (default) · off never does · npm, pnpm or bun requires that one · or pass a path";
readonly group: "input";
readonly type: {
readonly _tag: "Text";
readonly default: "detect";
};
};
readonly out: {
readonly name: "out";
readonly alias: "o";
readonly metavar: "DIR";
readonly description: "Directory to write bundles into (default: the current directory)";
readonly group: "output";
readonly type: {
readonly _tag: "Directory";
readonly default: ".";
};
};
readonly layout: {
readonly name: "layout";
readonly description: string;
readonly group: "output";
readonly type: {
readonly _tag: "Choice";
readonly choices: readonly Layout[];
};
};
readonly layoutThreshold: {
readonly name: "layout-threshold";
readonly metavar: "N";
readonly description: string;
readonly group: "output";
readonly type: {
readonly _tag: "Integer";
readonly default: undefined;
};
};
readonly archiveName: {
readonly name: "archive-name";
readonly metavar: "NAME";
readonly description: "Base name for the archive in --layout single";
readonly group: "output";
readonly type: {
readonly _tag: "Text";
readonly default: undefined;
};
readonly dependsOn: {
readonly flag: "layout";
readonly value: "single";
};
};
readonly allVersions: {
readonly name: "all-versions";
readonly description: "Bundle every published version matching a range, not just the best match";
readonly group: "selection";
readonly type: {
readonly _tag: "Boolean";
readonly default: false;
};
};
readonly maxVersions: {
readonly name: "max-versions";
readonly metavar: "N";
readonly description: "With --all-versions, keep at most the N newest matching versions";
readonly group: "selection";
readonly type: {
readonly _tag: "Integer";
readonly default: undefined;
};
readonly dependsOn: {
readonly flag: "all-versions";
readonly value: true;
};
};
readonly includePrerelease: {
readonly name: "include-prerelease";
readonly description: "Let prereleases satisfy ranges";
readonly group: "selection";
readonly type: {
readonly _tag: "Boolean";
readonly default: false;
};
};
readonly optional: {
readonly name: "optional";
readonly description: "Follow optionalDependencies. On by default — this is where per-platform native binaries live";
readonly group: "selection";
readonly type: {
readonly _tag: "Boolean";
readonly default: true;
};
};
readonly peer: {
readonly name: "peer";
readonly description: "Follow non-optional peerDependencies";
readonly group: "selection";
readonly type: {
readonly _tag: "Boolean";
readonly default: true;
};
};
readonly platform: {
readonly name: "platform";
readonly metavar: "LIST";
readonly description: "Restrict optional deps to these platforms. An OS alone takes every arch: win32,linux · linux-x64 · linux-x64-musl (default: all)";
readonly group: "selection";
readonly type: {
readonly _tag: "TextList";
};
};
readonly registry: {
readonly name: "registry";
readonly alias: "r";
readonly metavar: "URL";
readonly description: "Registry to fetch from. Outranks every other source, including npm_config_registry (default: your .npmrc)";
readonly group: "registry";
readonly type: {
readonly _tag: "Text";
readonly default: undefined;
};
};
readonly npmrc: {
readonly name: "npmrc";
readonly metavar: "PATH";
readonly description: "Explicit .npmrc to read registries and credentials from. npm_config_registry in the environment still outranks its registry= line";
readonly group: "registry";
readonly type: {
readonly _tag: "File";
readonly mustExist: true;
};
};
readonly rewriteTarballHost: {
readonly name: "rewrite-tarball-host";
readonly description: "Fetch tarballs from --registry rather than the URL the registry returned";
readonly group: "registry";
readonly type: {
readonly _tag: "Boolean";
readonly default: false;
};
};
readonly timeout: {
readonly name: "timeout";
readonly metavar: "MS";
readonly description: "Ceiling on every network wait: connecting, the preflight check, and each request. Prevents an unreachable registry hanging the run";
readonly group: "registry";
readonly type: {
readonly _tag: "Integer";
readonly default: 10000;
};
};
readonly retries: {
readonly name: "retries";
readonly metavar: "N";
readonly description: "Retries for transient failures (5xx, 429, dropped connections)";
readonly group: "registry";
readonly type: {
readonly _tag: "Integer";
readonly default: 3;
};
};
readonly concurrency: {
readonly name: "concurrency";
readonly alias: "c";
readonly metavar: "N";
readonly description: "Simultaneous registry requests";
readonly group: "registry";
readonly type: {
readonly _tag: "Integer";
readonly default: 10;
};
};
readonly dryRun: {
readonly name: "dry-run";
readonly description: "Resolve and report what would be bundled, downloading nothing";
readonly group: "other";
readonly type: {
readonly _tag: "Boolean";
readonly default: false;
};
};
readonly verify: {
readonly name: "verify";
readonly description: "Check every tarball against the registry's checksum";
readonly group: "other";
readonly type: {
readonly _tag: "Boolean";
readonly default: true;
};
};
readonly force: {
readonly name: "force";
readonly description: "Replace any existing output file. --no-force refuses instead. Omit to be asked per file";
readonly group: "output";
readonly type: {
readonly _tag: "Boolean";
readonly default: undefined;
};
};
readonly forceSpec: {
readonly name: "force-spec";
readonly metavar: "SPEC";
readonly description: "Always replace these specs, whatever --force says about the rest, e.g. --force-spec tsdown esbuild@0.21.5";
readonly group: "output";
readonly type: {
readonly _tag: "TextList";
};
};
readonly printAbsolutePath: {
readonly name: "print-absolute-path";
readonly description: "Report full paths instead of ones relative to the current directory";
readonly group: "output";
readonly type: {
readonly _tag: "Boolean";
readonly default: false;
};
};
readonly json: {
readonly name: "json";
readonly description: "Emit a machine-readable summary on stdout";
readonly group: "output";
readonly type: {
readonly _tag: "Boolean";
readonly default: false;
};
};
readonly quiet: {
readonly name: "quiet";
readonly alias: "q";
readonly description: "Only report warnings and errors";
readonly group: "other";
readonly type: {
readonly _tag: "Boolean";
readonly default: false;
};
};
readonly interactive: {
readonly name: "interactive";
readonly alias: "i";
readonly description: "Choose from what the resolution actually found — platforms, layout — instead of guessing flags up front";
readonly group: "other";
readonly type: {
readonly _tag: "Boolean";
readonly default: false;
};
};
readonly yes: {
readonly name: "yes";
readonly alias: "y";
readonly description: "Never prompt; take the default for any question";
readonly group: "other";
readonly type: {
readonly _tag: "Boolean";
readonly default: false;
};
};
}flagDescriptors
Every flag, in declaration order.
const flagDescriptors: readonly FlagDescriptor[]FlagGroup
Sections of the surface, in the order --help presents them.
type FlagGroup = "input" | "other" | "output" | "registry" | "selection"
const FlagGroup: {
readonly Input: "input";
readonly Output: "output";
readonly Selection: "selection";
readonly Registry: "registry";
readonly Other: "other";
}flags
const flags: {
file: Flag<Option<string>>;
prod: Flag<boolean>;
lockfile: Flag<string>;
out: Flag<string>;
layout: Flag<Option<"dir" | "flat" | "per-spec" | "single">>;
layoutThreshold: Flag<Option<number>>;
archiveName: Flag<Option<string>>;
allVersions: Flag<boolean>;
maxVersions: Flag<Option<number>>;
includePrerelease: Flag<boolean>;
optional: Flag<boolean>;
peer: Flag<boolean>;
platform: Flag<readonly string[]>;
registry: Flag<Option<string>>;
npmrc: Flag<Option<string>>;
rewriteTarballHost: Flag<boolean>;
timeout: Flag<number>;
retries: Flag<number>;
concurrency: Flag<number>;
dryRun: Flag<boolean>;
verify: Flag<boolean>;
force: Flag<Option<boolean>>;
forceSpec: Flag<readonly string[]>;
printAbsolutePath: Flag<boolean>;
json: Flag<boolean>;
quiet: Flag<boolean>;
interactive: Flag<boolean>;
yes: Flag<boolean>;
}ForceMode
What to do about an output file that already exists.
type ForceMode = "all" | "none" | "prompt"
const ForceMode: { readonly Prompt: "prompt"; readonly All: "all"; readonly None: "none"; }OverwriteAnswer
How one "this file already exists" question was answered.
type OverwriteAnswer = "always" | "keep" | "overwrite"
const OverwriteAnswer: {
readonly Overwrite: "overwrite";
readonly Keep: "keep";
readonly Always: "always";
}specsArgument
const specsArgument: Argument<readonly unknown[]>specsDescriptor
const specsDescriptor: {
readonly name: "spec";
readonly metavar: "SPEC";
readonly description: "Packages to bundle: lodash, react@18.2.0, @babel/core@^7, typescript@next";
}VERSION
Kept in step with package.json; recorded in every bundle manifest.
const VERSION: stringTypes
CommandEcho
Everything needed to reconstruct the command that produced this run.
type CommandEcho = {
readonly specs: readonly string[];
readonly file?: string | undefined;
readonly prod?: boolean | undefined;
readonly lockfile?: string | undefined;
readonly skipLockfile?: boolean | undefined;
readonly layout: Layout;
readonly outDir: string;
readonly platforms: string;
readonly optional: boolean;
readonly peer: boolean;
readonly allVersions: boolean;
readonly maxVersions?: number | undefined;
readonly includePrerelease: boolean;
readonly verify: boolean;
readonly force?: ForceMode | undefined;
readonly forceSpecs?: readonly string[] | undefined;
readonly registry?: string | undefined;
readonly archiveName?: string | undefined;
}Config
The shape the handler receives.
Written out rather than inferred so that the helpers below have a stable, readable contract instead of a conditional type nobody can hover.
type Config = {
readonly specs: readonly string[];
readonly file: Option<string>;
readonly prod: boolean;
readonly lockfile: string;
readonly out: string;
readonly layout: Option<Layout>;
readonly layoutThreshold: Option<number>;
readonly archiveName: Option<string>;
readonly allVersions: boolean;
readonly maxVersions: Option<number>;
readonly includePrerelease: boolean;
readonly optional: boolean;
readonly peer: boolean;
readonly platform: readonly string[];
readonly registry: Option<string>;
readonly npmrc: Option<string>;
readonly rewriteTarballHost: boolean;
readonly timeout: number;
readonly retries: number;
readonly concurrency: number;
readonly dryRun: boolean;
readonly verify: boolean;
readonly force: Option<boolean>;
readonly forceSpec: readonly string[];
readonly printAbsolutePath: boolean;
readonly json: boolean;
readonly quiet: boolean;
readonly interactive: boolean;
readonly yes: boolean;
}FlagDependency
When a flag only means something in the presence of another.
type FlagDependency = { readonly flag: string; readonly value?: string | boolean | undefined; }FlagDescriptor
type FlagDescriptor = {
readonly name: string;
readonly alias?: string | undefined;
readonly metavar?: string | undefined;
readonly description: string;
readonly group: FlagGroup;
readonly type: FlagType;
readonly dependsOn?: FlagDependency | undefined;
}FlagType
What a flag accepts, and what it is when absent.
A default of undefined means the flag is genuinely optional — absent is
distinguishable from any value it could carry. That distinction is not
decoration: --force relies on it to tell "replace everything" from "refuse"
from "ask me per file".
type FlagType = {
readonly _tag: "Boolean";
readonly default: boolean | undefined;
} | {
readonly _tag: "Integer";
readonly default: number | undefined;
} | {
readonly _tag: "Text";
readonly default: string | undefined;
} | {
readonly _tag: "TextList";
} | {
readonly _tag: "Choice";
readonly choices: readonly string[];
} | {
readonly _tag: "File";
readonly mustExist: boolean;
} | {
readonly _tag: "Directory";
readonly default: string;
}ForcePolicy
How --force was answered.
type ForcePolicy = { readonly global: ForceMode; readonly specs: readonly string[]; }LayoutDecision
What to do about the layout, once the resolution is known.
type LayoutDecision = {
readonly _tag: "Keep";
} | {
readonly _tag: "Ask";
} | {
readonly _tag: "Switch";
readonly layout: Layout;
}LayoutInput
type LayoutInput = {
readonly explicitLayout: boolean;
readonly yes: boolean;
readonly json: boolean;
readonly dryRun: boolean;
readonly interactive: boolean;
readonly threshold: Option<number>;
readonly perSpecArchives: number;
readonly perSpecEntries: number;
readonly uniquePackages: number;
}LoadedNpmrc
A resolved .npmrc, and where its values came from.
type LoadedNpmrc = {
readonly config: NpmrcConfig;
readonly sources: readonly string[];
readonly tls: TlsSettings;
readonly warnings: readonly string[];
}LockfileCandidate
A lockfile found for a package.json.
type LockfileCandidate = {
readonly path: string;
readonly format: LockfileFormat;
readonly importer: string;
}LockfilePolicy
What --lockfile was asked for.
type LockfilePolicy = {
readonly _tag: "Off";
} | {
readonly _tag: "Detect";
} | {
readonly _tag: "Manager";
readonly format: LockfileFormat;
} | {
readonly _tag: "Path";
readonly path: string;
}OverwritePlan
What a run should do about output files that already exist.
type OverwritePlan = {
readonly force: boolean;
readonly skipExisting: ReadonlySet<string>;
readonly overwrite: ReadonlySet<string>;
}PinnedBy
The lockfile a run was pinned to.
type PinnedBy = {
readonly path: string;
readonly format: string;
readonly lockfileVersion: string;
readonly importer?: string | undefined;
}PinnedLockfile
A settled pin: the graph, where it came from, and the roots it names.
type PinnedLockfile = {
readonly lockfile: LockedTree;
readonly path: string;
readonly specs: readonly PackageSpec[];
}ResolvedInput
What the run will bundle, once --file and --lockfile have been settled.
type ResolvedInput = {
readonly specs: readonly PackageSpec[];
readonly lockfile?: LockedTree | undefined;
readonly lockfilePath?: string | undefined;
readonly declared?: readonly string[] | undefined;
readonly detectionRan?: boolean | undefined;
}TextInput
type TextInput = {
readonly result: BundleResult;
readonly options: BundleContext;
readonly tty: Service;
readonly elapsedMs: number;
readonly warnings: readonly string[];
readonly quiet: boolean;
readonly absolutePaths?: boolean | undefined;
readonly echo?: CommandEcho | undefined;
readonly pinnedBy?: PinnedBy | undefined;
}