Skip to content
This repository was archived by the owner on Jul 24, 2026. It is now read-only.

feat(nix): add self-contained flake with generated shell completions - #104

Merged
schickling merged 3 commits into
mainfrom
schickling-assistant/2026-07-20-nix-flake
Jul 20, 2026
Merged

feat(nix): add self-contained flake with generated shell completions#104
schickling merged 3 commits into
mainfrom
schickling-assistant/2026-07-20-nix-flake

Conversation

@schickling-assistant

@schickling-assistant schickling-assistant commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

What

Adds a self-contained flake.nix at the repo root, so nix build works on this repo's own HEAD — and wires it up as this repo's first CI.

Outputs:

  • packages.smalltalk / packages.defaultst and smalltalk bins, meta.mainProgram = "st"
  • checks.help — smoke test running st --help
  • checks.completions — asserts every installed shell yields a non-empty script, and that fish output still binds to st
  • checks.typecheck — runs the repo's own npm run build (typecheck-only) over src/ and examples/
  • devShells.default

Inputs are nixpkgs, flake-utils, and github:compoundingtech/pty (nixpkgs follows). Nothing private, so anyone can build this standalone.

Why

Packaging for this repo currently lives out-of-tree in a private dotfiles wrapper, pinned to a fixed old rev via a smalltalk-src input. That means the repo's own HEAD was never buildable on its own and the packaging silently drifted from the source — the wrapper still symlinked the pre-rename @myobie/pty path, while package.json / package-lock.json now name the dep @compoundingtech/pty. This flake targets the current tree (src = self) and uses the current name.

Version is read from package.json rather than hardcoded. Reconciling that with a richer build identity is tracked in #103.

CI

.github/workflows/nix.yml is the repo's first CI, modeled on compoundingtech/pty's nix.yml: PR + push-to-main, ubuntu-latest, DeterminateSystems/determinate-nix-action@v3.

It runs nix flake check, not nix build — the distinction is the point. nix build would only prove the package compiles; nix flake check additionally evaluates every checks.*, so help, completions, and typecheck all gate the PR. Two of those exercise a real built binary: the completions contract is verified by invoking st completions <shell> on the artifact that was just built, not by inspecting committed files.

The flake is also what makes CI tractable at all here. package.json declares "@compoundingtech/pty": "file:../pty" — a sibling-checkout dependency. A plain Node workflow would have to clone pty next door at some manually-chosen rev and keep that in sync by hand; as a flake input, pty is resolved and pinned by flake.lock like any other dependency.

What CI does not cover

nix flake check does not run this repo's vitest suites. To be precise about why, since it is not the reason you would guess:

  • unit — this very nearly works. Installing devDependencies in the sandbox turned out to be surmountable (see mkDevCheck in the flake), and 1322 of 1323 unit tests pass there. The single holdout is tests/unit/cli.test.ts, which asserts st <semver>+<short-sha>; the SHA is produced by shelling out to git rev-parse in the source dir, and a hermetic build has no .git. The CLI degrades gracefully by design — it is only the test that requires a git checkout. Making this green means settling build identity, i.e. Adopt the shared build-identity contract: st --version has no build identity, MCP serverInfo reports a different version #103, so it is deliberately left out rather than papered over.
  • integration — not attempted, and shouldn't be: it shells out to rsync and spawns real pty sessions.

So: treat "CI green" as "it builds, the binary runs, completions are intact, and it typechecks" — not as "the test suite passed". Closing the unit gap is a viable follow-up; the cheapest version is skipping that one environment-dependent assertion when git is unavailable, which is a change to the test file and so out of scope for this PR.

Completions contract

Completions are generated at build time by the just-built binary (st completions <shell>), not committed, so they cannot drift from the command tree in src/commands/completions.ts. All three shells the generator supports are installed:

shell installed path
bash share/bash-completion/completions/st.bash
zsh share/zsh/site-functions/_st
fish share/fish/vendor_completions.d/st.fish

