Skip to content

Commit e973ecc

Browse files
committed
docs(rfc): update shim implementation to use symlinks and vp env run wrappers
- Unix: Change from hardlinks to symlinks pointing to ../current/bin/vp - Windows: Change from node.exe copy + VITE_PLUS_SHIM_TOOL env var to .cmd wrappers calling `vp env run <tool>` - Add Unix-Specific Considerations section documenting argv[0] detection - Update Windows-Specific Considerations with new wrapper templates - Update directory structure to use current/bin/ subdirectory
1 parent eaaaf0e commit e973ecc

1 file changed

Lines changed: 110 additions & 56 deletions

File tree

rfcs/env-command.md

Lines changed: 110 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ This RFC proposes adding a `vp env` command that provides system-wide, IDE-safe
2323
A shim-based approach where:
2424

2525
- `VITE_PLUS_HOME/bin/` directory is added to PATH (system-level for IDE reliability)
26-
- Shims (`node`, `npm`, `npx`) are hardlinks/copies of the `vp` binary
26+
- Shims (`node`, `npm`, `npx`) are symlinks to the `vp` binary (Unix) or `.cmd` wrappers (Windows)
2727
- The `vp` CLI itself is also in `VITE_PLUS_HOME/bin/`, so users only need one PATH entry
2828
- The binary detects invocation via `argv[0]` and dispatches accordingly
2929
- Version resolution and installation leverage existing `vite_js_runtime` infrastructure
@@ -174,7 +174,7 @@ argv[0] = "npx" → Shim mode: resolve version, exec npx
174174
│ │ │
175175
│ ▼ │
176176
│ ┌──────────────────────────────┐ │
177-
│ │ ~/.vite-plus/bin/node │ ◄── Hardlink to vp binary (via PATH) │
177+
│ │ ~/.vite-plus/bin/node │ ◄── Symlink to vp binary (via PATH)
178178
│ │ (shim intercepts command) │ │
179179
│ └──────────────┬───────────────┘ │
180180
│ │ │
@@ -212,11 +212,11 @@ argv[0] = "npx" → Shim mode: resolve version, exec npx
212212
│ │
213213
│ ~/.vite-plus/ (VITE_PLUS_HOME) │
214214
│ ├── bin/ │
215-
│ │ ├── vp ────────────────────── Symlink to ../current/vp
215+
│ │ ├── vp ────────────────────── Symlink to ../current/bin/vp
216216
│ │ ├── node ──────────────────────┐ │
217-
│ │ ├── npm ──────────────────────┼──▶ Hardlinks to vp binary
217+
│ │ ├── npm ──────────────────────┼──▶ Symlinks to ../current/bin/vp
218218
│ │ └── npx ──────────────────────┘ │
219-
│ ├── current/vp The actual vp CLI binary │
219+
│ ├── current/bin/vp The actual vp CLI binary │
220220
│ ├── js_runtime/node/ Node.js installations │
221221
│ │ ├── 20.18.0/bin/node Installed Node.js versions │
222222
│ │ ├── 22.13.0/bin/node │
@@ -257,18 +257,19 @@ argv[0] = "npx" → Shim mode: resolve version, exec npx
257257
```
258258
VITE_PLUS_HOME/ # Default: ~/.vite-plus
259259
├── bin/
260-
│ ├── vp -> ../current/vp # Symlink to current vp binary (Unix)
261-
│ ├── node # Hardlink to vp binary (Unix)
262-
│ ├── npm # Hardlink to vp binary (Unix)
263-
│ ├── npx # Hardlink to vp binary (Unix)
264-
│ ├── tsc # Hardlink for global package binary (Unix)
265-
│ ├── vp.cmd # Wrapper script calling ..\current\vp.exe (Windows)
266-
│ ├── node.exe # Copy of current\vp.exe (Windows)
267-
│ ├── npm.cmd # Wrapper script (Windows)
268-
│ └── npx.cmd # Wrapper script (Windows)
260+
│ ├── vp -> ../current/bin/vp # Symlink to current vp binary (Unix)
261+
│ ├── node -> ../current/bin/vp # Symlink to vp binary (Unix)
262+
│ ├── npm -> ../current/bin/vp # Symlink to vp binary (Unix)
263+
│ ├── npx -> ../current/bin/vp # Symlink to vp binary (Unix)
264+
│ ├── tsc -> ../current/bin/vp # Symlink for global package (Unix)
265+
│ ├── vp.cmd # Wrapper calling ..\current\bin\vp.exe (Windows)
266+
│ ├── node.cmd # Wrapper calling vp env run node (Windows)
267+
│ ├── npm.cmd # Wrapper calling vp env run npm (Windows)
268+
│ └── npx.cmd # Wrapper calling vp env run npx (Windows)
269269
├── current/
270-
│ ├── vp # The actual vp CLI binary (Unix)
271-
│ └── vp.exe # The actual vp CLI binary (Windows)
270+
│ └── bin/
271+
│ ├── vp # The actual vp CLI binary (Unix)
272+
│ └── vp.exe # The actual vp CLI binary (Windows)
272273
├── js_runtime/
273274
│ └── node/
274275
│ ├── 20.18.0/ # Installed Node versions
@@ -301,7 +302,7 @@ VITE_PLUS_HOME/ # Default: ~/.vite-plus
301302
| Directory | Purpose |
302303
| ------------------ | ------------------------------------------------------------------ |
303304
| `bin/` | vp symlink and all shims (node, npm, npx, global package binaries) |
304-
| `current/` | The actual vp CLI binary (bin/vp symlinks here) |
305+
| `current/bin/` | The actual vp CLI binary (bin/ shims point here) |
305306
| `js_runtime/node/` | Installed Node.js versions |
306307
| `packages/` | Installed global packages with metadata |
307308
| `shared/` | NODE_PATH symlinks for package require() resolution |
@@ -605,27 +606,29 @@ fn execute_run_command() {
605606
- Consistent behavior across all tools
606607
- Already proven pattern (used by fnm, volta)
607608

608-
### 2. Hardlinks over Symlinks (Unix)
609+
### 2. Symlinks for Shims (Unix)
609610

610-
**Decision**: Use hardlinks for shims on Unix, with fallback to copy.
611+
**Decision**: Use symlinks for all shims on Unix, pointing to the vp binary.
611612

612613
**Rationale**:
613614

614-
- Hardlinks work across more filesystem types than symlinks
615-
- Symlinks can cause argv[0] to resolve to the target name
616-
- Hardlinks preserve the intended argv[0] value
617-
- Copy fallback for cross-filesystem scenarios
615+
- Symlinks preserve argv[0] - executing a symlink sets argv[0] to the symlink path, not the target
616+
- Proven pattern used by Volta successfully
617+
- Single binary to maintain - update `current/bin/vp` and all shims work
618+
- No binary accumulation issues (symlinks are just filesystem pointers)
619+
- Relative symlinks (e.g., `../current/bin/vp`) work within the same directory tree
618620

619-
### 3. Wrapper Scripts for Windows npm/npx
621+
### 3. Wrapper Scripts for Windows
620622

621-
**Decision**: Use `.cmd` wrapper scripts for npm/npx on Windows with `VITE_PLUS_SHIM_TOOL` environment variable.
623+
**Decision**: Use `.cmd` wrapper scripts on Windows that call `vp env run <tool>`.
622624

623625
**Rationale**:
624626

625627
- Windows PATH resolution prefers `.cmd` over `.exe` for extensionless commands
626-
- npm is typically invoked as `npm` not `npm.exe`
627-
- `.cmd` wrappers set `VITE_PLUS_SHIM_TOOL` env var and forward to `vp.exe`
628-
- More maintainable than multiple .exe copies - only one binary to update
628+
- Simple wrapper format: `vp env run npm %*` - no binary copies needed
629+
- Same pattern as Volta (`volta run <tool>`)
630+
- Single `vp.exe` binary to maintain in `current/bin/`
631+
- No `VITE_PLUS_SHIM_TOOL` env var complexity - dispatch via `vp env run` command
629632

630633
### 4. execve on Unix, spawn on Windows
631634

@@ -733,8 +736,8 @@ Recommended Fix:
733736

734737
The global CLI installation script (`packages/global/install.sh`) will be updated to:
735738

736-
1. Install the `vp` binary to `~/.vite-plus/current/vp`
737-
2. Create symlink `~/.vite-plus/bin/vp``../current/vp`
739+
1. Install the `vp` binary to `~/.vite-plus/current/bin/vp`
740+
2. Create symlink `~/.vite-plus/bin/vp``../current/bin/vp`
738741
3. Configure shell PATH to include `~/.vite-plus/bin`
739742
4. Setup Node.js version manager based on environment:
740743
- **CI environment**: Auto-enable (no prompt)
@@ -1499,61 +1502,112 @@ $ vp env --current --json
14991502
| `VITE_PLUS_TOOL_RECURSION` | **Internal**: Prevents shim recursion | unset |
15001503
| `VITE_PLUS_UNSAFE_GLOBAL` | Bypass global package interception | unset |
15011504

1505+
## Unix-Specific Considerations
1506+
1507+
### Shim Structure
1508+
1509+
```
1510+
VITE_PLUS_HOME/
1511+
├── bin/
1512+
│ ├── vp -> ../current/bin/vp # Symlink to actual binary
1513+
│ ├── node -> ../current/bin/vp # Symlink to same binary
1514+
│ ├── npm -> ../current/bin/vp # Symlink to same binary
1515+
│ ├── npx -> ../current/bin/vp # Symlink to same binary
1516+
│ └── tsc -> ../current/bin/vp # Symlink for global package
1517+
└── current/
1518+
└── bin/
1519+
└── vp # The actual vp CLI binary
1520+
```
1521+
1522+
### How argv[0] Detection Works
1523+
1524+
When a user runs `node`:
1525+
1. Shell finds `~/.vite-plus/bin/node` in PATH
1526+
2. This is a symlink to `../current/bin/vp`
1527+
3. Kernel resolves symlink and executes `vp` binary
1528+
4. `argv[0]` is set to the invoking path: `node` (or full path)
1529+
5. `vp` binary extracts tool name from `argv[0]` (gets "node")
1530+
6. Dispatches to shim logic for node
1531+
1532+
**Key Insight**: Symlinks preserve argv[0]. This is the same pattern Volta uses successfully.
1533+
1534+
### Symlink Creation
1535+
1536+
All shims use relative symlinks:
1537+
1538+
```bash
1539+
# Core tools
1540+
ln -sf ../current/bin/vp ~/.vite-plus/bin/node
1541+
ln -sf ../current/bin/vp ~/.vite-plus/bin/npm
1542+
ln -sf ../current/bin/vp ~/.vite-plus/bin/npx
1543+
1544+
# Global package binaries
1545+
ln -sf ../current/bin/vp ~/.vite-plus/bin/tsc
1546+
```
1547+
15021548
## Windows-Specific Considerations
15031549

15041550
### Shim Structure
15051551

15061552
```
15071553
VITE_PLUS_HOME\
15081554
├── bin\
1509-
│ ├── vp.cmd # Wrapper script calling ..\current\vp.exe
1510-
│ ├── node.exe # Copy of current\vp.exe
1511-
│ ├── npm.cmd # Wrapper script
1512-
│ └── npx.cmd # Wrapper script
1555+
│ ├── vp.cmd # Wrapper calling ..\current\bin\vp.exe
1556+
│ ├── node.cmd # Wrapper calling vp env run node
1557+
│ ├── npm.cmd # Wrapper calling vp env run npm
1558+
│ └── npx.cmd # Wrapper calling vp env run npx
15131559
└── current\
1514-
└── vp.exe # The actual vp CLI binary
1560+
└── bin\
1561+
└── vp.exe # The actual vp CLI binary
15151562
```
15161563

15171564
### Wrapper Script Template (vp.cmd)
15181565

15191566
```batch
15201567
@echo off
1521-
"%~dp0..\current\vp.exe" %*
1568+
"%~dp0..\current\bin\vp.exe" %*
15221569
exit /b %ERRORLEVEL%
15231570
```
15241571

1525-
The `vp.cmd` wrapper simply forwards all arguments to the actual `vp.exe` binary in the `current` directory.
1572+
The `vp.cmd` wrapper forwards all arguments to the actual `vp.exe` binary.
15261573

1527-
### Wrapper Script Template (npm.cmd)
1574+
### Wrapper Script Template (node.cmd, npm.cmd, npx.cmd)
1575+
1576+
```batch
1577+
@echo off
1578+
"%~dp0..\current\bin\vp.exe" env run node %*
1579+
exit /b %ERRORLEVEL%
1580+
```
15281581

1582+
For npm:
15291583
```batch
15301584
@echo off
1531-
setlocal
1532-
set "VITE_PLUS_SHIM_TOOL=npm"
1533-
"%~dp0node.exe" %*
1585+
"%~dp0..\current\bin\vp.exe" env run npm %*
15341586
exit /b %ERRORLEVEL%
15351587
```
15361588

1537-
The `.cmd` wrapper sets `VITE_PLUS_SHIM_TOOL` environment variable before calling `node.exe` (which is a copy of `vp.exe`). The Rust binary checks this env var first before falling back to argv[0] detection.
1589+
**How it works**:
1590+
1591+
1. User runs `npm install`
1592+
2. Windows finds `~/.vite-plus/bin/npm.cmd` in PATH
1593+
3. Wrapper calls `vp.exe env run npm install`
1594+
4. `vp env run` command handles version resolution and execution
15381595

15391596
**Benefits of this approach**:
15401597

1541-
- Single `vp.exe` binary to update in `current\` directory
1542-
- `node.exe` in `bin\` is a copy for shim detection via argv[0]
1543-
- `.cmd` wrappers are trivial text files
1544-
- Clear separation of concerns: `.cmd` sets context, binary does the work
1598+
- Single `vp.exe` binary to update in `current\bin\`
1599+
- All shims are trivial `.cmd` text files (no binary copies)
1600+
- Consistent with Volta's Windows approach
1601+
- Clear, readable wrapper scripts
15451602
15461603
### Windows Installation (install.ps1)
15471604
1548-
The Windows installer (`install.ps1`) follows the same flow:
1605+
The Windows installer (`install.ps1`) follows this flow:
15491606
1550-
1. Download and install `vp.exe` to `~/.vite-plus/current/`
1607+
1. Download and install `vp.exe` to `~/.vite-plus/current/bin/`
15511608
2. Create `~/.vite-plus/bin/vp.cmd` wrapper script
1552-
3. Configure User PATH to include `~/.vite-plus/bin`
1553-
4. Setup Node.js version manager based on environment:
1554-
- **CI environment**: Auto-enable (no prompt)
1555-
- **No system Node.js**: Auto-enable (no prompt)
1556-
- **Interactive with system Node.js**: Prompt user
1609+
3. Create shim wrappers: `node.cmd`, `npm.cmd`, `npx.cmd`
1610+
4. Configure User PATH to include `~/.vite-plus/bin`
15571611
15581612
## Testing Strategy
15591613
@@ -1605,9 +1659,9 @@ env-doctor/
16051659
### Phase 1: Core Infrastructure (P0)
16061660
16071661
1. Add `vp env` command structure to CLI
1608-
2. Implement argv[0] detection in main.rs (also check `VITE_PLUS_SHIM_TOOL` env var for Windows)
1662+
2. Implement argv[0] detection in main.rs
16091663
3. Implement shim dispatch logic for `node`
1610-
4. Implement `vp env setup` (Unix hardlinks, Windows .exe copy + .cmd wrappers)
1664+
4. Implement `vp env setup` (Unix symlinks, Windows .cmd wrappers)
16111665
5. Implement `vp env doctor` basic diagnostics
16121666
6. Add resolution cache (persists across upgrades with version field)
16131667
7. Implement `vp env default [version]` to set/show global default Node.js version
@@ -1661,7 +1715,7 @@ The following decisions have been made:
16611715
16621716
1. **VITE_PLUS_HOME Default Location**: `~/.vite-plus` - Simple, memorable path that's easy for users to find and configure.
16631717
1664-
2. **Windows Wrapper Strategy**: `.cmd` wrappers with `VITE_PLUS_SHIM_TOOL` environment variable - More maintainable, only one binary to update.
1718+
2. **Windows Wrapper Strategy**: `.cmd` wrappers that call `vp env run <tool>` - Consistent with Volta, no binary copies needed.
16651719
16661720
3. **Corepack Handling**: Not included - vite-plus has integrated package manager functionality, making corepack shims unnecessary.
16671721

0 commit comments

Comments
 (0)