fix(installer): support Windows self-update - #103
Conversation
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
|
/review |
|
/review without questions |
|
lgtm |
|
/nik find possible regressions or error propagations |
|
Now I have all the information needed. Let me deliver the final report. Regression and Error Propagation AnalysisThe PR adds Windows-specific handling to the bash installer and corresponding test assertions. I traced the new code paths, the original code paths they replaced, and the new failure modes they introduce. Findings, ordered by severity: 1. HIGH — PowerShell helper uses
|
|
/nikcli fix and make pr |
SummaryI addressed the regression review findings on PR #103 ( install (bash) — addressed findings #1, #2, #3, #4, #5, #6, #9, #10
Tests — addressed finding #7 + added behavioral coverage
Verification
PRPushed as a follow-up commit on |
The PR introducing Windows self-update had 10 regression risks flagged
during review. This commit addresses the high and medium severity items
and tightens the test coverage so the deferred-replace path is actually
exercised.
install (bash):
- Derive binary_name after the os=windows branch so the --binary flow
cannot pick up nikcli.exe on a macOS host running inside an MSYS2
sub-shell (regression #6).
- Hoist the powershell.exe check into a require_powershell helper and
call it BEFORE staging the pending binary, so a host without
powershell.exe reports the failure cleanly instead of leaving a
stranded nikcli.exe.new.PID file behind (regression #1, #3).
- Surface mv/chmod failures with fail/exit 1 in install_binary instead
of relying on set -e to detect them. A read-only install directory
no longer prints 'Installed to ...' on failure (regression #2).
- Reject 0-byte helper files before invoking powershell.exe so an
interrupted heredoc does not silently succeed without moving the
binary (regression #1 follow-up).
- Clean up the pending binary when the heredoc write fails so a future
nikcli run does not try to load a staged but unmoved copy.
- Remove trailing newline from the windows_path fallback so the
powershell argument parser does not see an embedded whitespace (low
severity #9).
- Rename the helper from a hidden dotfile (.nikcli-update-PID.ps1) to
the destination-pattern style (nikcli.exe.update.PID.ps1) for
consistency with the existing pending filename pattern (low #10).
- Rename the install_from_binary staged file from a hidden dotfile to
a visible local-PID file for consistency.
tests (Bun):
- Replace the shallow 'expect(installer).toContain(...)' assertions in
automation.test.ts with regression-targeting checks: ordering of
require_powershell vs pending-file staging, mv return-code handling,
helper emptiness guard, and the binary_name derivation order
(regression #7).
- Add a new test/installation/install-script.test.ts that actually
executes the bash install script in a sandboxed environment with a
fake uname/cygpath/powershell.exe/mv on PATH. The fake mv emulates
Windows 'rename over a locked file' semantics so the deferred-replace
path can be exercised from a Linux test runner. Tests cover the
non-Windows direct replace, Windows first install, Windows locked
binary with PowerShell available (verifies powershell.exe receives
the cygpath-translated Windows path), and Windows locked binary
without PowerShell (verifies non-zero exit and no straggling
.new.PID files).
Local verification:
- bash -n ./install -> SYNTAX OK
- bun test test/release/automation.test.ts test/installation/ -> 35 pass
- bun run typecheck -> clean
Regression review follow-upThanks to the review bot for the deep regression walkthrough. This commit install (bash)
Tests
A new
Verification
Pushed as a follow-up commit on the same branch |
Co-authored-by: nikomatt69 <nikomatt69@users.noreply.github.com>
|
/review |
| for arg in "$@"; do LAST="$arg"; done | ||
| if [ "$#" -ge 2 ] && [ -e "$LAST" ]; then | ||
| echo "fake-locked-mv: cannot overwrite '$LAST'" >&2 | ||
| exit 1 |
There was a problem hiding this comment.
Suggestion: The if/else in buildFakeBin for writing the mv shim could be an early return. The current shape is the "if A do X, else do Y" pattern that the style guide recommends avoiding.
if (opts.lockedMv) {
await fs.writeFile(...)
return
}
await fs.writeFile(...)Only a style preference — feel free to ignore if you prefer the current form.
| rm -f "$staged_binary" | ||
| if [ "$install_deferred" = true ]; then | ||
| spinner_stop ok "Update staged; restart nikcli to finish" | ||
| else |
There was a problem hiding this comment.
Bug: the comment "In all cases the staged file should be gone" is not quite right. If install_binary exits via require_powershell (PowerShell missing on a host with a locked destination), the set -e + exit 1 short-circuits this function and the rm -f "$staged_binary" never runs. The staged ${INSTALL_DIR}/${binary_name}.local.$$ is leaked in that path.
The same shape exists in download_and_install — if install_binary exits via require_powershell, the rm -rf "$tmp_dir" does not run, so the extracted archive and its ${binary_name} copy persist under ${TMPDIR:-/tmp}/nikcli_install_$$.
Two ways to address (your call):
- Move the cleanup before
install_binary(e.g. anEXITtrap that also covers the download tmp dir). - Have
install_binaryreturn a status instead ofexit-ing, and let the callersrmon the error path.
Either is fine — happy to leave it as-is if the edge case is acceptable since the script aborts with a clear error.
| expect(code).not.toBe(0) | ||
| expect(stderr).toContain("powershell.exe is unavailable") | ||
|
|
||
| const installDir = path.join(home, ".nikcli", "bin") |
There was a problem hiding this comment.
Test gap: this test claims it ensures "no pending file behind", but the stragglers filter only catches .new.<pid> files. The install_from_binary path stages the source as ${INSTALL_DIR}/${binary_name}.local.<pid> before calling install_binary. If install_binary exits via require_powershell (no PowerShell + locked destination), that .local.<pid> file IS left behind and this test would not catch it. The test description and the assertion are out of sync.
A minimal fix: also assert the .local. stragglers are empty, or rename the assertion to specifically target the .new.<pid> file the deferred path would have created.
aa0251d to
e4e57e0
Compare


Issue for this PR
Closes #104
Type of change
What does this PR do?
Fixes the curl installer on Windows in two places:
nikcli.exe, but the installer searched fornikclinikcli.execannot replace itself on WindowsThe installer now selects the platform-specific executable name. If Windows locks the installed executable, it stages the new binary and starts a hidden PowerShell helper that replaces it after NikCli exits.
How did you verify your code works?
bash -n ./installbun test test/release/automation.test.ts test/installation/effect-service.test.ts(23 pass)bun run typecheckinpackages/nikclinikcli.exenikcli.exelocked for 8 seconds; installer returned in 0.57 seconds, then replacement completed with no temporary files leftnikcli-ai-windows-x64/bin/nikcli.exeScreenshots / recordings
N/A
Checklist