HarperFast / HarperFast/harper
Docker image dependency tree is resolved fresh at build time, not from the published shrinkwrap
- Dominant language
- JavaScript
- Stars
- 89
- Forks
- 10
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 200
Description
## SummaryThe Dockerfile installs harper from a locally built tarball:```
npm install --ignore-scripts --global harper-*.tgz
```npm decides whether to honour a package's bundled `npm-shrinkwrap.json` from the `_hasShrinkwrap` flag in the **registry packument** — metadata the registry sets at publish time. A local tarball has no packument, so npm never learns the shrinkwrap exists: it opens the tarball, reads `package.json`, and resolves the whole tree fresh.## EvidenceThe *same* published `harper@5.1.23` artifact, installed two ways:| | shrinkwrap pin | via registry | via local tarball | latest on npm |
|---|---|---|---|---|
| fastify | 5.8.5 | 5.8.5 | **5.10.0** | 5.10.0 |
| chokidar | 4.0.3 | 4.0.3 | 4.0.3 | 5.0.0 |
| graphql | 16.14.2 | 16.14.2 | 16.14.2 | 17.0.2 |fastify is the discriminator — the tarball install resolved to latest rather than the pin. (chokidar and graphql match only coincidentally: harper's `^4.0.3` / `^16.10.0` ranges cap below those new majors.) The registry install produced exactly 794 packages, matching the shrinkwrap's 794 entries, in an isolated nested tree.## Why it matters1. **The image is not reproducible.** Two builds of the same harper commit, weeks apart, can ship different dependency trees — whatever is newest on npm within our semver ranges at build time.
2. **The image does not match what npm consumers get.** Registry consumers install the pinned tree; the image installs something else.
3. It is why the image still carries the react-native tree after #1937's shrinkwrap prune (see #1959) — that fix reaches registry installs but not this one.## DirectionMake the image install honour the shrinkwrap. Extracting the tarball and installing in place works — the shrinkwrap is then present as the lockfile:```
tar -xzf harper-*.tgz --strip-components=1 -C && cd && npm install --omit=dev --ignore-scripts
```Measured locally (macOS, like-for-like): 444M -> 268M with #1959's prune in place, and the tree becomes pinned. `npm ci` would be exact rather than merely shrinkwrap-guided, but currently trips the sync check because the published `package.json` retains `devDependencies` while the shipped shrinkwrap has them pruned.Open questions for whoever picks this up:
- Extract-and-install vs. keeping `npm install --global` and accepting an unpinned image.
- Whether to make `npm ci` viable (strip `devDependencies` from the packed `package.json`), which would give an exactly-pinned image.
- Bin/symlink placement and `which harper` — the entrypoint depends on it, including the `HARPER_RUNTIME=bun` path.Depends on #1959: honouring an unpruned shrinkwrap would pin the react-native tree *in*, so this should land after it.
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Contributor guide
Assessment
This issue has not been assessed yet.