Skip to content

Commit 6f02073

Browse files
committed
docs(rfc): sync RFC with implementation
- Update status from "Draft" to "Implemented" - Fix Code Sharing table: all install functions are in vite_setup::install, not separate submodules - Fix Dependency Graph: remove junction (indirect via vite_setup), add actual deps (vite_path, owo-colors) - Fix Customization submenu to match code (numbered items, no install dir) - Replace winreg code sample with raw FFI description (matches implementation) - Replace windows_sys DLL sample with raw FFI (matches implementation) - Remove winreg from Binary Size Budget, add raw FFI note - Fix Alternatives #4: raw FFI, not winreg - Fix CI snippets: rename vite-init to vp-setup, update test workflow to match actual test-vp-setup-exe job - Mark Implementation Phases 1-3 as done, Phase 4 as future
1 parent 4dc36b5 commit 6f02073

1 file changed

Lines changed: 89 additions & 87 deletions

File tree

rfcs/windows-installer.md

Lines changed: 89 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Status
44

5-
Draft
5+
Implemented
66

77
## Summary
88

@@ -85,17 +85,12 @@ crates/vite_installer/ — standalone installer binary
8585

8686
### What Gets Extracted
8787

88-
| Current location in `upgrade/` | Extracted to `vite_setup::` | Purpose |
89-
| ----------------------------------------------------------------- | --------------------------- | ------------------------- |
90-
| `platform.rs``detect_platform_suffix()` | `platform` | OS/arch detection |
91-
| `registry.rs``resolve_version()`, `resolve_platform_package()` | `registry` | npm registry queries |
92-
| `integrity.rs``verify_integrity()` | `integrity` | SHA-512 verification |
93-
| `install.rs``extract_platform_package()` | `extract` | Tarball extraction |
94-
| `install.rs``generate_wrapper_package_json()` | `package_json` | Wrapper package.json |
95-
| `install.rs``write_release_age_overrides()` | `npmrc` | .npmrc overrides |
96-
| `install.rs``install_production_deps()` | `deps` | Run `vp install --silent` |
97-
| `install.rs``swap_current_link()` | `link` | Symlink/junction swap |
98-
| `install.rs``cleanup_old_versions()` | `cleanup` | Old version cleanup |
88+
| Original location in `upgrade/` | Extracted to `vite_setup::` | Purpose |
89+
| ------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
90+
| `platform.rs` | `platform` | OS/arch detection |
91+
| `registry.rs` | `registry` | npm registry queries |
92+
| `integrity.rs` | `integrity` | SHA-512 verification |
93+
| `install.rs` (all functions) | `install` | Tarball extraction, package.json generation, .npmrc overrides, dep install, symlink/junction swap, version cleanup, rollback support |
9994

10095
### What Stays in `vite_global_cli`
10196

@@ -117,15 +112,16 @@ crates/vite_installer/ — standalone installer binary
117112

118113
```
119114
vite_installer (binary, ~3-5 MB)
120-
├── vite_setup (new library)
115+
├── vite_setup (shared installation logic)
121116
├── vite_install (HTTP client)
122-
├── vite_shared (home dir, output)
117+
├── vite_shared (home dir resolution)
118+
├── vite_path (typed path wrappers)
123119
├── clap (CLI parsing)
124120
├── tokio (async runtime)
125121
├── indicatif (progress bars)
126-
└── junction (Windows junctions)
122+
└── owo-colors (terminal colors)
127123
128-
vite_global_cli (existing, unchanged)
124+
vite_global_cli (existing)
129125
├── vite_setup (replaces inline upgrade code)
130126
└── ... (all existing deps)
131127
```
@@ -156,13 +152,14 @@ This will install the vp CLI and monorepo task runner.
156152
Customization submenu:
157153

158154
```
159-
Install directory [C:\Users\alice\.vite-plus]
160-
Version [latest]
161-
npm registry [https://registry.npmjs.org]
162-
Node.js manager [auto]
163-
Modify PATH [yes]
155+
Customize installation:
164156
165-
Enter option number to change, or press Enter to go back:
157+
1) Version: [latest]
158+
2) npm registry: [(default)]
159+
3) Node.js manager: [auto-detect]
160+
4) Modify PATH: [yes]
161+
162+
Enter option number to change, or press Enter to go back:
166163
>
167164
```
168165

@@ -306,42 +303,32 @@ On failure before the **Activate** phase, the version directory is cleaned up an
306303

307304
### PATH Modification via Registry
308305

309-
Same approach as rustup and `install.ps1`:
306+
Same approach as rustup and `install.ps1`, using raw Win32 FFI (no external crate) following the same zero-dependency pattern as `vite_trampoline`:
310307

