import * as Context from "effect/Context"; import type * as Path from "effect/Path"; export declare namespace Tty { /** The host the CLI is attached to. */ export type Service = { /** * Whether to draw in place. * * False when output is piped, redirected or captured by CI — the renderer * then emits one line per milestone rather than repainting. */ readonly isInteractive: boolean; /** * Whether a question can be asked. * * Separate from `isInteractive`, which is about the *output* stream. On a * workstation the two coincide and one flag reasonably stood for both; * they are different properties of different streams, and two hosts have * now shown it: * * - `packall … < /dev/null` on a real terminal has a repaintable stderr * and no stdin. Asking there hangs or fails on end-of-input. * - A browser terminal repaints beautifully and cannot yet be read from, * because the page's line editor owns the keystrokes. * * The layout prompt is the one that made this matter: it is deliberately * *not* gated behind `--interactive`, so a large `--file` run reaches it * on any host that claims to be interactive. */ readonly canPrompt: boolean; /** * What this host calls itself — `process.platform` on Node, `browser` in a * page. * * Nothing *above* this service reads it: the CLI has no branch on the * platform, and the one place the engine used to take one has moved behind * `@packall/core/node`. It is here because something *below* it reads the * same fact from a global only one of the two hosts has. See * `announcePlatform`, which is the whole of why the field exists. */ readonly platform: string; /** Width available for the live line. */ readonly columns: number; /** Writes to the diagnostic stream. Everything human-readable goes here. */ readonly write: (text: string) => void; /** Writes to the data stream, which `--json` keeps clean. */ readonly writeOut: (text: string) => void; /** Reports a failed run. */ readonly exit: (code: number) => void; /** Where the command was run, for rendering paths as they were typed. */ readonly cwd: string; /** * Path semantics for this host. Windows-aware on Node, posix in a browser. * * Here rather than taken from `Path` separately so the formatting helpers * in `summary.ts` can stay ordinary functions. They are called from inside * string-building loops, and making them effectful to fetch a path * implementation would put a `yield*` in the middle of a template literal. */ readonly path: Path.Path; }; } /** Service tag for the host. */ export class Tty extends Context.Service()("@packall/cli/Tty") {} /** * 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. */ export const announcePlatform = (platform: string): void => { const host: { process?: { platform?: string | undefined } | undefined } = globalThis; if (host.process === undefined) host.process = { platform }; else host.process.platform ??= platform; };