Skip to content

feat: pin shared packs to commit SHAs via rac-lock.json - #41

Merged
raniejade merged 10 commits into
mainfrom
rac-lock-json
May 11, 2026
Merged

raniejade merged 10 commits into
mainfrom
rac-lock-json

Conversation

@raniejade

Copy link
Copy Markdown
Owner

Summary

Introduces .rac/rac-lock.json, a committed lockfile that pins each shared pack to a resolved commit SHA. Today two installs of the same config.toml can diverge because every run re-resolves the floating ref (main, v2, …) via git fetch ... && checkout FETCH_HEAD. With the lockfile, locked SHAs are checked out directly and CI can gate on lockfile drift.

What changed

  • New file .rac/rac-lock.json — committed JSON, sorted by pack id, version: 1. Schema: { id, repo, ref, resolved } per entry.
  • rac install / rac diff — default now uses locked SHAs when an entry exists; otherwise resolves and writes the lockfile. New --frozen-lockfile flag errors (exit 2) if a sync would mutate the lockfile. Existing --refresh-packs is widened to re-resolve and rewrite all entries. --refresh-packs --frozen-lockfile is rejected.
  • rac pack add / rac pack remove — keep the lockfile in sync alongside config.toml.
  • rac pack override — unchanged. Overridden packs are excluded from the lockfile entirely.
  • rac doctor — read-only on the lockfile; adds three new diagnostics: lockfile_malformed (error), missing_lockfile_entry (error, --frozen-lockfile only), stale_lockfile_entry (warn).
  • GitRunner type — now returns Promise<{ stdout: string }> (was Promise<void>) so resolving mode can capture git rev-parse HEAD. Not re-exported from src/index.ts today, so no public-API break.
  • Docs — README paragraph, docs/configuration.md Lockfile subsection, one-liner in docs/architecture.md.

Test plan

  • npm run typecheck
  • npm run test — 367 unit/integration tests
  • npm run lint
  • npm run test:harness — 7 end-to-end scenarios against a local bare git repo, matching the spec verification list:
    • clean install creates lockfile with matching SHA
    • upstream advances; non-refresh install keeps the locked SHA
    • --refresh-packs rewrites the lockfile
    • --frozen-lockfile post-refresh: succeeds, no diff
    • delete [[packs]] block + rac doctor warns; rac install prunes the entry
    • rac pack override prunes lockfile entry; --clear + install re-resolves
    • two "clones" with --frozen-lockfile produce byte-identical generated outputs

User-visible workflow

# normal install — fast, deterministic
rac install

# bump packs to latest upstream commits
rac install --refresh-packs
git commit .rac/rac-lock.json -m "bump packs"

# CI gate
rac install --frozen-lockfile

raniejade added 10 commits May 11, 2026 20:46
Adds PackLockEntry / PackLockFile types, extends PackRuntime with
resolvedSha, and introduces src/core/pack-lock.ts (loadPackLock,
writePackLock, findLockEntry) with full test coverage.
…t to ensureSharedPack

Changes GitRunner return type from Promise<void> to Promise<{ stdout: string }>,
updates defaultGitRunner to capture stdout, adds lockedSha option to
ensureSharedPack for deterministic SHA checkout, and captures resolvedSha
from rev-parse in resolving mode. Updates all mock call sites accordingly.
Adds FrozenLockfileError class, integrates loadPackLock/writePackLock
into resolvePacks, and routes each pack through locked or resolving mode
based on whether a matching lock entry exists. Extends pack-lock.test.ts
with eight resolvePacks integration scenarios covering first install,
locked repeat install, refresh, frozen-lockfile violations and success,
overrides exclusion, ref-change re-resolve, and stale-entry cleanup.
Enforce that git rev-parse HEAD returns a valid 40-char hex SHA in
ensureSharedPack; throw a descriptive error if not, preventing empty
strings from being written to the lockfile. Update two parsers tests
whose stub gitRunners were returning empty stdout for rev-parse, and
add drifted-entry frozen error and frozen+stale-only tests to the
pack-lock integration suite. Export FrozenLockfileError from src/index.ts.
Plumbs frozen?: boolean through InstallOptions/DiffOptions → computeInstallPlan
→ resolvePacks, and surfaces FrozenLockfileError as exit code 2 in both the
install and diff commands. Also guards against --refresh-packs + --frozen-lockfile
being combined (exit code 2, clear error message).
- addProjectPack now accepts opts.gitRunner, resolves the pack via
  ensureSharedPack after writing config.toml, and upserts the lockfile
  entry. On resolve failure, config.toml stays mutated but the lockfile
  is not touched, matching today's partial-failure behaviour.
