Skip to content

feat: add Windows support via file-copy apply mode#3

Merged
a3chron merged 12 commits into
mainfrom
feat/windows-support
Jul 12, 2026
Merged

feat: add Windows support via file-copy apply mode#3
a3chron merged 12 commits into
mainfrom
feat/windows-support

Conversation

@a3chron

@a3chron a3chron commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Windows handles symlinks poorly (they need Developer Mode or admin), so on Windows stellar now copies the theme file over starship.toml instead of symlinking. The mode is selected by runtime.GOOS and overridable via STELLAR_APPLY_MODE=copy|symlink for testing.

  • symlink: rename CreateSymlink -> ApplyTheme, add IsCopyMode() and a shared copyFile helper; back up the user's original config safely in copy mode (never overwrite an existing backup, so a lost config.json can't clobber it)
  • current: make verification mode-aware (only readlink in symlink mode)
  • update: unblock Windows, append .exe, download next to the binary, and do a Windows-safe self-replace; clean up the leftover .old on the next run
  • goreleaser: build windows/amd64 + windows/arm64
  • installer: add install.ps1 and point install.sh's Windows guard at it
  • tests: copy-mode unit and e2e coverage, incl. the backup-clobber safety net

Windows handles symlinks poorly (they need Developer Mode or admin), so on
Windows stellar now copies the theme file over starship.toml instead of
symlinking. The mode is selected by runtime.GOOS and overridable via
STELLAR_APPLY_MODE=copy|symlink for testing.

- symlink: rename CreateSymlink -> ApplyTheme, add IsCopyMode() and a shared
  copyFile helper; back up the user's original config safely in copy mode
  (never overwrite an existing backup, so a lost config.json can't clobber it)
- current: make verification mode-aware (only readlink in symlink mode)
- update: unblock Windows, append .exe, download next to the binary, and do a
  Windows-safe self-replace; clean up the leftover .old on the next run
- goreleaser: build windows/amd64 + windows/arm64
- installer: add install.ps1 and point install.sh's Windows guard at it
- tests: copy-mode unit and e2e coverage, incl. the backup-clobber safety net

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@a3chron a3chron linked an issue Jul 11, 2026 that may be closed by this pull request
a3chron and others added 11 commits July 11, 2026 21:17
…lf-update

Address code-review findings on the Windows support work:

- record applied_hash in config.json so stellar recognizes its own
  applied file mode-independently; the documented edit-and-reapply
  workflow, clean --all, and apply-mode switches no longer create junk
  versioned backups (legacy configs without the hash keep the old
  content-match fallback)
- stellar current: branch on the observed on-disk state (Lstat) instead
  of STELLAR_APPLY_MODE, restore the lost "theme file missing"
  diagnostic for copied configs, and surface path-resolution errors
- stellar rollback: print the backup notice instead of silently
  discarding the backup path; share the notice helper with apply and
  derive the restore hint from the written path
- install.ps1: verify the download against checksums.txt before
  replacing stellar.exe (temp-then-move), preserve REG_EXPAND_SZ when
  updating the user PATH, detect WoW64 hosts via PROCESSOR_ARCHITEW6432
- self-update: gate leftover cleanup to Windows, remove only stellar's
  own .old/.stellar-update-* artifacts, drop the unreachable removal of
  the running old binary
- tests: pin STELLAR_APPLY_MODE=symlink in SetupTestEnv and skip
  symlink tests where unsupported so the suite runs on Windows; make
  CaptureOutput restore stdout on test failure; add regression tests
  for the backup, rollback, current, and cleanup behavior

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ructured backup info

Move the AppliedHash bookkeeping out of the apply/rollback call sites into
ApplyTheme itself, so no future caller can forget it and cause spurious
backups. In copy mode the hash is computed while the file streams through
copyFile instead of re-reading it afterward.

Replace BackupIdentifier's path re-parsing with a BackupInfo{Path, Identifier}
built at creation time from the same author/version values used to construct
the path, formatted via theme.Theme.String() so the identifier wire format
stays defined in one place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Preserve the checksum-verified download when the Windows binary swap
  double-fails (rename to .old succeeded, install and restore both failed):
  replaceExecutable now owns the temp file's fate and returns recovery
  instructions instead of letting the caller delete the only good binary.
- Run update-leftover cleanup on every OS, not just Windows: temp files are
  created next to the binary everywhere, so an interrupted update on
  Linux/macOS no longer litters the bin dir forever.
- Match leftovers with os.ReadDir + prefix instead of filepath.Glob, which
  misinterpreted glob metacharacters in the install path (e.g. tools[1])
  and silently cleaned nothing.
- Turn the GitHub release endpoints into package vars as an e2e test seam.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-healthy

The regular-file branch reported a healthy applied theme as long as the
cached theme file existed, so a hand-modified or replaced starship.toml was
misreported as the current theme (a regression from the symlink-only check
on main). Compare the file's hash against cfg.AppliedHash (falling back to
the cached theme file for legacy configs) and report 'modified or replaced'
with a re-apply hint on mismatch. Also flag an empty CurrentPath and a
directory at the config path as broken instead of skipping validation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backups of the user's original starship.toml live at <author>/backup inside
the cache tree, so 'stellar clean' (and --all) swept them away and made the
printed restore hint permanently unrecoverable ('backup' is not a real hub
author, so there is nothing to re-download). Skip backup themes during
cleaning; they remain removable explicitly via 'stellar remove'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- OR TLS 1.2 into SecurityProtocol so Windows PowerShell 5.1 on pre-.NET-4.7
  defaults (TLS 1.0 only) can reach GitHub instead of failing the download.