The generator hardcodes complete -c st (and the zsh/bash equivalents), so the scripts are always for st regardless of which bin name invoked them — hence --cmd st and the st.fish filename. checks.completions greps for ^complete -c st so a change that broke that binding would fail the check rather than silently ship dead completions.

Notes

  • The sibling file: dep link is expressed as a small npm-name -> store path attrset mapped into ln -s lines, so adding a second sibling repo later is a one-line change.
  • checks.typecheck needs devDependencies, which the shipped package omits. Two sandbox obstacles, both documented at mkDevCheck: npm cannot resolve file:../pty through a store symlink (it chmods the linked bin/pty, which is read-only in the store, and fails EPERM), so the sibling is copied to the relative path the lockfile names and made writable; and --ignore-scripts keeps node-pty's native build out of the sandbox. npmDepsHash is unchanged — prefetch-npm-deps already hashes the full lockfile, and --omit=dev is install-time only.
  • # TODO(rust): markers flag the three things a Rust rewrite deletes: the npmDepsHash, the sibling-symlink block, and the node --experimental-strip-types bin shims.
  • The bin shims export _ST_INVOKED_AS, matching what bin/st does, so smalltalk --help self-identifies correctly.
  • Deliberately ships only st and smalltalk (exactly package.json's bin). The dotfiles wrapper also emitted a legacy coord alias, but this repo has zero references to it. No live regression: the dotfiles wrapper is unchanged for now.
  • Added /result to .gitignore.

Verification

  • nix build .#default succeeds. npmDepsHash = "sha256-CfgSssHEdSjL9liKjaw9aYKMtCJdaxWKGbFM59PgNwg=" (computed via nix run nixpkgs#prefetch-npm-deps -- package-lock.json, confirmed by the build; the two file:../pty entries are expectedly uncacheable and are supplied by the store symlink instead).
  • nix flake check passes locally — help, completions, and typecheck all green.
  • The workflow YAML was linted (yamllint, zero errors) and parsed before pushing.
  • st --help, smalltalk --help, and st completions fish all verified against the built output; the fish script starts with complete -c st -e.

Packaging for this repo lived out-of-tree in a private dotfiles wrapper
pinned to an old rev, so the repo's own HEAD was never buildable on its
own. This moves the derivation in-tree with only public inputs
(nixpkgs, flake-utils, compoundingtech/pty).

Completions for bash, zsh and fish are generated at build time by the
just-built binary via `st completions <shell>`, so they cannot drift
from the command tree in src/commands/completions.ts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty
@schickling
schickling marked this pull request as ready for review July 20, 2026 15:18
schickling-assistant and others added 2 commits July 20, 2026 17:19
This repo had no CI at all. Mirror compoundingtech/pty's nix.yml, but run
`nix flake check` rather than `nix build` so the flake's `checks.help` and
`checks.completions` actually gate PRs instead of only the package building.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty
`nix flake check` previously only exercised the built binary (help,
completions). Add `checks.typecheck`, which runs the repo's own
`npm run build` (typecheck-only: both tsconfigs set `noEmit`) over src/
and examples/.

This needs devDependencies, which the shipped package omits. Two sandbox
obstacles, both documented at the `mkDevCheck` definition: npm cannot
resolve the `file:../pty` devDependency through a store symlink (it
chmods the read-only linked bin and fails EPERM), so the sibling is
copied to the relative path the lockfile names; and `--ignore-scripts`
keeps node-pty's native build out of the sandbox.

The vitest `unit` project is not gated yet. 1322/1323 pass in the
sandbox; the holdout asserts a `+<short-sha>` version suffix resolved by
shelling out to `git rev-parse`, which a hermetic build has no `.git`
for. That is the build-identity question tracked in #103.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty
@schickling
schickling merged commit 6912003 into main Jul 20, 2026
1 check passed
schickling-assistant added a commit that referenced this pull request Jul 20, 2026
PR #104 added the flake with an npmDepsHash computed against the
package-lock.json on its own branch. Meanwhile 12a4772 ("sync stale
package-lock.json to package.json (0.3.0)") landed on main and changed
that lock. The merge of #104 combined the new flake with main's newer
lock, so the hash was stale from the moment it landed -- a semantic
conflict a text-level merge cannot see. main's Nix workflow has been red
on the merge commit (run 29775629348) ever since.

Regenerated with the command the flake itself documents:
  nix run nixpkgs#prefetch-npm-deps -- package-lock.json

Verified locally: `nix build .#default` succeeds and `nix flake check`
passes all three checks (help, completions, typecheck).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty
schickling-assistant added a commit to compoundingtech/convoy that referenced this pull request Jul 21, 2026
…ent (#87) (#91)

* feat(nix): self-contained flake + `nix flake check` as CI

convoy had no flake and no CI. This adds both, and they are the same thing:
`nix flake check` is the gate.

The flake is thin and self-contained — nixpkgs, flake-utils, and the two
sibling packages from their own flakes. It builds from `self`, so it packages
whatever revision it ships with rather than a pinned fetchFromGitHub.

Why a flake check and not a Node workflow: convoy depends on smalltalk and pty
via `file:../sibling`, so a plain `npm ci` in CI needs those repos checked out
next to convoy. Flake inputs solve that properly — the sibling packages come
from their own flakes, pinned in flake.lock, and the sibling node_modules links
are expressed declaratively as an npm-name -> store-path attrset.

Checks (all run a REAL built binary, not a source tree):
  - help        — `convoy --help`
  - completions — generates bash/zsh/fish, asserts each is non-empty, that fish
                  still binds to `convoy`, and that each shell accepts its own
                  generated script (`-n`). This is where zsh actually gets
                  syntax-checked; it is often absent from a dev machine.
  - typecheck   — convoy's own `tsc --noEmit`, against the same sibling deps.

The vitest suite is deliberately not wired up: parts of it shell out to
`git worktree` and probe the host, which the sandbox cannot provide.

Completions are generated at build time from the just-built binary and
installed via installShellCompletion, so what ships can never drift from
src/command-table.ts.

The smalltalk input points at the branch of compoundingtech/smalltalk#104 (the
PR adding smalltalk's flake) because that flake does not exist on its main yet;
there is a TODO to repoint once it merges.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty

* feat(nix): run the completions parity suite as a flake check

The suite in src/completions.test.ts is what keeps the command table, argv
dispatch, and the generated scripts from drifting — but nothing ran it
automatically. It is sandbox-safe (it only spawns `convoy` and reads
src/cli.ts), unlike the rest of the vitest suite, which shells out to
`git worktree` and probes the host.

bash, zsh, and fish are in the check's inputs so its per-shell syntax
assertions actually run instead of skipping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty

* fix(nix): repoint the smalltalk input at main

compoundingtech/smalltalk#104 has merged, so smalltalk's flake now exists
on main and the PR-branch pin is no longer needed. Drops the TODO that
asked for exactly this.

The pty input already pointed at plain `github:compoundingtech/pty`
(main) and carried no TODO, so it is left untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty

* fix(nix): lock smalltalk onto the post-#105 main tip

The flake pinned smalltalk at ec24ac4, whose npmDepsHash was stale --
smalltalk main was broken standalone, so every `nix flake check` here
failed on an input it does not own. smalltalk#105 fixed that hash on
main; this moves the lock onto that merge commit (850266f).

Only the smalltalk node moves. Verified locally: `nix build .#default`
succeeds, `nix flake check` is green on all four checks (help,
completions, parity, typecheck), and the built `convoy --help` and
`convoy completions fish` both work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1xLDkqUDCYUMMADdMqVgP
agent-session-id: 312caff5-3274-4d97-baa4-8ff06ab03fc5
agent-tool: Claude Code
agent-tool-version: 2.1.215
agent-model: claude-opus-4-8
agent-runtime-profile: /nix/store/acr8a3l2v366jgmwiq8xdrhgz1py0db5-coding-agent-runtime-profile/share/coding-agents/profile.json
agent-skills-manifest: /nix/store/sj1v5j91h8v8d1w9lca4040302lwrd6v-agent-skills-corpus/share/agent-skills/manifest.json
tooling-profile: dotfiles@unknown-dirty

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants