# AGENTS.md &mdash; install-artifact-from-github

> A no-dependency micro helper for developers of binary addons for Node. Three single-file bin utilities integrated with GitHub Releases: `save-to-github-cache` uploads a built binary artifact to a GitHub release (from CI); `install-from-cache` downloads it on the user's machine at install time, optionally verifies its integrity against a hash bag in the addon's `package.json`, and falls back to building from sources on any failure; `hash-github-cache` generates that hash bag at release time. Zero dependencies, ESM, Node >= 18.

For detailed usage docs see the [wiki](https://github.com/uhop/install-artifact-from-github/wiki).

## Setup

```bash
git clone --recursive https://github.com/uhop/install-artifact-from-github.git
cd install-artifact-from-github
npm install
```

The wiki is a git submodule in `wiki/`.

## Commands

- **Test:** `npm test` (runs `tape6 --flags FO` against the mock-server harness)
- **Test (sequential):** `npm run test:seq` (`tape6-seq --flags FO`)
- **JavaScript check:** `npm run js-check` (`tsc --project tsconfig.check.json` &mdash; checkJs on the bin sources)
- **Lint:** `npm run lint` (Prettier check)
- **Lint fix:** `npm run lint:fix` (Prettier write)

## Project structure

```
install-artifact-from-github/
├── package.json            # Package config; "type": "module"; exposes the three bins
├── tsconfig.check.json     # Lint config -- checkJs on the bin sources
├── bin/
│   ├── install-from-cache.js     # Consumer-side bin: download -> integrity check -> verify -> fallback rebuild
│   ├── save-to-github-cache.js   # CI-side bin: compress + upload artifact to a GitHub release
│   └── hash-github-cache.js      # Release-side bin: hash release assets into the package.json integrity bag
├── scripts/
│   ├── dump-env.js         # Dev helper: dump npm-provided env vars
│   └── example-save.sh     # Manual upload example (uses a personal token)
├── tests/                  # Automated tests (tape-six)
│   └── helpers/            # mock-server.js (GitHub API + asset host impersonation), run-bin.js
└── wiki/                   # GitHub wiki documentation (git submodule)
```

## How the bins work

- `install-from-cache` runs as the consuming addon's `install` script. It computes `${platform}-${arch}-${abiSlot}`, builds the asset URL from the consumer's `package.json` (`github` or `repository.url`, plus `version`), then tries `.br` &rarr; `.gz` &rarr; uncompressed. If the addon pins an `artifactHashes` bag and the download is from the canonical source, the decompressed bytes' SHA-256 must match before writing (integrity check); on success it also verifies via the consumer's `verify-build` (or `test`) script; on any failure it runs `npm run rebuild` (typically `node-gyp rebuild`). All failures degrade gracefully to the source build.
- `save-to-github-cache` runs in GitHub Actions on a tag build: reads `GITHUB_REPOSITORY` / `GITHUB_REF` / `GITHUB_TOKEN` (or `PERSONAL_TOKEN`), resolves the release upload URL, and uploads the artifact in the formats requested by `--format` (default `br`).
- `hash-github-cache` runs at release time (e.g. `prepublishOnly`): hashes the release's assets (`--from-release`) or a local directory (`--from`) and `--write`s / `--check`s the `{slot -> sha256}` `artifactHashes` bag in the consumer's `package.json`. It shares slot naming + decompression with `install-from-cache`, so a bag it writes always verifies.
- Configuration knobs follow the `--flag` / `--flag-var ENVVAR` / default-env-var triple convention: mirror host (`DOWNLOAD_HOST`), path/version skipping (`DOWNLOAD_SKIP_PATH`, `DOWNLOAD_SKIP_VER`), proxy agent (`DOWNLOAD_AGENT`), N-API level (`DOWNLOAD_NAPI`), forced source build (`DOWNLOAD_FORCE_BUILD` / `--force-build`). The canonical (verified) location is `<host>/<owner>/<repo>/releases/download/`, every part of it parsed from the addon's own `package.json`: `repository.url` names the host when it is a full URL (so an enterprise-hosted addon resolves to its own instance), and `https://github.com` is the fallback for the `owner/repo` / `github:owner/repo` shorthands and for anything unparseable. A `--host` / `DOWNLOAD_HOST` mirror resolving anywhere else is not integrity-checked, while one resolving back onto that prefix still is.
- musl Linux is detected (`linux-musl`) using the detect-libc algorithm.

## Code style

- **ES modules** (`"type": "module"`), Node >= 18.
- **Prettier** for formatting &mdash; run `npm run lint:fix` before committing.
- **No narrating comments** &mdash; comments are short _why_-markers only (a non-trivial decision or constraint, an algorithm reference, or required JSDoc); never a restatement of _what_ the code does.
- Each bin stays a self-contained single file &mdash; they must be trivially auditable (the security story depends on it).

## Key conventions

- **No runtime dependencies, ever.** The bins use only the Node standard library. DevDeps for tooling are fine.
- **Every change must be fail-safe**: any failure in the download path must fall back to `npm run rebuild`. Never make a download/verification failure fatal.
- **Integrity verification is opt-in and source-scoped** (since 1.7.0): it runs only for the canonical GitHub source and only when the addon ships an `artifactHashes` bag. The test is whether the _resolved URL_ falls under the addon's own release location — a template over the `host`/`owner`/`repo` parsed from its `package.json`, never a literal — and not whether an override was supplied: the hashes attest to the bytes the author published _there_, so anywhere else (another host, or another path on the same host) is the deployer's own trust root and must never be integrity-checked, while a `--host` / `DOWNLOAD_HOST` resolving back onto that prefix is the original and keeps its check. `GITHUB_SERVER_URL` must never re-enter the host cascade — in GitHub Actions it names the instance running the workflow, not the host of a dependency's artifacts (issue #27). The addon's own `repository.url` is the authority instead, which is also the test harness's seam: a sandbox pointing it at the mock server makes the mock the canonical source. A failed check stays fail-safe (rebuild), never fatal. The bag is authored in the consumer's `package.json`, never in this package.
- **No build step, no importable API** &mdash; this package ships three bins only; there is no `src/`, no `.d.ts` sidecars, no `exports` map.
- Wiki documentation lives in the `wiki/` submodule &mdash; update it alongside behavior changes; commit in the submodule, then bump the pointer in the parent repo.
- Tests impersonate the GitHub Releases API + asset host with a local HTTP server (`tests/helpers/mock-server.js`) &mdash; no network access in tests.
- **npm 12 (July 2026)** disables dependency lifecycle scripts by default; consumers must allowlist the addon that uses this package (`npm approve-scripts <addon>`). Keep the consumer-side allowlist flow visible in the docs (README + wiki).