- Expand registry PATH entries before comparing against the install dir, so
  an existing unexpanded entry (e.g. %LOCALAPPDATA%\stellar\bin) is
  recognized and no duplicate is appended; the raw REG_EXPAND_SZ value is
  still written back untouched.
- Merge the duplicated if ($RawPath) blocks computing NewPath and Kind.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…afety

- TestE2E_Update: successful update, already-up-to-date, and checksum-
  mismatch paths against an httptest server via the release-URL seams, with
  the test binary snapshotted and restored around the swap.
- stellar current: healthy untouched copy-mode apply, hand-edited config
  reported as modified, empty current path and directory-at-config-path
  reported as broken.
- stellar clean / clean --all: original-config backups survive and the
  printed restore hint still applies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pers

- export symlink.IsManaged as the single "is starship.toml stellar's own"
  predicate; backupOriginalConfig and `stellar current` now use the same
  three-signal check, so current's modified diagnostic can no longer
  disagree with whether the next apply actually backs the file up
- add theme.BackupThemeName so the backup writer (internal/symlink) and
  the clean-time guard (internal/cache) share one constant
- add theme.IsValidIdentifierRune as the single [a-zA-Z0-9_-] definition,
  used by sanitizeBackupAuthor, with a test pinning it to the parser regex
- collapse current.go's four duplicated diagnostic blocks into
  printReapplyHint / printThemeFileMissing (output unchanged)
- extract platformBinaryName shared by updateCmd and the update E2E test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hanging

The backup version-slot search only exited on ENOENT, so any persistent
non-ENOENT stat error (ENOTDIR because <author>/backup exists as a plain
file, EACCES on an unreadable dir) spun the loop forever and hung
`stellar apply`/`stellar rollback`. Probe errors now abort the apply with
a clear error, leaving the user's unmanaged starship.toml untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cleanupUpdateLeftovers runs on every command start and removed any
.stellar-update-* file next to the binary, including the temp file of a
"stellar update" still running in another process, aborting that update
at the final rename. Leftover temp files are now only removed once they
are older than an hour; an in-flight download's mtime is always fresh.
The .old removal stays unconditional since it is never in-flight.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ery files from leftover cleanup

On Windows, os.Rename intermittently fails with sharing violations while
Defender or the Search Indexer briefly holds a freshly written file. The
new symlink.RenameWithRetry rides that out with a bounded backoff (no-op
single rename on other OSes) and is used for the theme-apply rename and
the self-update binary swap.

The self-update double-failure path now also renames the preserved,
checksum-verified download to a .stellar-recovery-* name that
removeUpdateLeftovers never matches, so a later stellar run can no longer
delete the exact file the recovery instructions point at.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@a3chron a3chron merged commit 2f4579f into main Jul 12, 2026
@a3chron a3chron deleted the feat/windows-support branch July 12, 2026 12:21
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.

[Feature Request] Add native PowerShell installation script (install.ps1)

1 participant