import { message, type VerificationState } from './manifest.ts'; import { type Attester, type StrongRef } from './attestation.ts'; import { type Policy, type Signer } from './policy.ts'; import { type Accepted, type Inherited, type Minimum, type Review, type ReviewItem } from './review.ts'; import type { NetworkOptions } from './atproto.ts'; /** * Where this package's own release lives, and who signs it — what `bundle * install` with no URL fetches. * * It is a constant rather than something read out of `package.json`, because it * is a *trust* statement: the identity below is accepted for this package's own * install without asking, and a value that could be edited by whatever is being * installed would not be worth having. It is trusted rather than required, so a * release signed some other way is a question rather than a refusal. * `BUNDLE_SELF_SOURCE` overrides the URL for a mirror; the trust goes with it. * * `listing` is this package's own listing, which `bundle install` asks first * and an install follows from then on, as it would any listed app; it is said * with the DID rather than the handle, since handles change hands. The listing * only says *where*: the trust stays with the URL, so if it ever named another * one, the release there would be a question rather than a trusted install. * `home` is the project's page. */ export declare const SELF: { readonly url: 'https://github.com/pipobscure/bundles/releases/latest/download/bundle.nzip'; /** The listing it publishes itself under, `@pipobscure.com/bundle`, by its DID's address: a handle can change hands. */ readonly listing: 'at://did:plc:rwypz6edxmzyhob2k3rsl2fw/com.pipobscure.bundle.listing/bundle'; /** Its page, where a like of it sends people: an `nzip:` link would need bundle already. */ readonly home: 'https://bundle.pipobscure.com/'; readonly identity: 'https://github.com/pipobscure/bundles/.github/workflows/publish.yml@refs/heads/main'; readonly issuer: 'https://token.actions.githubusercontent.com'; }; /** * The self-install target, with the environment's overrides applied. A mirror * (`BUNDLE_SELF_SOURCE`) has no listing; `BUNDLE_SELF_LISTING` names one * anyway — another listing to ask, which the tests point at a fake account. */ export declare function self(): { url: string; identity: string; issuer: string; listing?: string | undefined; }; /** * Whether an install is this package's own: an app installed from its release * URL, or following its listing — whatever it ended up called. */ export declare function isSelf(record: InstallRecord): boolean; /** What an installed archive is, and where it came from. */ export interface InstallRecord { /** * The key in the record: the file name an app was installed as, or for a * plugin `:` (`bled:@alice/bled-gpio`). */ name: string; /** For a plugin: the scope it was installed into — the package name of the app it is for. */ scope?: string | undefined; /** The package name inside the archive, when it has one. */ package?: string | undefined; /** * For a plugin: what `bundle install --for` was given, as typed. It is * what `list()` tells an app it is called, and only identifies; two * plugins can share one. */ label?: string | undefined; /** Where in `dir` the file is, when that is not `name` — a plugin's `@alice/bled-gpio.nzip`. */ file?: string | undefined; /** * For a plugin linked for development: the folder it is loaded from, as * it is. Nothing is fetched, reviewed or verified, and anything that * verifies plugins refuses it. */ link?: string | undefined; /** * How it was installed, which is what an update asks again: a URL; a * domain, whose `nzip:` TXT record names the URL; or the `at://` address of * a listing, which does. Absent in older records — see `sourceOf()`. */ source?: string | undefined; /** Where it was last fetched from — what the source named then. */ url: string; /** Older records: the domain whose `nzip:` record named the URL. Read as the source. */ alias?: string | undefined; /** The server's ETag, for the conditional request an update makes. */ etag?: string | undefined; /** `Last-Modified`, used when there is no ETag. */ lastModified?: string | undefined; /** The sigstore identity that signed the installed version. */ identity?: string | undefined; /** The OIDC issuer that vouched for that identity. */ issuer?: string | undefined; /** The certificate subject, for an archive signed against an ordinary CA. */ subject?: string | undefined; /** The whole-file hash, `:` — what attestations of it name. */ hash?: string | undefined; /** Who had attested the installed version (good verdicts), as `[kind@]did`. */ attestedBy?: string[] | undefined; /** * Every attestation of the installed version seen so far, good and bad — * what `validate()` compares against to say what is new. */ seen?: Seen[] | undefined; /** When `validate()` last checked it, ISO 8601. */ validatedAt?: string | undefined; /** * Who has been accepted for this install, across its versions: their * evidence on a later version proceeds without asking. */ accepted?: Accepted | undefined; /** Older records: attesters an install required. Read as accepted. */ attesters?: string[] | undefined; /** sha256 of the file as installed. */ sha256: string; /** When it was installed or last updated, ISO 8601. */ at: string; /** * Where it was installed to, so an update can find it again: the directory * on the PATH. For an app kept in the store (see `stored`), the directory * its command links are in. */ dir: string; /** * For an app on macOS and Linux: where the archive itself is kept — in * `appsDir()`, as `.nzip` — with `dir` holding only a symlink to it, * which is the command. Absent on Windows, where the archive is the * command, and in records from before the store; those move into it on * their next update. */ stored?: string | undefined; /** * For an app that asks for one (`bundle.app` in its package.json): what * its entry in the desktop's applications made — see app-entry.ts — * which an update replaces and uninstalling removes. */ menu?: string[] | undefined; /** Installed with `--no-menu`: its updates add no entry either. */ menuDeclined?: boolean | undefined; /** * For a part of an app (see parts.ts): the app's install, which pins it. * It is trusted because that app is, and is installed, updated and * removed with it; `hash` is the pin it was held to. */ partOf?: string | undefined; } /** One attestation, as an install remembers having seen it. */ export interface Seen { did: string; handle?: string | undefined; kind?: string | undefined; verdict: 'good' | 'bad'; /** What the attester said about it, and the post they linked: for showing what is new. */ note?: string | undefined; post?: StrongRef | undefined; } export interface InstallOptions { /** Where to install (default: `installDir()`). */ dir?: string | undefined; /** Extra trusted roots, as PEM text or paths to PEM files. */ roots?: string[] | undefined; /** Require this sigstore signing identity. */ identity?: string | undefined; /** Require this sigstore OIDC issuer. */ issuer?: string | undefined; /** Require attestations from these attesters (DIDs, already resolved). */ attesters?: Attester[] | undefined; /** How many of `attesters` must have attested (default: all of them). */ quorum?: number | undefined; /** Refuse an archive any of these has marked bad. */ block?: Attester[] | undefined; /** Signers accepted without asking, on top of the policy. */ trust?: Signer[] | undefined; /** * Accepted already, by the person, as if for this install before: what * they accepted once for a whole suite. Remembered with the install. */ accepted?: Accepted | undefined; /** * Floors that must hold, and vouch for nothing: a suite's, for a member * being installed. On an update, the suites it belongs to say. */ minimum?: Minimum[] | undefined; /** * Hashes whose good attestations count as the archive's own: the suite a * member is being installed from. On an update, the suites it is in say. */ inherit?: Inherited[] | undefined; /** The policy to apply (default: the machine's, for the installed name). */ policy?: Policy | undefined; /** Ask the backlink index who has attested it (default: what the policy says). */ discover?: boolean | undefined; /** How attestations are fetched — for tests. */ network?: NetworkOptions | undefined; /** Shown everything that was found, before anything is decided. */ onReview?: ((review: Review, about: { name: string; url: string; }) => void) | undefined; /** * Asked when nothing known vouches for the archive: return the items to * accept, or none to decline. Without it, such an install stops with * `ERR_BUNDLE_UNCONFIRMED` instead. */ decide?: ((review: Review, about: { name: string; url: string; }) => Promise) | undefined; /** Install under this name instead of the one the server suggests. */ name?: string | undefined; /** * Install as a plugin for this scope — the package name of the app it is * for — into that scope's directory, rather than onto the PATH. A plugin's * listing needs none: it goes in the scope of the app installed here from * the listing it names, and a scope given must be that one. */ scope?: string | undefined; /** * `scope` came from a page — an `nzip:` link's `for=` — rather than from * the person: it may only say where a plugin's listing goes, never make a * plugin of anything else. */ linked?: boolean | undefined; /** * Set the desktop up for `.nzip` files, for this user — see * `ensureDesktopSetup()`: on Windows the association, PATHEXT and the * icon; on macOS and Linux the file type and its icon. Default: true, or * false when `BUNDLE_NO_DESKTOP_SETUP` (or the older * `BUNDLE_NO_WINDOWS_SETUP`) is set. Turn it off when something else owns * the file type — an installer, or a test suite, which has no business * rewriting the machine it runs on. */ associate?: boolean | undefined; /** * Give an app that asks for one an entry in the desktop's applications — * the menu, Launchpad, Start (see app-entry.ts). Default: what the install * being updated said, else true — or false when `BUNDLE_NO_DESKTOP_SETUP` * is set. False declines it for this install's updates too. */ menu?: boolean | undefined; log?: ((line: string) => void) | undefined; /** How a domain's TXT records are looked up (default: `node:dns`). For tests. */ resolveTxt?: ((domain: string) => Promise) | undefined; /** * The listing the target names, already fetched from its PDS and * verified — a suite's member, which suite.ts has read to learn where it * goes — so it is not fetched again. */ located?: Located | undefined; } /** A listing as fetched from its publisher's PDS and verified. */ export type Located = Awaited>; export interface UpdateResult { record: InstallRecord; /** * What happened: nothing new; a new archive in place; a new archive the * person declined, or that needed a decision nobody was there to make; one * that was refused outright; or a fetch that failed. For a part of an app, * also: `added` — the app has it now — and `removed` — the app no longer * has it, and it is gone. */ state: 'unchanged' | 'updated' | 'added' | 'removed' | 'declined' | 'unconfirmed' | 'refused' | 'failed'; /** The sha256 of the file that was replaced, when something was. */ previous?: string | undefined; /** The whole-file hash that was replaced, `:`, when something was. */ previousHash?: string | undefined; /** The whole-file hash installed now. */ hash?: string | undefined; /** For a part: the app it is a part of — whose update this result came with. */ partOf?: string | undefined; /** Why, for anything but `unchanged` and `updated`. */ reason?: string | undefined; review?: Review | undefined; } /** * Where installed archives go: a directory this tool owns, which the user is * expected to have on their PATH. `BUNDLE_INSTALL_DIR` overrides it. * * Deliberately not "the first writable directory on PATH": guessing at * `/usr/local/bin` or at whatever a shell happens to list first is how install * scripts end up writing somewhere nobody expected. */ export declare function installDir(): string; /** * Where apps are kept, on macOS and Linux: `BUNDLE_APPS`, else `bundle/apps` * in the data directory, beside the plugins. What sits on the PATH is a * symlink into here, so the archive keeps its `.nzip` — and with it the file * type and its icon — while the command is called by its bare name; an update * replaces the archive here, and every link to it runs the new one; and the * link can be renamed without the archive noticing. * * Windows keeps the archive on the PATH itself, as `.nzip`: there the * extension is what makes it runnable, and a symlink needs a privilege * ordinary users do not have. */ export declare function appsDir(): string; /** Whether apps are kept in `appsDir()` and linked onto the PATH: everywhere but Windows. */ export declare function storesApps(): boolean; /** Where in `appsDir()` the app installed as `name` is kept. */ export declare function storedPath(name: string): string; /** Where the record of what is installed lives. */ export declare function recordPath(): string; /** Everything installed, by name. */ export declare function records(): Record; /** * An archive already here, to install without fetching it: for an install * with no network, an image build, or a test. `file` is read, or `bytes` are * taken as they are. `source` is the URL it stands for, if any: an update * fetches that, and the parts the archive declares are resolved against it — * else against the file, so parts sitting beside it are found, and an update * reads the file again. */ export interface LocalArchive { file?: string | undefined; bytes?: Buffer | undefined; /** What to install it as (an app's command name); `options.name` does the same. Default: the file's name. */ name?: string | undefined; source?: string | undefined; } /** * Fetch `target`, verify what comes back, and put it on the PATH under the name * the server suggests — `Content-Disposition`, or the last segment of the URL. * * `target` is a URL; a bare domain whose `nzip:` TXT record names one — see * `resolveAlias()`; a listing, `@/` — see listing.ts; a * local archive (`./app.nzip`), or one given as a `LocalArchive`. The alias's * or the listing's name is used then, rather than the server's. Which of these * it was is remembered, and an update asks it again, so a publisher who moves * their releases moves every install with them. * * An app that declares parts (`bundle.parts`, see parts.ts) brings them: each * is fetched from beside it, held to the hash the app names, and installed as * a plugin in the app's scope — all of them, or none, and the app with them. * * Nothing is written outside a temporary file until the signature checks out, * and the temporary file is removed if it does not. */ export declare function install(target: string | LocalArchive, options?: InstallOptions): Promise; /** * What a plugin was installed as: what `bundle install --for` was given, as * typed, so whoever reads `list()` can tell which install it was. Older * records, from before it was kept, are known by their source. Apps import * by package name; this only identifies, and two plugins may share one. */ export declare function labelOf(record: InstallRecord): string | undefined; /** Where an install's archive is: in the store, or on the PATH as it always was. */ export declare function pathOf(record: InstallRecord): string; /** * The commands that run an app: for one in the store, every symlink in its * directory that points at its archive, by whatever name it has been given * since; otherwise the file on the PATH, when it is there. A plugin is no * command, and has none. */ export declare function commandsOf(record: InstallRecord): string[]; /** * The policy an install answers to: an app's, with its `apps` section; a * plugin's, with its scope's section and never the app's — the app's rules * are about the app's author. A part of an app answers to the app's: the app * is what vouches for it, and the scope's section is about plugins someone * else wrote. */ export declare function policyOf(record: Pick): Policy; /** The package name an archive's package.json gives, if it has one. */ export declare function packageName(bytes: Buffer): string | undefined; /** * Re-check what an installed archive came from, and replace it if the publisher * has published something new. With no name, every install; with several, those. * * A new version goes through the same review as an install, against what has * been accepted for this one so far. One install that is refused, declined or * waiting on a decision does not stop the rest; each has its own result, and * the installed copy stays as it was. * * An app's parts come with it: after its own result, one for each part — * `unchanged`, `updated` (with the hash before and after), `added` or * `removed` — so a caller can tell what to restart. They are put in place * with the app, all of them or none. Naming a part updates its app. */ export declare function update(name: string | string[] | undefined, options?: InstallOptions): Promise; /** * What a domain publishes as its installable app: a TXT record of the form * * nzip: * * where `` is an absolute `https:` URL, or a reference resolved against * `https:///`. The command it installs as is the domain's first label. * So `npm.npmjs.org` carrying `nzip:/app/npm.nzip` means * `bundle install npm.npmjs.org` fetches `https://npm.npmjs.org/app/npm.nzip` * and installs it as `npm`. * * The record only says *where* to fetch from, and DNS is not authenticated, so * it is worth exactly that: the archive is verified like any other, and the * identity that signed it is pinned like any other. What an alias does not do * is tell you who should have signed it — `--identity` is still how you say so. * * Only `https:` is accepted: a record that points at plain HTTP would let * anyone on the path choose the bytes, and the signature check would then be * the only thing between them and the first install's trust decision. */ export declare function resolveAlias(domain: string, resolveTxt?: (domain: string) => Promise): Promise<{ domain: string; name: string; url: string; }>; /** How an install was made — what an update asks again. Older records said less. */ export declare function sourceOf(record: InstallRecord): string; /** What is on disk, measured against what the record says should be. */ export interface InstalledCheck { record: InstallRecord; /** Where the file is, or would be. */ path: string; /** * `ok` — present, the recorded bytes, and still vouched for by someone * accepted for it. `missing` — the file is gone. `changed` — something * other than `update` replaced it. Otherwise the state that was reached: * `invalid` (including a blocking attester's bad verdict), * `valid-untrusted` (nobody accepted vouches for it any more, or the * policy no longer holds), `unsigned`. */ state: 'ok' | 'missing' | 'changed' | VerificationState; /** The whole-file hash as it is now, when there is a file to hash. */ sha256?: string | undefined; reason: string; /** The review it was judged by, when it got that far. */ review?: Review | undefined; } /** * Check every install against its record: the file is there, its bytes are the * ones that were installed, and it still verifies as whoever signed it. * * The hash is the cheap half and the interesting one. `update` is the only * thing that should ever replace an installed archive, so a file whose hash has * moved without the record moving with it was changed by something else — which * a signature check alone would not notice, since the replacement may be * perfectly well signed. */ export declare function installed({ roots }?: { roots?: string[] | undefined; }): InstalledCheck[]; /** * Re-fetch the attestations every install depends on, so that `installed()` * judges them as they are now rather than as the cache last saw them — which * is how a withdrawn attestation, or a new warning, shows up. Returns the * problems met, if any; the cache keeps what it had for those. */ export declare function refreshInstalled(network?: NetworkOptions): Promise; /** What `validate()` found for one install. */ export interface Validation { record: InstallRecord; check: InstalledCheck; /** Attestations not seen before. */ added: Seen[]; /** Attestations seen before that are gone: withdrawn, or no longer counted. */ removed: Seen[]; /** Fetches that failed; the cache answered instead. */ problems: string[]; /** True when it was validated recently enough to be left alone (`every`). */ skipped: boolean; } /** * Re-validate installs — `which` names them as `uninstall` does, and none * means all — against everything known about them now: the attestations are * fetched afresh, discovery included, and each install is re-checked exactly * as `installed()` does. What sets this apart is the comparison: each install * remembers which attestations it has seen, so the answer says what is *new* * — a scanner's warning published since the install, an auditor vouching * late — and what has been withdrawn, and then remembers the new picture. * * Made to run unattended, at login or on a timer: `every` leaves alone any * install validated more recently than that, so a shell that starts often * does not ask the network each time. */ export declare function validate(which?: string[], { roots, network, every }?: { roots?: string[] | undefined; network?: NetworkOptions | undefined; /** Milliseconds: skip installs validated more recently than this. */ every?: number | undefined; }): Promise; /** The bad verdicts in a review that did not decide anything, as lines to show. */ export declare function warningsOf(review: Review): string[]; /** What has been accepted for an install, reading older records' fields as acceptance. */ export declare function acceptedOf(record: InstallRecord): Accepted; /** What `uninstall` removed: the install, and its plugins when nothing else can load them. */ export type Uninstalled = InstallRecord & { /** The app's plugins, removed with it: its parts, always; the rest when no other install loads them. */ plugins: InstallRecord[]; /** Other installs of the same app, for whom its plugins were left where they are. */ sharedWith: string[]; }; /** * Forget an install, and remove the file it put on the PATH. * * `which` is a name, a URL, the domain it was installed by, or nothing — and * nothing means this package's own install, which is what somebody typing * `bundle uninstall` means. The desktop setup — the association on * Windows, the file type and its icon everywhere — is left alone: other * archives may rely on it, and it is not this one's to take away. The app's * own entry in the desktop's applications, if it has one, goes with it. * * An app's plugins go with it: nothing can load them any more. The exception * is another install of the same app — the same package, under another name — * which loads the same scope, so they stay for that; its parts go whatever * else loads the scope, and a part is never uninstalled by itself. A scope no app is * installed as, such as a suite's shared one, belongs to no single app and is * only ever emptied plugin by plugin. */ export declare function uninstall(which?: string): Uninstalled; /** The name this package installs itself under, which the extension decides. */ export declare function selfName(): string; /** One file to put in place, and how. */ export interface Placement { target: string; bytes: Buffer; mode: number; } /** * Move everything into place as one step: every file written beside its * target first, then each renamed into place, and only then the old ones and * those to go removed. A failure while writing changes nothing; one while * renaming puts back what had been renamed. So no run ends with some of the * new files and some of the old — short of the machine stopping between two * renames, which a set of files spread over several directories cannot be * guarded against. * * How a file that is there is replaced depends on the platform. Elsewhere the * new one is renamed over it, which replaces it in one step, and the old one * is kept as a second name for the same file, to put back. Windows will not * rename over a file a process has open — a running app's own archive, a part * a service has loaded — but will move one aside, and delete it, while it is * open; whoever has it open goes on reading it. So there (`aside`) the old * file is renamed out of the way first, for the moment it takes to rename the * new one in, and a running program keeps the version it started with until * it is restarted. An old file that cannot be deleted yet is left, and the * next commit to the same place takes it away. * * Exported because this is the part of updating a running app on Windows that * can be tested anywhere: `aside` chooses the strategy, and `rename` can fail * on purpose. */ export declare function commit(placements: Placement[], removals?: string[], { aside, rename }?: { aside?: boolean | undefined; rename?: ((from: string, to: string) => void) | undefined; }): void; /** The parts an installed app has, by record. */ export declare function partsOf(name: string): InstallRecord[]; /** * What to call the file: the name the server asked for, or the last segment of * the URL. Either way it is reduced to a bare file name — a `Content-Disposition` * is a suggestion from someone else's server, and a suggestion that can contain * `../` is an arbitrary write. * * Then the extension is decided rather than accepted, because `.nzip` is not * decoration: on Windows it is the whole mechanism — the association is by * extension — so it is put on. Everywhere else it is noise between a person and * the command they mean to type, so it comes off, and `bundle.nzip` installs as * `bundle`. Any other extension is left alone; it is the publisher's business. */ export declare function fileName(response: Response, url: string): string; /** `chosen` as it should sit on the PATH: with `.nzip` on Windows, without it elsewhere. */ export declare function commandName(chosen: string): string; /** * Whether installs set the desktop up for `.nzip` files: yes, unless they go * under a `BUNDLE_ROOT`, or `BUNDLE_NO_DESKTOP_SETUP` is set — or `BUNDLE_NO_WINDOWS_SETUP`, its name * from when the only setup there was happened on Windows, honoured still so * that nothing which set it starts rewriting a desktop now. */ export declare function desktopSetupWanted(): boolean; /** How the desktop setup runs a program — injected by tests, which have none of them. */ export type Runner = (command: string, args: string[]) => { status: number | null; error?: Error | undefined; stdout?: string | undefined; stderr?: string | undefined; }; /** * Tell this user's desktop about `.nzip` files, as far as each platform has a * way to: on Windows, how to run one and what it looks like * (`ensureWindowsAssociation()`); on macOS (`ensureMacFileType()`) and the * freedesktop unixes (`ensureFreedesktopFileType()`), what kind of file it is * and its icon — the tree in `icons/`. A `#!` prefix is what runs an archive * there, so nothing else is needed for that. * * The same rules for every platform: per-user, never anything that needs an * administrator; idempotent, so what is already right is left alone and the * notes returned say only what changed; and a failure is a note, never a * failed install — the archive is on the PATH either way, and an icon is not * worth refusing it over. `bundle uninstall` leaves all of it where it is. */ export declare function ensureDesktopSetup(name?: string): string[]; /** The MIME type `.nzip` files are registered as on the freedesktop desktops. */ export declare const NZIP_MIME = "application/x-nzip"; /** * The shared-mime-info package that declares `.nzip`: a name, the glob that * recognises one, and its icon — `application-x-nzip`, the name the icon * theme spec derives from the type, put beside it in the user's hicolor theme. * The generic icon is what a theme falls back to before that, and the * subclass says what it is underneath: a zip, which archive tools can open — * the `#!` prefix in front is the kind of leading data zip readers expect. */ export declare function mimePackageXml(): string; /** * Register `.nzip` with a freedesktop desktop — GNOME, KDE, and the rest that * read shared-mime-info and the icon theme spec — for the current user, under * `$XDG_DATA_HOME` (`~/.local/share` by default): * * mime/packages/pipobscure-bundle-nzip.xml the type (`mimePackageXml()`) * icons/hicolor/scalable/mimetypes/application-x-nzip.svg its icon * * and then `update-mime-database`, which is what turns the packages into the * cache file managers actually read. Both files are compared before they are * written, and nothing is run when neither changed. * * The icon cache is only refreshed when there already is one. GTK trusts an * `icon-theme.cache` for as long as the theme directory itself is no newer, * and a file added in a subdirectory does not make it newer — so creating a * cache in a directory other programs also drop icons into would hide theirs. * Without one, the icon is found by looking, which is what this needs. */ export declare function ensureFreedesktopFileType({ dataHome, runner }?: { dataHome?: string | undefined; runner?: Runner | undefined; }): string[]; /** The uniform type identifier `.nzip` is declared as on macOS. */ export declare const NZIP_UTI = "com.pipobscure.bundle.nzip"; /** * The `Info.plist` of the app that declares `.nzip` on macOS: an exported * type, `com.pipobscure.bundle.nzip`, for the extension, with the icon, and a * document type naming it with the role None — so Finder shows the icon and a * name, and never offers this app to open one with. * * It conforms to `public.archive` rather than `public.zip-archive`, though a * zip is what it is: a zip archive is something Archive Utility opens on a * double-click, by unpacking it, and that is not what anybody double-clicking * an app means. `CFBundleVersion` moves only when this declaration does. */ export declare function macInfoPlist(): string; /** Where the app that declares `.nzip` goes on macOS. */ export declare function macAppDir(home?: string): string; /** * Give `.nzip` files a name and the tree icon in Finder, for the current user. * * macOS has no file for that the way Linux does: what a file type is and what * it looks like are declared by an application, in its `Info.plist`, and * known once Launch Services has seen the application. So this writes the * smallest application that can — `NodeBundle.app`, beside this tool's other * state in `~/Library/Application Support/bundle`, out of the way of * Launchpad and the Applications folder — and has `lsregister` look at it. * It is never run: its executable only says so and exits. * * As everywhere, files are compared before they are written, and * `lsregister` is only asked when one of them changed. */ export declare function ensureMacFileType({ home, runner }?: { home?: string | undefined; runner?: Runner | undefined; }): string[]; /** Where the applet that receives `nzip:` links goes, on macOS. */ export declare function macLinksApp(home?: string): string; /** * Make `nzip:` links open `archive` — the installed bundle — for this user * (see links.ts for what each platform registers, and why each passes the * link on as it does). Idempotent like the rest of the desktop setup: what is * right already is left, and a step that fails is a note. */ export declare function ensureLinkHandler(archive: string, { runner, home, dataHome, force }?: { runner?: Runner | undefined; home?: string | undefined; dataHome?: string | undefined; /** Do every step again, whatever looks right already: `bundle links` repairing. */ force?: boolean | undefined; }): string[]; /** * What keeps `nzip:` links from opening `archive` here, if anything: what * `bundle links` checks before it repairs. Empty when they open it. On macOS * the applet is checked, not which app Launch Services prefers, which has no * command to ask; registering again is how that is put right. */ export declare function linkHandlerProblems(archive: string, { runner, home, dataHome }?: { runner?: Runner | undefined; home?: string | undefined; dataHome?: string | undefined; }): string[]; /** Take away what `ensureLinkHandler()` registered: nothing is left to open the links. */ export declare function removeLinkHandler({ runner, home, dataHome }?: { runner?: Runner | undefined; home?: string | undefined; dataHome?: string | undefined; }): void; /** * Make `.nzip` runnable for the current user on Windows: associate it with a * file type that runs node with the archive mounted, put the extension on * PATHEXT so the name alone is enough, and give the file type the tree icon * (`windowsIcon()`). * * Which node is the one `windowsNode()` finds, as a `#!` launcher would on * unix: the one on PATH when it is new enough, else the one running this, * else the newest a version manager has. So installing this package from npm * under an older node (`npx @pipobscure/bundle install`) still leaves `.nzip` * opening with one that runs it. With none to be found, the association is * left as it is, and the notes say why. * * Everything here is per-user — HKCU and `HKCU\Environment`, never `/M` — so it * needs no administrator. A machine-wide association is an installer's job, and * the node installer is the right place for it. All of it is idempotent: * what is already right is left alone, and what is missing is written. * * The icon does not depend on a node being found, so it is set either way; * and when it or the association changed, Explorer is told (`refreshExplorer()`), * or it would go on showing what it had cached. */ export declare function ensureWindowsAssociation(name?: string): string[]; /** * The one value `reg query` was asked for, out of what it printed. * * Its output is ` `, with the default value named * `(Default)`. A value can itself contain runs of spaces — a command line with * quoted paths usually does — so only the name and the type are matched, and * everything after the type is the value. * * Exported because this is the part of the Windows path that can be tested * anywhere; the `reg` call around it cannot. */ export declare function parseRegQuery(stdout: string, name: string): string | null; export { message }; //# sourceMappingURL=install.d.ts.map