import * as FS from 'node:fs'; import * as PATH from 'node:path'; import { parseArgs } from 'node:util'; import { createBundle, signBundle, signedName, verifyBundle, runBundle, fileSigner } from './api.ts'; import { members, parseDate } from './archive.ts'; import { packageVersion } from './files.ts'; import * as AUDIT from './audit.ts'; import { message, wholeFileHash, STATES, type VerificationResult, type VerificationState } from './manifest.ts'; import { systemRoot } from './scopes.ts'; import { formatAttester, parseAttester, parseDuration, policyFromEnvironment, cachedDids, postUrl, noteTooLong, type Attester, type StrongRef, } from './attestation.ts'; // Re-exported because this is where a CLI consumer looks for it; it is defined // in the format layer so the `bundle` launcher can report an exit code without // loading the whole CLI. export { STATES }; import * as SKILLS from './skill.ts'; import * as REVIEW from './review.ts'; import { PLAIN, styleFor, bytes, plural, untrusted, type Style, type Mark } from './style.ts'; import type { Validation, Seen } from './install.ts'; // Argument parsing and reporting, and nothing else. Every command below is a // `parseArgs` call, a message or two, and one call into `api.ts` — which is // deliberate: what the CLI can do is exactly what an embedder can do, because // they are the same functions. /** What `bundle --help` prints: the commands, and how to ask about one. */ export const USAGE = `usage: bundle [options] bundle -v, --version commands: create build an archive from a list of files sign sign an archive as it is: app.unsigned.nzip into app.nzip audit report what is about to be reviewed, and gate signing on the verdict verify verify an archive and report its trust state attest vouch for an archive from an atproto account, or withdraw that like like an app: a star for it, and a post saying so likes who likes an app publish list an archive's URL under a name, so others can find and install it unpublish take a listing down again suite write, check and publish a suite: listings installed together run mount a signed archive and run it search search the listed archives by name, description and publisher listings every listed archive, from a local index kept in step with the network install fetch a signed archive — or a suite of them — from a URL, domain or listing parts print the parts an app declares: each archive's package, url and hash update refetch what was installed, and replace it if it changed installed list what is installed, and re-check each against its record validate re-check installs, and say what has been attested since — at startup uninstall remove an installed archive, and forget where it came from node say which node runs bundles here, or install one sea build a node runtime that verifies an archive before running it trust refresh the sigstore trust root and the cached attestations policy show the rules this machine installs by, and where they come from lexicon show, check or publish the atproto lexicons for attestations and listings skill install this package's bundle-auditing skill into a project shell print what to load at shell start: Tab completion, and validate links make nzip: links open bundle, or say whether they do bundle --help what one command does, and its options bundle help the same options: -h, --help show this help; after a command, that command's -v, --version print the version --system the machine's layout, under one root (see 'install')`; /** What `bundle --help` prints: one command, what it does, and its options. */ export const HELP: Record = { create: `create options: usage: create [options] -b, --base base directory the file list is relative to (default: .) -l, --launcher put this package's shell launcher in front, so the result runs by name — the usual way to make a program -p, --prefix put some other prefix in front: a launcher of your own, or a verifying node; omit both for a plain archive -f, --files read the newline-separated file list from here (default: stdin) -o, --output write the archive here (default: stdout) -k, --key leaf private key (PEM); signs at build time with --chain -c, --chain full certificate chain (PEM, leaf first) --hash digest for the whole-file hash and member digests (default: sha256) --sign digest the signature over that hash uses (default: sha256) --date date every member with this one time, as a rebuild should: 2024-05-01T12:00:00Z, or a date (UTC midnight) the prefix decides the archive's shape, and is part of what an audit reviews; signing keeps it. the launcher runs the node on PATH when that meets the app's engines.node (from package.json in --base, never below 26.11.1), else the newest one nvm, fnm, volta, asdf, mise or n has installed or bundle downloaded ('bundle node install'), and otherwise exits 69 saying which node to install. Only the lower bound is checked. every member records its own digest, and the archive records its whole-file hash, so even unsigned it says what its bytes should be. each member is dated with its file's own time (never the time of the build), no later than SOURCE_DATE_EPOCH when that is set; --date gives them all one time instead. So the same files make the same archive — in the same timezone, since ZIP times are local: build with TZ=UTC.`, sign: `sign options: usage: sign [options] -o, --output write the signed archive here; '-' for stdout. By default app.unsigned.nzip is signed into app.nzip, and a name without '.unsigned' is signed in place -x, --executable make the output executable (implied when the archive has a prefix) --hash digest for the whole-file hash and member digests (default: sha256) --sign digest the signature over that hash uses (default: sha256) by default this signs through sigstore, taking the identity from CI when there is one and otherwise opening a GitHub sign-in: --flow auto | ci | browser | device (default: auto) --token use this OIDC token instead of signing in --oidc-issuer OIDC issuer (default: sigstore's dex) --connector identity provider to jump to (default: github) --fulcio certificate authority (default: fulcio.sigstore.dev) --rekor transparency log; empty string to skip it --tsa timestamp authority; empty string to skip it or, to sign against a certificate authority of your own: -k, --key leaf private key (PEM) -c, --chain full certificate chain (PEM, leaf first)`, audit: `audit options: usage: audit [options] -b, --baseline a previously approved archive to review against, so the review is of what changed rather than of everything -v, --verdict where the verdict is (default: .audit.json) --check exit non-zero unless a clean verdict pins these bytes --approve record a clean verdict you reached by reading it yourself -n, --note what you checked, recorded with --approve with none of those it reports what is about to be reviewed and how. The review itself needs judgement, so no command performs it: install the skill with 'bundle skill' and run /audit-bundle, or read the archive yourself and --approve. Signing is not gated unless you run --check before it.`, verify: `verify options: usage: verify [options] -a, --archive archive to verify (or pass it as a positional argument) -r, --root extra trusted root certificate (PEM); repeatable --identity require this sigstore signing identity --issuer require this sigstore OIDC issuer --sigstore-root sigstore trust root (default: the cache 'trust' fills) --attester <[kind@]who> require an attestation from this DID or handle, optionally of this kind (audited@did:web:…); repeatable --quorum how many of the attesters must have attested (default: all) --max-age