311-
```rust
312-
use winreg::RegKey;
313-
use winreg::enums::*;
314-
315-
fn add_to_path(bin_dir: &str) -> Result<()> {
316-
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
317-
let env = hkcu.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)?;
318-
319-
let current_path: String = env.get_value("Path")?;
320-
if !current_path.split(';').any(|p| p.eq_ignore_ascii_case(bin_dir)) {
321-
let new_path = format!("{bin_dir};{current_path}");
322-
env.set_value("Path", &new_path)?;
323-
// Broadcast WM_SETTINGCHANGE so other processes pick up the change
324-
broadcast_settings_change();
325-
}
326-
Ok(())
327-
}
328-
```
308+
1. Read current `HKCU\Environment\Path` via `RegQueryValueExW`
309+
2. Check if bin dir is already present (case-insensitive, handles trailing backslash)
310+
3. Prepend `%VP_HOME%\bin` if not present, write back via `RegSetValueExW` as `REG_EXPAND_SZ`
311+
4. Broadcast `WM_SETTINGCHANGE` via `SendMessageTimeoutW` so other processes pick up the change
312+
313+
See `crates/vite_installer/src/windows_path.rs` for the full implementation.
329314

330315
### DLL Security (for download-folder execution)
331316

332-
Following rustup's approach — when the `.exe` is downloaded to `Downloads/` and double-clicked, malicious DLLs in the same folder could be loaded. Mitigations:
317+
Following rustup's approach — when the `.exe` is downloaded to `Downloads/` and double-clicked, malicious DLLs in the same folder could be loaded. Two mitigations, both using raw FFI (no `windows-sys` crate):
333318

334319
```rust
335-
// In build.rs — linker flags
320+
// build.rs — linker-time: restrict DLL search at load time
336321
#[cfg(windows)]
337322
println!("cargo:rustc-link-arg=/DEPENDENTLOADFLAG:0x800");
338323

339-
// In main() — runtime mitigation
324+
// main.rs — runtime: restrict DLL search via Win32 API
340325
#[cfg(windows)]
341-
unsafe {
342-
windows_sys::Win32::System::LibraryLoader::SetDefaultDllDirectories(
343-
LOAD_LIBRARY_SEARCH_SYSTEM32,
344-
);
326+
fn init_dll_security() {
327+
unsafe extern "system" {
328+
fn SetDefaultDllDirectories(directory_flags: u32) -> i32;
329+
}
330+
const LOAD_LIBRARY_SEARCH_SYSTEM32: u32 = 0x0000_0800;
331+
unsafe { SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32); }
345332
}
346333
```
347334

@@ -404,41 +391,53 @@ Submit to winget, chocolatey, scoop. Each has its own manifest format and review
404391

405392
### Release Workflow Additions
406393

394+
In `build-upstream/action.yml`, the installer binary is built and cached alongside the CLI:
395+
407396
```yaml
408-
# In build-rust job matrix (already has windows targets)
409-
- name: Build installer (Windows only)
410-
if: contains(matrix.settings.target, 'windows')
411-
run: cargo build --release --target ${{ matrix.settings.target }} -p vite_installer
397+
- name: Build installer binary (Windows only)
398+
if: contains(inputs.target, 'windows')
399+
run: cargo build --release --target ${{ inputs.target }} -p vite_installer
400+
```
412401
413-
- name: Upload installer artifact
402+
In `release.yml`, installer artifacts are uploaded per-target, renamed with the target triple, and attached to the GitHub Release:
403+
404+
```yaml
405+
- name: Upload installer binary artifact (Windows only)
414406
if: contains(matrix.settings.target, 'windows')
415407
uses: actions/upload-artifact@v4
416408
with:
417-
name: vite-init-${{ matrix.settings.target }}
409+
name: vp-setup-${{ matrix.settings.target }}
418410
path: ./target/${{ matrix.settings.target }}/release/vp-setup.exe
419411
```
420412

421413
### Test Workflow
422414

423-
Extend `test-standalone-install.yml` with new jobs:
415+
`test-standalone-install.yml` includes a `test-vp-setup-exe` job that builds the installer from source and tests silent installation across three shells:
424416

425417
```yaml
426-
test-init-exe:
418+
test-vp-setup-exe:
419+
name: Test vp-setup.exe (${{ matrix.shell }})
420+
runs-on: windows-latest
427421
strategy:
428422
matrix:
429-
shell: [cmd, pwsh, powershell, bash]
430-
runs-on: windows-latest
423+
shell: [cmd, pwsh, bash]
431424
steps:
432-
- name: Download vp-setup.exe
433-
run: # download from artifacts or latest release
434-
- name: Install (silent)
435-
run: vp-setup.exe -y
425+
- uses: actions/checkout@v4
426+
- uses: oxc-project/setup-rust@v1
427+
- name: Build vp-setup.exe
428+
run: cargo build --release -p vite_installer
429+
- name: Install via vp-setup.exe (silent)
430+
run: ./target/release/vp-setup.exe -y
431+
env:
432+
VP_VERSION: alpha
436433
- name: Verify installation
437434
run: |
438435
vp --version
439436
vp --help
440437
```
441438

