Skip to content

Commit f3bd656

Browse files
committed
refactor: restructure VERSION_DIR to install vite-plus as npm dependency
Instead of extracting vite-plus internals (dist/, binding/, templates/, rules/, package.json) directly into ~/.vite-plus/VERSION/, generate a wrapper package.json that declares vite-plus as a dependency and let npm install it into node_modules/. This decouples the vp binary from vite-plus's internal file layout. New structure: VERSION_DIR/{bin/vp, package.json, node_modules/vite-plus/} - Update js_executor.rs to resolve scripts from node_modules/vite-plus/dist - Remove extract_main_package, strip_dev_dependencies, MAIN_PACKAGE_ENTRIES - Add generate_wrapper_package_json to upgrade/install.rs - Remove main tarball download from upgrade flow (npm handles it) - Remove .node file extraction (npm installs via optionalDeps) - Simplify install.sh and install.ps1 to only extract binary + generate wrapper - Rewrite setupLocalDevDeps to symlink packages/cli as node_modules/vite-plus - Rewrite installCiDeps to generate wrapper with file: protocol refs - Update CI get_cli_version to read from node_modules/vite-plus/package.json
1 parent 36ee7a9 commit f3bd656

8 files changed

Lines changed: 140 additions & 334 deletions

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ jobs:
361361
run: |
362362
# Helper to read the installed CLI version from package.json
363363
get_cli_version() {
364-
node -p "require(require('path').resolve(process.env.USERPROFILE || process.env.HOME, '.vite-plus', 'current', 'package.json')).version"
364+
node -p "require(require('path').resolve(process.env.USERPROFILE || process.env.HOME, '.vite-plus', 'current', 'node_modules', 'vite-plus', 'package.json')).version"
365365
}
366366
367367
# Save initial (dev build) version
@@ -407,7 +407,7 @@ jobs:
407407
408408
# Helper to read the installed CLI version from package.json
409409
function Get-CliVersion {
410-
node -p "require(require('path').resolve(process.env.USERPROFILE, '.vite-plus', 'current', 'package.json')).version"
410+
node -p "require(require('path').resolve(process.env.USERPROFILE, '.vite-plus', 'current', 'node_modules', 'vite-plus', 'package.json')).version"
411411
}
412412
413413
# Save initial (dev build) version
@@ -450,7 +450,7 @@ jobs:
450450
shell: cmd
451451
run: |
452452
REM Save initial (dev build) version
453-
for /f "usebackq delims=" %%v in (`node -p "require(require('path').resolve(process.env.USERPROFILE, '.vite-plus', 'current', 'package.json')).version"`) do set INITIAL_VERSION=%%v
453+
for /f "usebackq delims=" %%v in (`node -p "require(require('path').resolve(process.env.USERPROFILE, '.vite-plus', 'current', 'node_modules', 'vite-plus', 'package.json')).version"`) do set INITIAL_VERSION=%%v
454454
echo Initial version: %INITIAL_VERSION%
455455
456456
REM --check queries npm registry and prints update status
@@ -464,7 +464,7 @@ jobs:
464464
dir "%USERPROFILE%\.vite-plus\"
465465
466466
REM Verify version changed after update
467-
for /f "usebackq delims=" %%v in (`node -p "require(require('path').resolve(process.env.USERPROFILE, '.vite-plus', 'current', 'package.json')).version"`) do set UPDATED_VERSION=%%v
467+
for /f "usebackq delims=" %%v in (`node -p "require(require('path').resolve(process.env.USERPROFILE, '.vite-plus', 'current', 'node_modules', 'vite-plus', 'package.json')).version"`) do set UPDATED_VERSION=%%v
468468
echo Updated version: %UPDATED_VERSION%
469469
if "%UPDATED_VERSION%"=="%INITIAL_VERSION%" (
470470
echo Error: version should have changed after upgrade, still %INITIAL_VERSION%
@@ -477,7 +477,7 @@ jobs:
477477
vp env doctor
478478
479479
REM Verify version restored after rollback
480-
for /f "usebackq delims=" %%v in (`node -p "require(require('path').resolve(process.env.USERPROFILE, '.vite-plus', 'current', 'package.json')).version"`) do set ROLLBACK_VERSION=%%v
480+
for /f "usebackq delims=" %%v in (`node -p "require(require('path').resolve(process.env.USERPROFILE, '.vite-plus', 'current', 'node_modules', 'vite-plus', 'package.json')).version"`) do set ROLLBACK_VERSION=%%v
481481
echo Rollback version: %ROLLBACK_VERSION%
482482
if not "%ROLLBACK_VERSION%"=="%INITIAL_VERSION%" (
483483
echo Error: version should have been restored after rollback, expected %INITIAL_VERSION%, got %ROLLBACK_VERSION%

crates/vite_global_cli/src/commands/upgrade/install.rs

Lines changed: 18 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,22 @@ fn is_safe_tar_path(path: &Path) -> bool {
2626
&& !path.components().any(|c| matches!(c, std::path::Component::ParentDir))
2727
}
2828

29-
/// Files/directories to extract from the main package tarball.
30-
const MAIN_PACKAGE_ENTRIES: &[&str] =
31-
&["binding/", "dist/", "templates/", "rules/", "AGENTS.md", "package.json"];
32-
33-
/// Extract the platform-specific package (binary + .node files).
29+
/// Extract the platform-specific package (binary only).
3430
///
3531
/// From the platform tarball, extracts:
3632
/// - The `vp` binary → `{version_dir}/bin/vp`
37-
/// - Any `.node` files → `{version_dir}/binding/`
33+
///
34+
/// `.node` files are no longer extracted here — npm installs them
35+
/// via the platform package's optionalDependencies.
3836
pub async fn extract_platform_package(
3937
tgz_data: &[u8],
4038
version_dir: &AbsolutePath,
4139
) -> Result<(), Error> {
4240
let bin_dir = version_dir.join("bin");
43-
let binding_dir = version_dir.join("binding");
4441
tokio::fs::create_dir_all(&bin_dir).await?;
45-
tokio::fs::create_dir_all(&binding_dir).await?;
4642

4743
let data = tgz_data.to_vec();
4844
let bin_dir_clone = bin_dir.clone();
49-
let binding_dir_clone = binding_dir.clone();
5045

5146
tokio::task::spawn_blocking(move || {
5247
let cursor = Cursor::new(data);
@@ -80,12 +75,6 @@ pub async fn extract_platform_package(
8075
use std::os::unix::fs::PermissionsExt;
8176
std::fs::set_permissions(&target, std::fs::Permissions::from_mode(0o755))?;
8277
}
83-
} else if file_name.ends_with(".node") {
84-
// .node NAPI files go to binding/ (alongside index.cjs loader)
85-
let target = binding_dir_clone.join(file_name);
86-
let mut buf = Vec::new();
87-
entry.read_to_end(&mut buf)?;
88-
std::fs::write(&target, &buf)?;
8978
}
9079
}
9180

@@ -97,92 +86,24 @@ pub async fn extract_platform_package(
9786
Ok(())
9887
}
9988

100-
/// Extract the main package (JS bundles, templates, rules, package.json).
89+
/// Generate a wrapper `package.json` that declares `vite-plus` as a dependency.
10190
///
102-
/// Copies specific directories and files from the tarball to the version directory.
103-
pub async fn extract_main_package(
104-
tgz_data: &[u8],
91+
/// This replaces the old approach of extracting the main package tarball.
92+
/// npm will install `vite-plus` and all its transitive deps via `vp install`.
93+
pub async fn generate_wrapper_package_json(
10594
version_dir: &AbsolutePath,
95+
version: &str,
10696
) -> Result<(), Error> {
107-
let version_dir_owned = version_dir.as_path().to_path_buf();
108-
let data = tgz_data.to_vec();
109-
110-
tokio::task::spawn_blocking(move || {
111-
let cursor = Cursor::new(data);
112-
let decoder = GzDecoder::new(cursor);
113-
let mut archive = Archive::new(decoder);
114-
115-
for entry_result in archive.entries()? {
116-
let mut entry = entry_result?;
117-
let path = entry.path()?.to_path_buf();
118-
119-
// Strip the leading `package/` prefix
120-
let relative = path.strip_prefix("package").unwrap_or(&path).to_path_buf();
121-
122-
// Reject paths with traversal components (security)
123-
if !is_safe_tar_path(&relative) {
124-
continue;
125-
}
126-
127-
let relative_str = relative.to_string_lossy();
128-
129-
// Check if this entry matches our allowed list
130-
let should_extract = MAIN_PACKAGE_ENTRIES.iter().any(|allowed| {
131-
if allowed.ends_with('/') {
132-
// Directory prefix match
133-
relative_str.starts_with(allowed)
134-
} else {
135-
// Exact file match
136-
relative_str == *allowed
137-
}
138-
});
139-
140-
if !should_extract {
141-
continue;
142-
}
143-
144-
let target = version_dir_owned.join(&*relative_str);
145-
146-
if entry.header().entry_type().is_dir() {
147-
std::fs::create_dir_all(&target)?;
148-
} else {
149-
// Ensure parent directory exists
150-
if let Some(parent) = target.parent() {
151-
std::fs::create_dir_all(parent)?;
152-
}
153-
let mut buf = Vec::new();
154-
entry.read_to_end(&mut buf)?;
155-
std::fs::write(&target, &buf)?;
156-
}
97+
let json = serde_json::json!({
98+
"name": "vp-global",
99+
"version": version,
100+
"private": true,
101+
"dependencies": {
102+
"vite-plus": version
157103
}
158-
159-
Ok::<(), Error>(())
160-
})
161-
.await
162-
.map_err(|e| Error::Upgrade(format!("Task join error: {e}").into()))??;
163-
164-
Ok(())
165-
}
166-
167-
/// Strip devDependencies and optionalDependencies from package.json.
168-
pub async fn strip_dev_dependencies(version_dir: &AbsolutePath) -> Result<(), Error> {
169-
let package_json_path = version_dir.join("package.json");
170-
171-
if !tokio::fs::try_exists(&package_json_path).await.unwrap_or(false) {
172-
return Ok(());
173-
}
174-
175-
let content = tokio::fs::read_to_string(&package_json_path).await?;
176-
let mut json: serde_json::Value = serde_json::from_str(&content)?;
177-
178-
if let Some(obj) = json.as_object_mut() {
179-
obj.remove("devDependencies");
180-
obj.remove("optionalDependencies");
181-
}
182-
183-
let updated = serde_json::to_string_pretty(&json)?;
184-
tokio::fs::write(&package_json_path, format!("{updated}\n")).await?;
185-
104+
});
105+
let content = serde_json::to_string_pretty(&json)? + "\n";
106+
tokio::fs::write(version_dir.join("package.json"), content).await?;
186107
Ok(())
187108
}
188109

crates/vite_global_cli/src/commands/upgrade/mod.rs

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -92,26 +92,16 @@ pub async fn execute(options: UpgradeOptions) -> Result<ExitStatus, Error> {
9292
eprintln!("info: downloading vite-plus@{} for {}...", resolved.version, platform_suffix);
9393
}
9494

95-
// Step 6: Download both tarballs
95+
// Step 6: Download platform tarball (main package is installed via npm)
9696
let client = HttpClient::new();
9797

98-
let (platform_data, main_data) = tokio::try_join!(
99-
async {
100-
client.get_bytes(&resolved.platform_tarball_url).await.map_err(|e| {
101-
Error::Upgrade(format!("Failed to download platform package: {e}").into())
102-
})
103-
},
104-
async {
105-
client
106-
.get_bytes(&resolved.main_tarball_url)
107-
.await
108-
.map_err(|e| Error::Upgrade(format!("Failed to download main package: {e}").into()))
109-
},
110-
)?;
98+
let platform_data = client
99+
.get_bytes(&resolved.platform_tarball_url)
100+
.await
101+
.map_err(|e| Error::Upgrade(format!("Failed to download platform package: {e}").into()))?;
111102

112103
// Step 7: Verify integrity
113104
integrity::verify_integrity(&platform_data, &resolved.platform_integrity)?;
114-
integrity::verify_integrity(&main_data, &resolved.main_integrity)?;
115105

116106
if !options.silent {
117107
eprintln!("info: installing...");
@@ -121,10 +111,9 @@ pub async fn execute(options: UpgradeOptions) -> Result<ExitStatus, Error> {
121111
let version_dir = install_dir.join(&resolved.version);
122112
tokio::fs::create_dir_all(&version_dir).await?;
123113

124-
// Step 9: Extract platform package (binary + .node files)
114+
// Step 9: Extract platform binary and install via npm
125115
let result = install_platform_and_main(
126116
&platform_data,
127-
&main_data,
128117
&version_dir,
129118
&install_dir,
130119
&resolved.version,
@@ -146,38 +135,28 @@ pub async fn execute(options: UpgradeOptions) -> Result<ExitStatus, Error> {
146135
#[allow(clippy::print_stdout, clippy::print_stderr)]
147136
async fn install_platform_and_main(
148137
platform_data: &[u8],
149-
main_data: &[u8],
150138
version_dir: &AbsolutePathBuf,
151139
install_dir: &AbsolutePathBuf,
152140
new_version: &str,
153141
current_version: &str,
154142
silent: bool,
155143
) -> Result<ExitStatus, Error> {
156-
// Extract platform package
144+
// Extract platform package (binary only; .node files installed via npm optionalDeps)
157145
install::extract_platform_package(platform_data, version_dir).await?;
158146

159-
// Extract main package
160-
install::extract_main_package(main_data, version_dir).await?;
161-
162-
// Verify critical files were extracted
147+
// Verify binary was extracted
163148
let binary_name = if cfg!(windows) { "vp.exe" } else { "vp" };
164149
let binary_path = version_dir.join("bin").join(binary_name);
165150
if !tokio::fs::try_exists(&binary_path).await.unwrap_or(false) {
166151
return Err(Error::Upgrade(
167152
"Binary not found after extraction. The download may be corrupted.".into(),
168153
));
169154
}
170-
let package_json_path = version_dir.join("package.json");
171-
if !tokio::fs::try_exists(&package_json_path).await.unwrap_or(false) {
172-
return Err(Error::Upgrade(
173-
"package.json not found after extraction. The download may be corrupted.".into(),
174-
));
175-
}
176155

177-
// Strip dev dependencies from package.json
178-
install::strip_dev_dependencies(version_dir).await?;
156+
// Generate wrapper package.json that declares vite-plus as a dependency
157+
install::generate_wrapper_package_json(version_dir, new_version).await?;
179158

180-
// Install production dependencies
159+
// Install production dependencies (npm installs vite-plus + all transitive deps)
181160
install::install_production_deps(version_dir).await?;
182161

183162
// Save previous version for rollback

crates/vite_global_cli/src/commands/upgrade/registry.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,10 @@ pub struct DistInfo {
2626
pub integrity: String,
2727
}
2828

29-
/// Resolved version info with URLs and integrity for both packages.
29+
/// Resolved version info with URLs and integrity for the platform package.
3030
#[derive(Debug)]
3131
pub struct ResolvedVersion {
3232
pub version: String,
33-
pub main_tarball_url: String,
34-
pub main_integrity: String,
3533
pub platform_tarball_url: String,
3634
pub platform_integrity: String,
3735
}
@@ -42,7 +40,7 @@ const PLATFORM_PACKAGE_SCOPE: &str = "@voidzero-dev";
4240
/// Resolve a version from the npm registry.
4341
///
4442
/// Makes two HTTP calls:
45-
/// 1. Main package metadata to get version, tarball URL, integrity, and optional deps
43+
/// 1. Main package metadata to get version and optional deps (to find platform package)
4644
/// 2. Platform package metadata to get platform-specific tarball URL and integrity
4745
pub async fn resolve_version(
4846
version_or_tag: &str,
@@ -90,8 +88,6 @@ pub async fn resolve_version(
9088

9189
Ok(ResolvedVersion {
9290
version: main_meta.version,
93-
main_tarball_url: main_meta.dist.tarball,
94-
main_integrity: main_meta.dist.integrity,
9591
platform_tarball_url: platform_meta.dist.tarball,
9692
platform_integrity: platform_meta.dist.integrity,
9793
})

crates/vite_global_cli/src/js_executor.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,16 @@ impl JsExecutor {
5555
}
5656

5757
// 3. Auto-detect from binary location
58-
// JS scripts are at ../dist relative to the binary directory
59-
// e.g., ~/.vite-plus/<version>/bin/vp -> ~/.vite-plus/<version>/dist/
58+
// JS scripts are at ../node_modules/vite-plus/dist relative to the binary directory
59+
// e.g., ~/.vite-plus/<version>/bin/vp -> ~/.vite-plus/<version>/node_modules/vite-plus/dist/
6060
let exe_path = std::env::current_exe().map_err(|_| Error::JsScriptsDirNotFound)?;
6161
// Resolve symlinks to get the real binary path (Unix only)
6262
// Skip on Windows to avoid path resolution issues
6363
#[cfg(unix)]
6464
let exe_path = std::fs::canonicalize(&exe_path).map_err(|_| Error::JsScriptsDirNotFound)?;
6565
let bin_dir = exe_path.parent().ok_or(Error::JsScriptsDirNotFound)?;
66-
let package_dir = bin_dir.parent().ok_or(Error::JsScriptsDirNotFound)?;
67-
let scripts_dir = package_dir.join("dist");
66+
let version_dir = bin_dir.parent().ok_or(Error::JsScriptsDirNotFound)?;
67+
let scripts_dir = version_dir.join("node_modules").join("vite-plus").join("dist");
6868

6969
AbsolutePathBuf::new(scripts_dir).ok_or(Error::JsScriptsDirNotFound)
7070
}

0 commit comments

Comments
 (0)