Skip to content

Feat/node - #41

Open
Hansehart wants to merge 6 commits into
mainfrom
feat/node
Open

Feat/node#41
Hansehart wants to merge 6 commits into
mainfrom
feat/node

Conversation

@Hansehart

Copy link
Copy Markdown
Owner

No description provided.

Installs the runtime from the official prebuilt tarball into /usr/local and
verifies it against SHASUMS256.txt, rather than following the upstream node
feature's nvm install. nvm exists to switch versions at runtime, which a pinned
dev container does not need, and it would be the only such layer in this repo:
every other feature here resolves a version, checks it, and drops a binary on
PATH.

npm's global prefix defaults to the dev user's ~/.local so `npm i -g` needs no
root, matching the ~/.local/bin convention claude-code, codex and uv already
set. With a stateDir it moves to the volume alongside npm's cache and user
config, so globally installed CLIs survive a rebuild.

The npmrc option follows settingsJson/configToml/daemonJson: baked at build,
written by the run-once hook, and restored if something overwrites it.

Note the tarball ships node, npm and npx only. There is no compiler, so packages
needing node-gyp will not build, and corepack is gone as of Node 26, so yarn and
pnpm need an explicit npm i -g.
sops was the only feature claiming ", verified against its published checksums"
in its description, though uv verifies checksums too and says nothing. The claim
belongs in install.sh, where it already is, not in the feature listing.

The stateDir options had split into two idioms. sops now uses the "If set,
exports X ... (persist it by mounting a volume there). Empty ..." shape that
claude-code and codex already used.

install.sh comments use a closed vocabulary of section tags. docker-in-docker
had "Repo:", the only tag outside it, and tagged the entrypoint install as
"Install:" after a "Configure:" block, which broke the shared order. It is the
same ship-a-script step the other features label "Hook:".

The init.sh hooks said "once its volume is mounted" in claude-code and codex but
"once the volume is mounted" in sops and uv.

No version bumps: these change prose only, following 5b2b440.
Every install.sh tags its sections from the same vocabulary and runs them in the
order Dependencies, Resolve, Fetch, Install, Configure, Hook, Verify. Two
features broke it.

uv baked its default Python in a "Configure:" block after the "Hook:" block. The
block only needs the profile, which is written earlier, so it moves up.

latex wrote config.env and installed lib.sh and init.sh under "Configure:"
before the install branch. That is the hook install, so it is retagged and moved
after the branch. install_texlive() reads SCHEME, REPO and INSTALLER_DIR from
install.sh's own scope rather than from config.env, so nothing in the branch
depends on what the moved block writes, and the hook is not needed until
postCreate. Its state dir group block also lost the "Configure:" tag when nested
in the else branch.

latex keeps Fetch before Resolve: install-tl -print-platform cannot run until the
installer is downloaded, as the comment says.

Both also pick up the stateDir description idiom. Patch bumps because the shipped
install.sh changed and the publish action only pushes new versions.
The claude-code test resolves its target as ${CLAUDE_CONFIG_DIR:-$HOME/.claude},
so it follows the state dir when one is set. Its codex twin hardcoded
$HOME/.codex, which happens to work only because the scenario sets no stateDir.
Node keeps several supported lines alive at once (today 26 Current, 24 Active LTS,
22/20 Maintenance), so "newest, or one exact patch" did not cover the normal ask.
lts resolved to the tag vlts and 24 to v24, both 404s at download time, leaving an
exact patch as the only way to hold a line -- and Renovate bumps the feature
version, not an option value inside it, so that pin had to be moved by hand.

index.json carries "lts": false | "<codename>" per release, so the channel and the
lines fall out of the file install.sh already downloads. No nvm: that layer exists
to switch versions at runtime, which a pinned dev container does not need.

The default moves from latest to lts. latest is Node's Current line, which upstream
does not recommend for production, so it sat where headless-chrome puts dev and
canary rather than where that feature puts its default: latest|stable both resolve
to key="Stable" there, two majors behind Canary. The invariant across this repo is
that the default gives you the upstream's stable track. Aliasing latest to LTS
would have kept the uniform default, but nodejs.org/dist/latest really does mean
Current and contradicting upstream's own vocabulary is the worse surprise.

Partial versions come free: the three-part glob does not match 24.19, which falls
through to the line branch and pins a minor line.

Also fixes a SIGPIPE this branch shipped in 0aba5f5. The tag came from
`grep -oP ... | head -n 1`, and grep emits 860 lines into a head that exits after
one; under `set -euo pipefail` the closed pipe kills grep with 141 and aborts the
install. It went unnoticed because the feature was never built here -- no Docker in
this environment -- and because a `printf | grep -m1` variant fails the same way
while a pre-filtered pipeline survives on buffering alone. grep -m1 now reads a
here-string, which bash backs with a temp file, and `|| true` lets a no-match reach
the explicit error instead of dying silently under set -e. No other feature has the
pattern; age, sops and headless-chrome pipe a source that emits a single match.

Scenarios cover both new forms with assertions that survive a floating patch: the
24 line by prefix, and lts by the resolved major being even, which is the property
that identifies an LTS line under Node's release policy.
The greps matched one release per line, which held only because nodejs.org serves
index.json pretty-printed. Nothing documents that. Against a minified index the lts
branch returned v26.8.1, the Current line, silently mislabelled as LTS -- it fails by
answering wrongly rather than by erroring, which is the failure mode worth removing.

tr '}' '\n' puts one release per line whichever way the file arrives, so both shapes
now resolve identically. curl | tr cannot SIGPIPE, since tr reads to EOF.

latest was the only resolve branch without a scenario, and the branch that shipped the
pipe bug in 847fe90. Its assertion is deliberately weak because the build is the real
check: a resolution mistake aborts the install before the script runs.

The description said "on a selectable channel", which stopped being true when the
option grew release lines.
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