439+
The workflow triggers on changes to `crates/vite_installer/**` and `crates/vite_setup/**`.
440+
442441
## Code Signing
443442

444443
Windows Defender SmartScreen flags unsigned executables downloaded from the internet. This is a significant UX problem for a download-and-run installer.
@@ -462,9 +461,10 @@ Key dependencies and their approximate contribution:
462461
| `indicatif` | Progress bars | ~100 KB |
463462
| `sha2` | Integrity verification | ~50 KB |
464463
| `serde_json` | Registry JSON parsing | ~200 KB |
465-
| `winreg` | Windows registry | ~50 KB |
466464
| Rust std + overhead | | ~500 KB |
467465

466+
Note: Windows registry access uses raw FFI (~0 KB overhead) instead of the `winreg` crate, following the same zero-dependency pattern as `vite_trampoline`.
467+
468468
Use `opt-level = "z"` (optimize for size) in package profile override, matching the trampoline approach.
469469

470470
## Alternatives Considered
@@ -490,42 +490,44 @@ Like rustup, make `vp.exe` detect when called as `vp-setup.exe` and switch to in
490490

491491
Embed the PowerShell script in a self-extracting exe. Fragile, still requires PowerShell runtime.
492492

493-
### 4. Use `winreg` vs PowerShell for PATH (Decision: `winreg`)
493+
### 4. Use `winreg` Crate vs Raw FFI for PATH (Decision: Raw FFI)
494494

495-
- `winreg` crate: Direct registry API, no subprocess, reliable
495+
- `winreg` crate: Higher-level API, adds ~50 KB dependency
496+
- Raw Win32 FFI: Zero external dependencies, matches `vite_trampoline` pattern, slightly more code
496497
- PowerShell subprocess: Proven in `install.ps1` but adds process spawn overhead and PowerShell dependency
497-
- Decision: Use `winreg` for direct registry access — the whole point of the exe installer is to not depend on PowerShell
498+
- Decision: Use raw FFI for direct registry access — keeps the installer dependency-free for Win32 operations, consistent with the trampoline's approach
498499

499500
## Implementation Phases
500501

501-
### Phase 1: Extract `vite_setup` Library
502+
### Phase 1: Extract `vite_setup` Library (done)
502503

503-
- Create `crates/vite_setup/Cargo.toml`
504-
- Move shared code from `vite_global_cli/src/commands/upgrade/` into `vite_setup`
505-
- Update `vite_global_cli` to import from `vite_setup`
506-
- Run existing tests to verify no regressions
504+
- Created `crates/vite_setup/` with `platform`, `registry`, `integrity`, `install` modules
505+
- Moved shared code from `vite_global_cli/src/commands/upgrade/` into `vite_setup`
506+
- Updated `vite_global_cli` to import from `vite_setup`
507+
- All 353 existing tests pass
507508

508-
### Phase 2: Create `vite_installer` Binary
509+
### Phase 2: Create `vite_installer` Binary (done)
509510

510-
- Create `crates/vite_installer/` with `[[bin]] name = "vp-setup"`
511-
- Implement CLI argument parsing (clap)
512-
- Implement installation flow calling `vite_setup`
513-
- Implement Windows PATH modification via `winreg`
514-
- Implement interactive prompts
515-
- Implement progress bar for downloads
516-
- Add DLL security mitigations
511+
- Created `crates/vite_installer/` with `[[bin]] name = "vp-setup"`
512+
- Implemented CLI argument parsing (clap) with env var merging
513+
- Implemented installation flow calling `vite_setup`
514+
- Implemented Windows PATH modification via raw Win32 FFI
515+
- Implemented interactive prompts with customization submenu
516+
- Implemented progress spinner for downloads
517+
- Added DLL security mitigations (build.rs linker flag + runtime `SetDefaultDllDirectories`)
517518

518-
### Phase 3: CI Integration
519+
### Phase 3: CI Integration (done)
519520

520-
- Add init binary build to release workflow
521-
- Add artifact upload and GitHub Release attachment
522-
- Add test jobs for `vp-setup.exe` across shell types
521+
- Added installer binary build to `build-upstream/action.yml` (Windows targets only)
522+
- Added artifact upload and GitHub Release attachment in `release.yml`
523+
- Added `test-vp-setup-exe` job to `test-standalone-install.yml` (cmd, pwsh, bash)
524+
- Updated release body with `vp-setup.exe` download mention
523525

524-
### Phase 4: Documentation & Distribution
526+
### Phase 4: Documentation & Distribution (future)
525527

526-
- Update installation docs
527-
- Host on `vite.plus/vp-setup.exe`
528-
- Update release body template with download link
528+
- Update installation docs on website
529+
- Host on `vite.plus/vp-setup.exe` with architecture auto-detection
530+
- Submit to winget, chocolatey, scoop
529531

530532
## Testing Strategy
531533

0 commit comments

Comments
 (0)