Skip to content

fix: LTS lines from the index, UTF-16/BOM .nvmrc, .nvmrc after a cd; docs: a page of scenarios - #44

Merged
linghengqian merged 17 commits into
mainfrom
docs/use-case-scenarios
Nov 4, 2026
Merged

linghengqian merged 17 commits into
mainfrom
docs/use-case-scenarios

Conversation

@linghengqian

@linghengqian linghengqian commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Seventeen commits, which can be read apart:

  1. docs: add a page of scenarios, and say when .nvmrc is read
  2. docs: say that Node.js 26 entered long-term support as Lithium (written before the fix and overtaken by 3 and 6, kept as it was pushed)
  3. fix(parse_legacy_file): add Lithium (Node.js 26) to the codename table (the table is only the cold start since 14)
  4. fix(parse_legacy_file): read a .nvmrc in UTF-16 or with a byte order mark
  5. fix(parse_legacy_file): make vfox notice a version file after a cd
  6. docs: describe Lithium, UTF-16 files and version files after a cd
  7. docs: say what a build of vfox with the working directory in its state does (its message says 48 lines and the shell of docs/nvmrc.md; the patch adds 55 lines and the replayed shell is the one of docs/scenarios.md, see 9) (its text on the build is gone again with 13)
  8. test: keep the hook tests from removing the state of the shell they run in
  9. docs: keep the vfox patch in the repository, and say what it covers (taken out again by 13)
  10. Merge origin/main into docs/use-case-scenarios: brings in fix: download with vfox's User-Agent so Huawei Cloud, TUNA, USTC and BFSU work as mirrors #45 (Huawei Cloud, TUNA, USTC and BFSU as mirrors). The only conflicts were the two "Unreleased" lists of docs/changes.md and docs/zh-CN/changes.md, where both pull requests added entries; both are kept, the one of fix: download with vfox's User-Agent so Huawei Cloud, TUNA, USTC and BFSU work as mirrors #45 first
  11. docs: say that a second local run of the e2e suite can be a replay
  12. test(e2e): wait for the Docker engine before the Windows suite pulls: Windows container (windows-latest) of the run for commit 11 failed because the Docker engine did not answer yet when run.ps1 started its base image pull (failed to connect to the docker API at npipe:////./pipe/docker_engine). Nothing in this pull request touches the Windows suite, and the two runs before on the same runner image passed, and the run for the commit itself passed in full
  13. docs: take the vfox patch out of this pull request, and link the issues: the problem is reported in vfox as version-fox/vfox#745 and tracked here as Track version-fox/vfox#745: .nvmrc and .node-version are not applied after a cd #46, which stays open until a vfox release has the change. The patch of commits 7 and 9 is not part of the result of this pull request
  14. fix(parse_legacy_file): learn the LTS lines from the index instead of a table: lts/<codename>, lts and lts/* no longer depend on a table that a release of the plugin has to extend for every line Node.js promotes. The plugin keeps what the index says in .lts-cache next to itself and reads it offline
  15. test(e2e): check the file in which the plugin learned the LTS lines: the Linux and Windows suites read the file after the install and the search (the WSL suite follows in 17)
  16. docs: say that the plugin learns the LTS lines from the index, English and Chinese
  17. test(e2e): run the check of the learned LTS lines on WSL, and say what it shows on Windows: the WSL suite had not run the check of 15, although its verify.go asks to be changed together with the Linux one; and docs/e2e.md and a comment of the Windows suite said that the check tries a rename over an existing file, which the log does not show (see "Not checked")

The pull request began as the page of scenarios. Writing its cases turned up three problems of the plugin. They are fixed here, one commit each (3 to 5), so that any of them can be dropped, and 6 carries the documentation of all three, in both languages. The table of LTS codenames, which had gone stale for Lithium, no longer needs anybody to keep it up: the plugin learns the lines from the index (14 to 17).

The page of scenarios

docs/scenarios.md and docs/zh-CN/scenarios.md show the plugin in concrete cases, and both READMEs say what it is for: it does for vfox what nvm-sh/nvm does for POSIX shells, and it also works in PowerShell, where nvm-sh/nvm does not run. Cases: the nvm commands next to vfox's; Windows PowerShell 5.1 from scratch (execution policy, profile, what differs on Windows); one Node.js for a whole team (.vfox.toml, or the .nvmrc that nvm users already have, vfox install --all -y); several projects in one shell; another release in one window; vfox exec for a test matrix and for CI; following a line or pinning a release; mirrors and proxies; editors. Where release 0.5.3 and main differ, the tables show both, every cell taken from a run of each.

The fixes

LTS lines (3, 14). Node.js 26 entered long-term support on 2026-10-28 as Lithium. The codename table ended at Krypton, so under latest_installed lts/lithium selected nothing and lts and lts/* selected a 24.x, while latest_available selected 26.13.0. Commit 3 added lithium = 26, which is the stopgap that a table needs once a year. ParseLegacyFile runs at every prompt in a directory with a version file, so it cannot ask the index for the lines: 336 KB, and two requests for an lts/ name, which took 750 to 1000 ms a prompt against 10 ms (measured with a logging mirror, nodejs.org from here; 270 to 380 ms through npmmirror). Commit 14 has the plugin write what the index says into .lts-cache, a text file next to the plugin where vfox keeps its own .available.cache, whenever it has the index in hand anyway: Available (vfox search, an install of latest or of a major), and an install of an exact release when the file is missing or a day old (vfox installs those without asking Available). ParseLegacyFile reads the file, over the built-in table, and never fetches. A plugin directory that cannot be written, or a plugin that has learned nothing yet, has the table, as before. nvm and fnm read the index; asdf-nodejs, whose logic this was taken from, still ends its table at Krypton. The built-in table is the cold start, and a line missing from it is learned the first time the plugin has the index in hand.

UTF-16 and a byte order mark (4). Windows PowerShell 5.1 writes UTF-16 with a mark for > and Out-File, and UTF-8 with a mark for -Encoding UTF8. The plugin read the bytes as they were, so the mark became part of the text, no release matched it, and vfox said nothing. util.decode_text decodes both, and leaves text with no mark as it is. UTF-32 and UTF-16 without a mark are still not read, and the docs say so.

A .nvmrc after a cd (5), a workaround. vfox 1.0.13 keeps the result of vfox env for a shell (env-state.json in the session directory) and calculates again only for another .vfox.toml, a changed PATH or a newer config file. A .nvmrc or .node-version is not among these, and main of vfox has the same code (internal/env/state.go, and configPaths in cmd/commands/env.go holds only .vfox.toml files that exist). So after a cd from a running shell the file was ignored. vfox runs ParseLegacyFile before it loads that state, and the hook now removes env-state.json whenever it reads a version file (util.forget_env_state). What that does and does not do, with the same shell replayed under release 0.5.3 and under this branch (global release 26.13.0):

Step Release 0.5.3 This branch
cd into a directory with a .nvmrc for 18.20.8 v26.13.0 v18.20.8
cd into a directory with a .node-version for 24.22.2 v26.13.0 v24.22.2
edit that file to name 22.23.4 v26.13.0 v22.23.4
cd into a directory with no config at all v26.13.0 v22.23.4
cd into a directory with a .vfox.toml for 18.20.8 v18.20.8 v18.20.8
cd into a directory with no config at all v26.13.0 v26.13.0
  • It does not fix the way out. In a directory with no config at all the hook is not called, so vfox keeps the result of the directory that was left and the shell stays on its release (the bold cell). A directory with a .vfox.toml or a version file of its own switches, and so does a new shell. I found no way to do that from a plugin, and it needs a change in vfox. It is reported there as version-fox/vfox#745 (a reproduction with the 1.0.13 release and with main, the cause, two ways to change it) and tracked here as Track version-fox/vfox#745: .nvmrc and .node-version are not applied after a cd #46. A change that ties vfox's kept result to the working directory, which I tried on 1.0.13, switched on every cd of the shell above, the way out included, with the plugin of this branch and with release 0.5.3; it does not notice an edit of the file in the directory the shell is in (release 0.5.3 stayed on the old release), so commit 5 would stay useful for that. The patch itself is not part of this pull request.
  • It reaches into a file that vfox owns, by its name and by $__VFOX_CURTMPPATH, and it makes vfox calculate again at every prompt in a directory with a version file. The extra calculation took about a millisecond per prompt with one SDK installed (7.4 against 7.6 ms for vfox env, 30 calls each), and with many SDKs it will cost more. It is isolated in one function and one call, so dropping commit 5 is a revert, plus the lines of commit 6 that describe it. Say so if you would rather not carry it.

How it was checked

  • Hook tests: 73 pass (the 63 of this branch before fix: download with vfox's User-Agent so Huawei Cloud, TUNA, USTC and BFSU work as mirrors #45, the 2 of fix: download with vfox's User-Agent so Huawei Cloud, TUNA, USTC and BFSU work as mirrors #45 and 8 for the LTS lines; 53 before this pull request), and the first three code commits pass at every step (55, 59, 63). Each of 12 mutations of the LTS code, made in a copy (what was learned ignored, the table winning over it, a stale or fresh file misjudged, no refresh at an install, the refresh before the install is checked, no rename, a Windows path without its backslash, a damaged line trusted, and others), turns one of them red. Commit 8 fixes a side effect of commit 5 that I found by running them in a shell where vfox is active: forget_env_state removed the env-state.json of that shell, since the tests used the real os.getenv and os.remove. CI has no such shell, so it did not show. The tests now hide __VFOX_CURTMPPATH, and with it set the file survives a run. StyLua 2.5.2 --check . and LuaLS 3.19.1 --checklevel=Warning are clean, with the release assets checked against the digests that GitHub publishes. gofmt, go vet (also GOOS=windows for the Windows suite) and go test -short in tests/e2e pass.
  • Against the real vfox 1.0.13, in a scratch HOME with a clean environment: the .nvmrc encodings (ASCII with LF, CRLF or no line break, UTF-8 with a mark, UTF-16 in both byte orders), lts/lithium, lts/* and lts under the three strategies, and the shell above in bash 5.3 and in PowerShell 7.6.9 (the official linux-x64 tarball, SHA-256 checked), calling the prompt function that the hook installs once per command. --debug shows Config changed: true in a directory with a version file and Using cached output after leaving it.
  • The LTS lines against the real vfox 1.0.13, in a scratch HOME: vfox search wrote .lts-cache with the twelve lines of the live index, equal to the built-in table, and left no temporary file. With a local mirror whose index has an invented v28.0.0, Mercury, a fake 28.0.0 installed and lts/mercury in a .nvmrc, latest_installed selects it and the prompts made no request; without the file it selects nothing. A PreInstall of 20.20.2 fetched the index once and not on the second run. gopher-lua 1.1.1 ends in a Go nil pointer dereference at if file == nil after local file = path ~= nil and io.open(path, "r") or nil; the code opens the file in two statements, and docs/development.md says so.
  • The e2e suite on Linux, glibc, vfox latest, default mirror, run locally with E2E_MAX_JOBS=1: pass, including the legacy probes and the new check of .lts-cache (commit 15; 208 s with -count=1), before the merge and again on the merge of main (78 s with -count=1). The first run on the merge printed ok ... (cached) with the timings of the run before it, because go test replays a result when what the test opened looks unchanged, plugin files that had changed included. I threw that run away and ran it again with -count=1; commit 11 says so in docs/e2e.md. The rest of the matrix ran in CI, see the next item.
  • The E2E workflow in the run for 28736b7 (run 41628376081): every job passed, and so did the other checks (check, license-eye, lint-go, lint-lua, test). The check of .lts-cache (commits 15 and 17) printed PASS the plugin learned 12 LTS lines from the index, the newest is 26 for all 17 combinations: Linux and Alpine on amd64 and arm64 (latest and main vfox with the default mirror, and latest with npmmirror on amd64), windows-latest (three), windows-11-arm (two) and WSL (two). In the Windows containers the plugin is in C:\Users\John Doe\.vfox\plugin\nodejs, with a space in the path, and Get-Content found the file there with all twelve codenames at the majors of the table, so on Windows the plugin finds its directory and writes to it. The run for 0c69b48, before commit 17, passed the same way on every job except WSL, which did not run the check yet.
  • The reproduction in version-fox/vfox#745, run in a scratch HOME with a clean environment and the nodejs plugin 0.5.3 from the registry (vfox add nodejs), with the 1.0.13 release binary and with a build of main (cdf0747): two bash sessions, one that goes into a .nvmrc directory and one that goes out of it. The output is identical for both, and the transcripts in the issue are that output. The seven-step shell of docs/scenarios.md ran on that build of main too, with release 0.5.3 of the plugin and with the plugin of this branch, and gave the same cells as with 1.0.13; the row of the table above that edits the file was only run on 1.0.13.
  • Every relative link and heading anchor resolves, in all 30 pages after the merge, and the English and Chinese scenarios.md have the same code blocks and the same inline code.

Not checked

  • Whether the wait for the Docker engine (commit 12) is what fixes the failure. The wait ran on Windows in the run for commit 12 and every job passed, but nothing in the log says whether the engine was slow this time. That it was slow in the failed run is read from the log and the timings, not reproduced. The function itself was also run with PowerShell 7.6.9 on Linux against a fake docker (an engine that answers at once, on the third call, never, and no docker).
  • A write over an existing .lts-cache on Windows (commits 14 and 15). The Windows jobs show that the plugin finds its directory and writes there, see above. They do not show that a write replaces a file that is already there: by the order of the steps the install writes the file and the search writes it again, but a rename that failed the second time would leave the first file, and the check passes with that file too. The plugin takes it quietly, since the file would stay as old as the first write and an install after a day would fetch the index again, with no other harm. The only basis for it working is that Go's os.Rename, which os.rename calls under gopher-lua, replaces an existing file on Windows. My first text for docs/e2e.md and a comment in the Windows suite said that the check tries this rename; commit 17 takes that back.
  • A cd in a running Windows shell. The Windows suite writes the pinned version with -Encoding Unicode and utf8BOM and probes both, and no longer removes env-state.json itself before a probe, so it relies on commit 5. Those probes passed on windows-latest (three combinations) and windows-11-arm (two) in the run for commit 12, and the two runs before it succeeded as well. But each probe starts a new PowerShell, so none of this is a cd in a shell that keeps running, which is what forget_env_state is for, and it does not show that the file is removed on Windows paths (a removal that fails is ignored on purpose). PowerShell 7 writes the same bytes for these encodings as Windows PowerShell 5.1 does for > and -Encoding UTF8 (checked on Linux).
  • Windows PowerShell 5.1 itself. The e2e suite never runs it. The statements about its execution policy and about what it writes rest on Microsoft's documentation, and the ones about the registry Path, junctions, the fallback to the global scope and vfox exec with node.exe rest on vfox's source. docs/scenarios.md marks each.
  • vfox's documentation says a bare vfox use is global on Windows. Its source defaults to the session when the hook is active. The pages spell out the scope in every Windows command.

docs/scenarios.md and its translation show the plugin in concrete cases:
the nvm-sh/nvm commands next to vfox's, Windows PowerShell 5.1 from
scratch, one Node.js for a whole team, several projects in one shell,
another release in one window, vfox exec, mirrors and editors. Both
READMEs say what the plugin is for: it does for vfox what nvm-sh/nvm
does for POSIX shells, and it also works in PowerShell, where nvm-sh/nvm
does not run.

Writing the cases turned up three things that the pages got wrong or
left out:

- .nvmrc and .node-version are read when a shell starts in their
  directory, not when you cd into it from a running shell. vfox 1.0.13
  keeps the first result of `vfox env` and works it out again only for
  another .vfox.toml, a changed PATH or a newer config file, and a
  directory with only a .nvmrc changes none of these. `vfox --debug env`
  shows the plugin parsing the file and vfox then printing "Using cached
  output". Checked in bash 5.3 and PowerShell 7.6.9. A .vfox.toml
  switches on cd. nvmrc.md gets a section for it, and the READMEs and
  troubleshooting.md stop saying "when you enter a directory".
- A .nvmrc with a byte order mark, or saved as UTF-16, selects nothing
  and vfox says nothing. Windows PowerShell 5.1 writes both: > and
  Out-File give UTF-16, -Encoding UTF8 gives UTF-8 with a mark.
  nvmrc.md lists it under "Not understood", and scenarios.md shows what
  to write instead.
- Windows PowerShell 5.1 is not driven by the e2e suite, which runs
  PowerShell 7. platforms.md says so, and scenarios.md marks what rests
  on Microsoft's documentation or on vfox's source instead of on a run.

docs/changes.md gets an "Unreleased" entry, with its translation.
The index of nodejs.org has marked 26.13.0 with the codename Lithium
since 2026-10-28, but lts.md still said that no codename was announced
and showed 24.22.0 as the newest LTS release.

It now shows today's `vfox search nodejs | grep -m1 '(LTS)'`, and what
the strategies do with Lithium on 2026-11-03: latest_available selects
26.13.0 for lts/* and lts/lithium, while latest_installed selects
24.22.2 for lts/* and nothing for lts/lithium, because the codename
table in hooks/parse_legacy_file.lua has no entry for 26 yet. The table
itself is not changed here, docs/development.md has the steps.
Node.js 26 entered long-term support on 2026-10-28 as Lithium. The table in
hooks/parse_legacy_file.lua ended at Krypton (24), so under `specified` and
`latest_installed` `lts/lithium` selected nothing and `lts` and `lts/*`
selected the newest 24.x, while `latest_available`, which reads the codename
from the index, selected 26.13.0 for all three.

Add `lithium = 26`. The hook tests cover `lts/lithium` and the bare `lts`,
which is the highest line of the table, and the table of the e2e suite
(LTSCodenames) and its test follow. With 22.23.4, 24.21.0 and 26.13.0
installed, `lts/lithium` and `lts` now select 26.13.0 under `latest_installed`
as well, checked against vfox 1.0.13.
…mark

Windows PowerShell 5.1 writes UTF-16 with a byte order mark for `>`, `>>`
and `Out-File`, and UTF-8 with a mark for `-Encoding UTF8`. ParseLegacyFile
removed the white space of the file and read the bytes as they were, so the
mark, or the zero bytes of UTF-16, became part of the text. No release
matched it, and vfox said nothing: the file selected nothing.

util.decode_text turns UTF-16 with a mark, in either byte order, into UTF-8
(surrogate pairs included, a lone surrogate becomes U+FFFD, a half unit at
the end is dropped) and drops the UTF-8 mark. Text that starts with no mark
is returned as it is, so UTF-32 and UTF-16 without a mark stay unread.

Checked with the bytes of each on Linux against vfox 1.0.13: ASCII with LF,
CRLF or no line break and the three marked encodings select 22.23.4, an
empty file and a mark alone select nothing. The Windows e2e writes the pinned
version with `-Encoding Unicode` and `utf8BOM`, which PowerShell 7 writes as
5.1 does for `>` and `-Encoding UTF8`, and probes both.
vfox keeps the result of `vfox env` for a shell in env-state.json in the
session directory ($__VFOX_CURTMPPATH). It calculates again only for another
.vfox.toml, a changed PATH or a newer config file, in vfox 1.0.13 and in
`main` alike. A .nvmrc or .node-version is not tracked, so after a `cd` into
a directory that has only one, vfox went on with the result it had made for
the directory before, and the file was ignored. It was read when the shell
started in the directory and after a `vfox use`, not after a cd.

vfox runs ParseLegacyFile before it loads that file, so the hook now removes
it (util.forget_env_state) whenever it reads a version file. vfox then
calculates again at every prompt in such a directory: a `cd` into it
switches, a cd from one to another switches, and a change of the file takes
effect at the next prompt. In the test below the extra calculation cost about
a millisecond per prompt with one SDK installed, and `--debug` shows `Config
changed: true` there and `Using cached output` elsewhere.

It is a workaround, to drop when vfox tracks version files. It cannot help
on the way out: in a directory with no config at all the hook is not called,
so vfox keeps the result of the directory that was left and the shell stays on
its release. A directory with a .vfox.toml or a version file of its own
switches, and so does a new shell. A fix in vfox would add the working
directory to the state it keeps.

Checked against vfox 1.0.13 in bash 5.3 and PowerShell 7.6.9, calling the
`prompt` function that the hook installs once per command, with 26.13.0 as
the global release: a cd into a .nvmrc for 18.20.8 selects it, then a
.node-version for 24.22.2, an edit of that file to 22.23.4 takes effect, a
cd into a .vfox.toml selects it and a cd into a directory with nothing goes
back to 26.13.0. Release 0.5.3 stays on 26.13.0 in all but the .vfox.toml
step. The Windows e2e no longer removes env-state.json itself before each
probe, so that it covers this.
The three fixes before this commit change what the pages say, and each page
marks what only `main` does with "Unreleased" and says what release 0.5.3
does, as docs/development.md asks.

- nvmrc.md: "When the file is read" now says that vfox keeps its first
  result and does not track version files, that the plugin makes up for it on
  the way in and cannot on the way out, and shows one shell step by step
  under release 0.5.3 and under `main`, with the `--debug` lines of both
  sides. "What the file may hold" says that UTF-16 and a byte order mark
  are decoded, and its table of the three strategies is taken again on
  2026-11-03 with 20.20.2, 22.23.4, 24.21.0, 25.9.0 and 26.13.0 installed,
  with a row for `lts/lithium`.
- lts.md: Lithium in the table, and the "Last release" column taken from
  the index of 2026-11-03. The examples run again with 22.23.4, 24.21.0 and
  26.13.0 installed, since `lts` now means 26.
- scenarios.md: the cases for several projects and for the encodings of
  Windows PowerShell 5.1 show release 0.5.3 and `main` side by side, every
  cell taken from a run of each.
- README, troubleshooting.md, development.md, changes.md and e2e.md follow,
  with the steps for the next promotion of an LTS line, the gap on the way
  out of a directory, and the Windows probes. The Chinese pages follow the
  English ones.
@linghengqian linghengqian changed the title docs: scenarios for nvm-sh/nvm users and Windows PowerShell 5.1, and when .nvmrc is read fix: Lithium, UTF-16 and BOM .nvmrc, .nvmrc after a cd; docs: a page of scenarios Nov 3, 2026
…e does

docs/development.md named the fix of the way out of a directory with a
version file as something vfox would have to do. It has been tried now: a
build of vfox 1.0.13 that keeps the working directory in env-state.json as
well (ConfigState.DirChanged, used by envFlag, 48 lines with a test) switches
in every step of the shell in docs/nvmrc.md, the way out included, also with
release 0.5.3 of the plugin, which has no workaround. The pages say so.
…un in

ParseLegacyFile now removes $__VFOX_CURTMPPATH/env-state.json
(util.forget_env_state), and the tests call it with the real os.getenv and
os.remove. Run in a shell where vfox is active, which is the shell of
anyone who works on a plugin, the 63 tests deleted the env-state.json of that
shell: with __VFOX_CURTMPPATH pointing at a directory that held one, the file
was gone afterwards. It is only a cache, vfox makes it again at the next
prompt, but a test must not change the environment it runs in.

The test file hides the variable from every test, and the tests that look at
the removal set it themselves. Run again with the variable set, the file is
still there, and the 63 tests pass with and without it. CI has no such shell,
which is why it did not show.
Since 7a6fa78 docs/development.md has said that a build of vfox which
keeps the working directory in env-state.json switches on the way out of a
directory with a version file. The patch behind that was a file next to a
checkout. It is docs/patches/vfox-1.0.13-env-state-working-dir.patch now,
with a header that says what it is for, and the pages link to it. The
licence check ignores *.patch: a patch for another project has to apply
as it is.

The pages say how to build a vfox with it (git clone --depth 1 --branch
v1.0.13, git apply, go test, go build). Run as written on 2026-11-03,
git apply is ok, the tests of internal/env and cmd pass, and the build
reports 1.0.13. Replaying the shell of docs/scenarios.md step by step,
with 26.13.0 as the global release:

- vfox 1.0.13 as released with the plugin of this branch stays on 24.22.2
  after the cd into a directory with no config at all (the known gap);
- with the patch, the plugin of this branch and release 0.5.3 both go
  back to 26.13.0, and every other step is right as well;
- an edit of a .node-version in the directory the shell is in takes
  effect at the next prompt with the plugin of this branch, patched vfox
  or not, and does not with release 0.5.3 on the patched vfox. So the
  patch does not make util.forget_env_state needless: the patch covers
  the cd in both directions, the workaround the edit in place.

Corrections to 7a6fa78, whose message cannot be amended: the patch adds
55 lines, not 48; the replayed shell is the one of docs/scenarios.md,
while docs/nvmrc.md has one more step, an edit of the file, which the
patch alone does not cover; and the patch does not apply to vfox main as
it is. On cdf0747 (2026-11-02) git apply refuses two of the three files
and patch -p1 skips one hunk, because main has added a check for the PATH
policy (PathPolicyChanged, SetPathPolicy) where the patch inserts its
lines. A build from main was not tried.
@linghengqian linghengqian added the enhancement New feature or request label Nov 3, 2026
Brings in #45 (73cba8b): the download of the package sends the User-Agent
of vfox, so Huawei Cloud, TUNA, USTC and BFSU work as mirrors.

The conflicts were in the "Unreleased" lists of docs/changes.md and
docs/zh-CN/changes.md, where both sides added entries. Both are kept, the
entry of #45 first because it reached main first.

The other files that both sides changed (lib/util.lua, tests/hooks_test.lua,
docs/development.md, docs/troubleshooting.md and their Chinese pages) merged
clean as text, so the result was checked:

- the 63 hook tests of this branch and the 2 of #45 pass, 65 in all, and with
  __VFOX_CURTMPPATH pointing at a directory that holds an env-state.json the
  tests leave the file alone;
- StyLua 2.5.2 --check . and LuaLS 3.19.1 --checklevel=Warning are clean;
- every relative link and heading anchor of the 30 pages resolves;
- hooks/pre_install.lua, docs/mirror.md and docs/zh-CN/mirror.md, which only
  #45 changed, are identical to origin/main.
go test replays the result of an earlier run when what the test opened looks
unchanged. A run of the Linux suite for the merge of main, after lib/util.lua
and hooks/pre_install.lua had changed since the run before, printed
`ok ... (cached)` with the output and the timings of that run, 28.92s and
138.63s again, to the hundredth. It would have passed whatever the plugin did.

docs/e2e.md and its Chinese page now say to add -count=1 after editing hooks/
or lib/. CI starts from a clean checkout and is not affected.
"Windows container (windows-latest)" of this pull request (run
41612312368, 2c02b48) ended about two minutes in with

  FAIL docker pull mcr.microsoft.com/windows/servercore:ltsc2025-KB5122871-amd64 exited with code 1

The pull, which run.ps1 starts in the background before anything else,
printed one line: "failed to connect to the docker API at
npipe:////./pipe/docker_engine; check if the path is correct and if the
daemon is running: open //./pipe/docker_engine: The system cannot find the
file specified." The tools were fetched and vfox@main was built without
trouble meanwhile, and the job failed when run.ps1 collected the pull. The
engine was not listening yet. Nothing in this pull request touches the
Windows suite, and the two runs of the same job before it (0898bbb, 8a7a881)
passed with the same script on the same runner image (windows-2025-vs2026
20261030.294.1). In those the pull began 10.9 and 16.0 seconds after the log
did, in the failed one 9.8 seconds, which fits an engine that comes up some
ten seconds after the machine; that is a reading of the log, not something
I could reproduce.

run.ps1 now calls Wait-Engine before it starts the pull: `docker version`
every five seconds, up to 60 times, the wait that install-docker-arm64.ps1
already has for the engine it starts. An engine that is up costs one call. One
that never answers fails after five minutes with its own message, instead of
at the end of the run with the exit code of the pull. docs/e2e.md and its
Chinese page say so.

Tested with PowerShell 7.6.9 on Linux and a fake docker, on the function cut
out of run.ps1 under the settings that run.ps1 has at its top: an engine that
answers at once (1 call), one that answers on the third call (returns after
2 s), one that never answers (throws "did not answer in 3 seconds" after 3
calls), and no docker at all (CommandNotFound); the preference variable is
restored every time, and run.ps1 parses. Without the line that switches off
the error preference for native commands, the second case throws on the
first failed call. Not run on Windows: the next run of this job is the test.
The patch changed another project. Nothing in the plugin, its tests or CI
used it, it applies to vfox 1.0.13 and not to main, and the only place
where it can do some good is vfox. The problem is reported there now, with
a reproduction, the cause in 1.0.13 and in main (cdf0747), and two ways to
fix it: version-fox/vfox#745. It is tracked here as #46 until a vfox
release fixes it.

Removed: docs/patches/, the ignore for *.patch in .licenserc.yaml, the
section "A vfox that notices a cd" of docs/development.md, and the
sentences in nvmrc.md, scenarios.md, changes.md and development.md (Known
gaps) that told how to build a vfox with it or counted on it, in English
and Chinese. They say now that the cache is vfox's and link the two
issues. What the pages say about the plugin does not change:
util.forget_env_state covers the way into a directory with a version file
and an edit in place, not the way out.

The patch stays in the history of the branch (8a7a881) and not in the
result of this pull request.
@linghengqian linghengqian added bug Something isn't working and removed enhancement New feature or request labels Nov 3, 2026
… a table

lts/<codename>, lts and lts/* under `specified` and `latest_installed` came
from a table in hooks/parse_legacy_file.lua, because ParseLegacyFile runs at
every prompt in a directory with a version file and must not fetch
index.json: 336 KB, 0.3 to 0.9 s here, and two requests for an lts/ name
under latest_available. Measured with a logging mirror: 10 ms a prompt
against 750 to 1000 ms. A table needs a release of the plugin for every line
that Node.js promotes: the released plugin lacks Lithium since 2026-10-28,
and asdf-nodejs, whose logic this was taken from, still ends at Krypton. nvm
and fnm read the index.

The plugin now writes what the index says into `.lts-cache` next to itself,
and ParseLegacyFile reads that file:

- Available calls util.remember_lts with the index it has decoded.
- PreInstall calls util.refresh_lts_cache once the install has its checksum,
  so an install that fails early fetches nothing. vfox installs an exact
  release without asking Available (seen with a fresh HOME: the first hook
  call is PreInstall), so that is where such an install learns. It asks
  Available when the file is missing or a day old, and only when the file can
  be written, so a read-only directory does not cost an index per install.
- ParseLegacyFile reads it through util.known_lts_lines: what was learned over
  the built-in util.LtsLines, and the newest major of the two for lts and
  lts/*. It never fetches.

vfox gives a plugin no path of its own. util.plugin_dir finds the directory in
package.path, where the plugin's hooks/?.lua and lib/?.lua are, and vfox keeps
its own .available.cache in that directory too. The file is plain text (the
json stub of the tests has no encode), written beside the old one and renamed
over it. No directory, nothing writable, a missing or damaged file: the plugin
has the table, as before. The table is the same twelve lines, now in util.

Checked against the real vfox 1.0.13 in a scratch HOME: `vfox search` wrote
the twelve lines of the live index, equal to the table. With a local mirror
whose index has an invented v28.0.0, Mercury, a fake 28.0.0 installed and
`lts/mercury` in a .nvmrc, latest_installed selects it with no request made
by the prompts, and nothing without the file. A PreInstall of 20.20.2 fetched
the index once, and not on the second run. 73 hook tests pass, 8 of them new,
and each of 12 mutations of the code turns one of them red. StyLua and LuaLS
are clean.

gopher-lua 1.1.1 ends in a Go nil pointer dereference at `if file == nil`
after `local file = path ~= nil and io.open(path, "r") or nil`. The code
opens the file in two statements, and docs/development.md says so.
After the pinned install and `vfox search`, the Linux and Windows suites read
.lts-cache in the plugin directory (${VFOX_HOME:-$HOME/.vfox}/plugin/nodejs,
and C:\Users\John Doe\.vfox\plugin\nodejs in the Windows containers). They
require every codename of LTSCodenames, the Go copy of the plugin's table,
with the same major, and a newest LTS major of 26 or more. The index names
all of them, so this also tells when the table is wrong.

On Windows this is the first run of the code that finds the plugin directory
in a package.path with backslashes and renames a file over an existing one. I
cannot try that here, there is no Windows: if it fails there, the file is
missing and the job says which. common.LearnedLTS reads the file, with a unit
test.

The Linux suite passes locally (vfox latest, default mirror, -count=1): "PASS
the plugin learned 12 LTS lines from the index, the newest is 26".
lts.md tells how the plugin knows the codenames: the index under
latest_available; under the other strategies what it learned, kept in
.lts-cache next to the plugin; the table as the start. A promotion looks
different now: the plugin knows the new line from the first search or install
after it, with no release of the plugin, and lts.md still says what release
0.5.3 does. nvmrc.md, changes.md and the examples follow.

development.md has a section on how the plugin learns: the file, the three
places that use it, how the directory is found and how the tests keep away
from the checkout. The steps for a promotion are housekeeping now, and the
gopher-lua quirk that ended in a nil pointer dereference is written down next
to the other one. e2e.md lists the new check. English and Chinese.
@linghengqian linghengqian changed the title fix: Lithium, UTF-16 and BOM .nvmrc, .nvmrc after a cd; docs: a page of scenarios fix: LTS lines from the index, UTF-16/BOM .nvmrc, .nvmrc after a cd; docs: a page of scenarios Nov 3, 2026
…t it shows on Windows

The WSL suite did not call verifyLearnedLTS, although tests/e2e/wsl/verify.go
asks to be changed together with the Linux one and docs/e2e.md listed the
check for every combination. It does now: it reads .lts-cache in the
VFOX_HOME of the slot and compares it with LTSCodenames, as the Linux suite
does. Nothing here can run wsl.exe, so its first run is the one in CI.

docs/e2e.md and the comment in tests/e2e/windows/verify.go said that the
Windows check tries the path with backslashes and a rename over an existing
file. The log of the run for 0c69b48 shows the file in
C:\Users\John Doe\.vfox\plugin\nodejs with the twelve lines on all five
Windows combinations (windows-latest three, windows-11-arm two), so the
plugin finds its directory there, with a space in the path, and writes to it.
It does not show whether the write that made the file replaced an earlier
one: by the order of the steps the install writes the file and the search
writes it again, but a rename that failed the second time would leave the
first file, which passes the check as well. The text now says what the log
shows, in English and Chinese.
@linghengqian
linghengqian merged commit a70070d into main Nov 4, 2026
13 checks passed
@linghengqian
linghengqian deleted the docs/use-case-scenarios branch November 4, 2026 01:02
linghengqian added a commit that referenced this pull request Nov 4, 2026
… LTS line (#47)

* docs: list the 0.5.4 changes under the release and drop the "Unreleased" remarks

Release 0.5.4 is published, so the pages stop marking its behavior as newer
than the last release. This is the procedure of "Releasing" in
docs/development.md, in English and Chinese:

- changes.md: "Unreleased" becomes "0.5.4 (2026-11-04)", every entry gets its
  pull request (#43, #45, #44), the sentence about the newest release "at the
  time of writing" goes, and the User-Agent is `vfox/1.0.13 vfox-nodejs/0.5.4`.
- README, README_CN: the paragraph that explains "Unreleased" goes, and the
  `.nvmrc` paragraph describes one behavior.
- lts, nvmrc, scenarios, versions, mirror: the "Unreleased: release 0.5.3 ..."
  remarks go. The tables that set 0.5.3 against "Unreleased" (nvmrc.md, and
  two in scenarios.md) keep the one column that is 0.5.4.
- troubleshooting, mirror, nvmrc: where a reader of an older plugin looks up
  a message or a symptom, a short "Before 0.5.4" / "Since 0.5.4" sentence
  stays (`version not found ... 27.0.0-alpha.0.0.0`, `checksum error` through
  Huawei Cloud, TUNA, USTC and BFSU, `.nvmrc` not applied after a `cd`).
  mirror.md no longer names the mirrors that worked before 0.5.4, only the
  four that failed, which is what was checked.
- scenarios.md: the outputs are from "the code that became release 0.5.4"
  (hooks/ and lib/ of the PR branch equal the release), and the sentence that
  gave the fresh shell per directory of the Windows e2e suite as a result of
  the `cd` limit now says only what the suite covers.
- source-build.md: the example URL of a released zip is 0.5.4.

Checked with vfox 1.0.13 in an isolated HOME, using the published zip (sha256
0e2cbadc...): `vfox info` says 0.5.4. Against the live nodejs.org, `vfox
search` lists 877 releases and no pre-release although the index starts with
27.0.0-alpha.0.0.0, and `.lts-cache` holds the 12 lines up to lithium. Against
a local mirror whose index starts with a pre-release, `install nodejs@latest`
takes the newest release, and the index, SHASUMS256.txt and the download all
carry `User-Agent: vfox/1.0.13 vfox-nodejs/0.5.4`; with small stand-in
tarballs, the 8-row table of nvmrc.md and the 7-row table of scenarios.md come
out as written, and the three .nvmrc encodings select 22.23.4. The links and
anchors of all pages resolve.

* docs(versions): Node.js 26 is an LTS line since 2026-10-28

"A line that is not in long-term support yet, such as 26 today" stopped being
true when 26.13.0 (Lithium) came out on 2026-10-28; the index of nodejs.org
shows it with `lts: Lithium`, and it is the newest release, so `latest` is an
LTS release now. The sentence is about why `latest` is not "the LTS", and the
table above it is from 2026-10-21, when 26 was still the line that was not in
long-term support. It says so now, in English and Chinese.

This is not caused by release 0.5.4. It is a separate commit so that it can
be dropped alone.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant