Skip to content

Commit ebce813

Browse files
committed
feat: export download package manager helper
1 parent 1db7b58 commit ebce813

15 files changed

Lines changed: 387 additions & 50 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/vite_install/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ pub mod package_manager;
44
mod request;
55
mod shim;
66

7-
pub use package_manager::{PackageManager, PackageManagerType};
7+
pub use package_manager::{
8+
PackageManager, PackageManagerType, download_package_manager,
9+
get_package_manager_type_and_version,
10+
};

crates/vite_install/src/package_manager.rs

Lines changed: 40 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -93,45 +93,23 @@ impl PackageManagerBuilder {
9393
/// Detect the package manager from the current working directory.
9494
pub async fn build(self) -> Result<PackageManager, Error> {
9595
let workspace_root = find_workspace_root(&self.cwd)?;
96-
let (package_manager_type, mut version, mut hash) =
96+
let (package_manager_type, version_or_latest, hash) =
9797
get_package_manager_type_and_version(&workspace_root, self.client_override)?;
9898

99-
let mut package_name = package_manager_type.to_string();
100-
let mut should_update_package_manager_field = false;
101-
102-
if version == "latest" {
103-
version = get_latest_version(package_manager_type).await?;
104-
should_update_package_manager_field = true;
105-
hash = None; // Reset hash when fetching latest since hash is version-specific
106-
}
107-
108-
// handle yarn >= 2.0.0 to use `@yarnpkg/cli-dist` as package name
109-
// @see https://github.com/nodejs/corepack/blob/main/config.json#L135
110-
if matches!(package_manager_type, PackageManagerType::Yarn) {
111-
let version_req = VersionReq::parse(">=2.0.0")?;
112-
if version_req.matches(&Version::parse(&version)?) {
113-
package_name = "@yarnpkg/cli-dist".to_string();
114-
}
115-
}
116-
11799
// only download the package manager if it's not already downloaded
118-
let install_dir = download_package_manager(
119-
package_manager_type,
120-
&package_name,
121-
&version,
122-
hash.as_deref(),
123-
)
124-
.await?;
100+
let (install_dir, package_name, version) =
101+
download_package_manager(package_manager_type, &version_or_latest, hash.as_deref())
102+
.await?;
125103

126-
if should_update_package_manager_field {
104+
if version_or_latest != version {
127105
// auto set `packageManager` field in package.json
128106
let package_json_path = workspace_root.path.join("package.json");
129107
set_package_manager_field(&package_json_path, package_manager_type, &version).await?;
130108
}
131109

132110
Ok(PackageManager {
133111
client: package_manager_type,
134-
package_name: package_name.into(),
112+
package_name,
135113
version,
136114
hash,
137115
bin_name: package_manager_type.to_string().into(),
@@ -201,7 +179,7 @@ impl PackageManager {
201179
}
202180

203181
/// Get the package manager name, version and optional hash from the workspace root.
204-
fn get_package_manager_type_and_version(
182+
pub fn get_package_manager_type_and_version(
205183
workspace_root: &WorkspaceRoot,
206184
default: Option<PackageManagerType>,
207185
) -> Result<(PackageManagerType, Str, Option<Str>), Error> {
@@ -322,17 +300,32 @@ async fn get_latest_version(package_manager_type: PackageManagerType) -> Result<
322300

323301
/// Download the package manager and extract it to the cache directory.
324302
/// Return the install directory, e.g. $`CACHE_DIR/vite/package_manager/pnpm/10.0.0/pnpm`
325-
async fn download_package_manager(
303+
pub async fn download_package_manager(
326304
package_manager_type: PackageManagerType,
327-
package_name: &str,
328-
version: &str,
305+
version_or_latest: &str,
329306
expected_hash: Option<&str>,
330-
) -> Result<AbsolutePathBuf, Error> {
331-
let tgz_url = get_npm_package_tgz_url(package_name, version);
307+
) -> Result<(AbsolutePathBuf, Str, Str), Error> {
308+
let version: Str = if version_or_latest == "latest" {
309+
get_latest_version(package_manager_type).await?
310+
} else {
311+
version_or_latest.into()
312+
};
313+
314+
let mut package_name: Str = package_manager_type.to_string().into();
315+
// handle yarn >= 2.0.0 to use `@yarnpkg/cli-dist` as package name
316+
// @see https://github.com/nodejs/corepack/blob/main/config.json#L135
317+
if matches!(package_manager_type, PackageManagerType::Yarn) {
318+
let version_req = VersionReq::parse(">=2.0.0")?;
319+
if version_req.matches(&Version::parse(&version)?) {
320+
package_name = "@yarnpkg/cli-dist".into();
321+
}
322+
}
323+
324+
let tgz_url = get_npm_package_tgz_url(&package_name, &version);
332325
let cache_dir = get_cache_dir()?;
333326
let bin_name = package_manager_type.to_string();
334327
// $CACHE_DIR/vite/package_manager/pnpm/10.0.0
335-
let target_dir = cache_dir.join("package_manager").join(&bin_name).join(version);
328+
let target_dir = cache_dir.join("package_manager").join(&bin_name).join(&version);
336329
let install_dir = target_dir.join(&bin_name);
337330

338331
// If all shims are already exists, return the target directory
@@ -343,7 +336,7 @@ async fn download_package_manager(
343336
&& is_exists_file(bin_file.with_extension("cmd"))?
344337
&& is_exists_file(bin_file.with_extension("ps1"))?
345338
{
346-
return Ok(install_dir);
339+
return Ok((install_dir, package_name, version));
347340
}
348341

349342
// $CACHE_DIR/vite/package_manager/pnpm/{tmp_name}
@@ -361,7 +354,7 @@ async fn download_package_manager(
361354
{
362355
Error::PackageManagerVersionNotFound {
363356
name: package_manager_type.to_string().into(),
364-
version: version.into(),
357+
version: version.clone(),
365358
url: tgz_url.into(),
366359
}
367360
} else {
@@ -389,7 +382,7 @@ async fn download_package_manager(
389382
// the installation while we were downloading
390383
if is_exists_file(&bin_file)? {
391384
tracing::debug!("bin_file already exists after lock acquisition, skip rename");
392-
return Ok(install_dir);
385+
return Ok((install_dir, package_name, version));
393386
}
394387

395388
// rename $target_dir_tmp to $target_dir
@@ -401,7 +394,7 @@ async fn download_package_manager(
401394
tracing::debug!("Create shim files for {}", bin_name);
402395
create_shim_files(package_manager_type, &bin_prefix).await?;
403396

404-
Ok(install_dir)
397+
Ok((install_dir, package_name, version))
405398
}
406399

407400
/// Remove the directory and all its contents.
@@ -1449,24 +1442,23 @@ mod tests {
14491442

14501443
#[tokio::test]
14511444
async fn test_download_package_manager() {
1452-
let result =
1453-
download_package_manager(PackageManagerType::Yarn, "@yarnpkg/cli-dist", "4.9.2", None)
1454-
.await;
1445+
let result = download_package_manager(PackageManagerType::Yarn, "4.9.2", None).await;
14551446
assert!(result.is_ok());
1456-
let target_dir = result.unwrap();
1447+
let (target_dir, package_name, version) = result.unwrap();
14571448
println!("result: {target_dir:?}");
14581449
assert!(is_exists_file(target_dir.join("bin/yarn")).unwrap());
14591450
assert!(is_exists_file(target_dir.join("bin/yarn.cmd")).unwrap());
1451+
assert_eq!(package_name, "@yarnpkg/cli-dist");
1452+
assert_eq!(version, "4.9.2");
14601453

14611454
// again should skip download
1462-
let result =
1463-
download_package_manager(PackageManagerType::Yarn, "@yarnpkg/cli-dist", "4.9.2", None)
1464-
.await;
1455+
let result = download_package_manager(PackageManagerType::Yarn, "4.9.2", None).await;
14651456
assert!(result.is_ok());
1466-
let target_dir = result.unwrap();
1457+
let (target_dir, package_name, version) = result.unwrap();
14671458
assert!(is_exists_file(target_dir.join("bin/yarn")).unwrap());
14681459
assert!(is_exists_file(target_dir.join("bin/yarn.cmd")).unwrap());
1469-
1460+
assert_eq!(package_name, "@yarnpkg/cli-dist");
1461+
assert_eq!(version, "4.9.2");
14701462
remove_dir_all_force(target_dir).await.unwrap();
14711463
}
14721464

packages/cli/binding/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ vite_install = { workspace = true }
2525
vite_path = { workspace = true }
2626
vite_str = { workspace = true }
2727
vite_task = { workspace = true }
28+
vite_workspace = { workspace = true }
2829

2930
[build-dependencies]
3031
napi-build = { workspace = true }

packages/cli/binding/index.d.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,89 @@ export interface CliOptions {
2828
resolveUniversalViteConfig: ((err: Error | null, arg: string) => Promise<string>)
2929
}
3030

31+
/**
32+
* Detect the workspace root and package manager type and version
33+
*
34+
* ## Parameters
35+
*
36+
* - `cwd`: The current working directory to detect the workspace root
37+
*
38+
* ## Returns
39+
*
40+
* Returns a `DetectWorkspaceResult` containing:
41+
* - The name of the package manager
42+
* - The version of the package manager
43+
* - Whether the workspace is a monorepo
44+
* - The workspace root, where the package.json file is located.
45+
*
46+
* ## Example
47+
*
48+
* ```javascript
49+
* const result = await detectWorkspace("/path/to/workspace");
50+
* console.log(`Package manager name: ${result.package_manager_name}`);
51+
* console.log(`Package manager version: ${result.package_manager_version}`);
52+
* console.log(`Is monorepo: ${result.is_monorepo}`);
53+
* console.log(`Workspace root: ${result.root}`);
54+
* ```
55+
*/
56+
export declare function detectWorkspace(cwd: string): Promise<DetectWorkspaceResult>
57+
58+
export interface DetectWorkspaceResult {
59+
packageManagerName?: string
60+
packageManagerVersion?: string
61+
isMonorepo: boolean
62+
root?: string
63+
}
64+
65+
/**
66+
* Download a package manager
67+
*
68+
* ## Parameters
69+
*
70+
* - `options`: Configuration for the package manager to download, including:
71+
* - `name`: The name of the package manager
72+
* - `version`: The version of the package manager
73+
* - `expected_hash`: The expected hash of the package manager
74+
*
75+
* ## Returns
76+
*
77+
* Returns a `DownloadPackageManagerResult` containing:
78+
* - The name of the package manager
79+
* - The install directory of the package manager
80+
* - The binary prefix of the package manager
81+
* - The package name of the package manager
82+
* - The version of the package manager
83+
*
84+
* ## Example
85+
*
86+
* ```javascript
87+
* const result = await downloadPackageManager({
88+
* name: "pnpm",
89+
* version: "latest",
90+
* });
91+
* console.log(`Package manager name: ${result.name}`);
92+
* console.log(`Package manager install directory: ${result.install_dir}`);
93+
* console.log(`Package manager binary prefix: ${result.bin_prefix}`);
94+
* console.log(`Package manager package name: ${result.package_name}`);
95+
* console.log(`Package manager version: ${result.version}`);
96+
* ```
97+
*/
98+
export declare function downloadPackageManager(options: DownloadPackageManagerOptions): Promise<DownloadPackageManagerResult>
99+
100+
export interface DownloadPackageManagerOptions {
101+
name: string
102+
version: string
103+
expectedHash?: string
104+
}
105+
106+
export interface DownloadPackageManagerResult {
107+
name: string
108+
installDir: string
109+
binPrefix: string
110+
packageName: string
111+
version: string
112+
}
113+
31114
/**
32115
* Result returned by JavaScript resolver functions.
33116
*

packages/cli/binding/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,8 @@ if (!nativeBinding) {
575575
throw new Error(`Failed to load native binding`)
576576
}
577577

578-
const { run, runCommand } = nativeBinding
578+
const { detectWorkspace, downloadPackageManager, run, runCommand } = nativeBinding
579+
export { detectWorkspace }
580+
export { downloadPackageManager }
579581
export { run }
580582
export { runCommand }

packages/cli/binding/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
1616
mod cli;
1717
mod commands;
18+
mod package_manager;
1819
mod utils;
1920

2021
use std::{collections::HashMap, sync::Arc};
@@ -28,6 +29,7 @@ use vite_path::current_dir;
2829
use vite_task::ResolveCommandResult;
2930

3031
use crate::cli::{Args, CliOptions as ViteTaskCliOptions, Commands};
32+
pub use crate::package_manager::{detect_workspace, download_package_manager};
3133

3234
/// Module initialization - sets up tracing for debugging
3335
#[napi_derive::module_init]

0 commit comments

Comments
 (0)