- removeProjectPack drops the matching lockfile entry when the lockfile
  exists; skips the write when the entry was absent (no spurious mtime
  change).
- setProjectPackOverride / clearProjectPackOverride are unchanged.
- Add test/pack-config.test.ts covering all 7 required scenarios.
- Update test/parsers.test.ts to pass a fake gitRunner to the three
  existing addProjectPack call sites so they stay hermetic.
Extends doctor() with three new project-scope lockfile checks:
- lockfile_malformed (error): emitted when rac-lock.json cannot be parsed
- missing_lockfile_entry (error, frozen mode only): emitted for each pack in
  config.toml that lacks a matching (id, repo, ref) lockfile entry
- stale_lockfile_entry (warn): emitted for each lockfile entry whose
  (id, repo, ref) triple is no longer present in config.toml

When the lockfile is malformed, resolvePacks is skipped to avoid masking
the diagnostic with a thrown exception; the rest of doctor's checks still run.

Also adds --frozen-lockfile flag to the doctor CLI command, and accepts
an optional gitRunner in doctor() opts for test isolation. Six new test
cases in test/doctor-overrides.test.ts cover all diagnostic branches.
- README.md: short paragraph under Shared Packs about the lockfile,
  --frozen-lockfile, --refresh-packs, and overrides skipping it.
- docs/configuration.md: new Lockfile subsection (format, write
  triggers, --refresh-packs, --frozen-lockfile, overrides, doctor checks).
- docs/architecture.md: one sentence noting SHA pinning happens at
  pack resolution (stage 1) via the lockfile.
- .changeset/rac-lock-json.md: minor changeset for @raniejade/rac.
- scripts/harness-smoke.mjs: setupLockfileSmoke verifies rac-lock.json
  is written after pack add + install; setupPackOverrideScope now uses
  a local bare git repo + GIT_CONFIG_GLOBAL insteadOf redirect so
  pack add succeeds without real GitHub network access.
Add noWrite option to resolvePacks that suppresses both writePackLock
and FrozenLockfileError; pass noWrite:true from doctor() to preserve
its read-only contract. Correct docs: overridden pack lockfile entries
are pruned on next install, not left untouched.
Extends setupLockfileSmoke to exercise all 7 spec verification
scenarios: lockfile creation with SHA matching upstream HEAD, pinning
across upstream advances, --refresh-packs rewrite, --frozen-lockfile
no-op, stale-entry doctor warning and pruning, pack override prunes
then re-resolves on clear, and two-clone byte-identical output with
--frozen-lockfile.
@raniejade
raniejade merged commit 33037ca into main May 11, 2026
2 checks passed
@raniejade
raniejade deleted the rac-lock-json branch May 11, 2026 12:05
raniejade pushed a commit that referenced this pull request May 14, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and publish to npm
yourself or [setup this action to publish
automatically](https://github.com/changesets/action#with-publishing). If
you're not ready to do a release yet, that's fine, whenever you add more
changesets to main, this PR will be updated.


# Releases
## @raniejade/rac@0.4.0

### Minor Changes

- [#39](#39)
[`c6f501b`](c6f501b)
Thanks [@raniejade](https://github.com/raniejade)! - Add local pack
overrides for dev cycle. A new `.rac/config.local.toml` (gitignored)
with `[[pack_overrides]]` entries redirects a configured `[[packs]]` id
to a local directory, bypassing git and cache so packs can be iterated
on locally without commit/push churn.

- New CLI: `rac pack override <id> <path>` and `rac pack override <id>
--clear`.
- `rac pack list` decorates overridden packs with `(override → <path>)`.
- `rac doctor` and `rac install` emit a `WARN` per active override
(install still exits 0).
- `rac init` writes `.rac/.gitignore` containing `config.local.toml`.

- [#41](#41)
[`33037ca`](33037ca)
Thanks [@raniejade](https://github.com/raniejade)! - Pin shared packs to
resolved commit SHAs via `.rac/rac-lock.json`. The lockfile is committed
alongside `config.toml`; future installs check out the locked SHA
instead of re-resolving the floating `ref`. Two machines installing the
same project now produce identical outputs, and CI can gate on lockfile
drift.

- New flag `--frozen-lockfile` for `rac install`, `rac diff`, and `rac
doctor`: errors (exit code 2) if the lockfile would change.
- Existing `--refresh-packs` now also re-resolves and rewrites the
lockfile.
    -   `rac pack add` / `rac pack remove` keep the lockfile in sync.
    -   Pack overrides skip the lockfile entirely.
- `rac doctor` reports malformed lockfiles, stale entries, and (with
`--frozen-lockfile`) missing entries.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant