Skip to content

Commit 37853c1

Browse files
committed
feat: export download package manager helper
1 parent 4fd6e56 commit 37853c1

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 }
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { tmpdir } from 'node:os';
2+
import path from 'node:path';
3+
4+
import { expect, test } from 'vitest';
5+
6+
import { detectWorkspace } from '../index.js';
7+
8+
const fixtures = path.join(import.meta.dirname, 'fixtures');
9+
10+
test('should detect pnpm monorepo workspace successfully', async () => {
11+
const cwd = path.join(fixtures, 'pnpm-monorepo');
12+
const result = await detectWorkspace(cwd);
13+
expect(result.packageManagerName).toBe('pnpm');
14+
expect(result.packageManagerVersion).toBe('10.19.0');
15+
expect(result.isMonorepo).toBe(true);
16+
expect(result.root).toBe(cwd);
17+
18+
// detect from sub directory
19+
const subCwd = path.join(cwd, 'packages', 'sub-package');
20+
const subResult = await detectWorkspace(subCwd);
21+
expect(subResult.packageManagerName).toBe('pnpm');
22+
expect(subResult.packageManagerVersion).toBe('10.19.0');
23+
expect(subResult.isMonorepo).toBe(true);
24+
expect(subResult.root).toBe(cwd);
25+
});
26+
27+
test('should detect npm monorepo workspace successfully', async () => {
28+
const cwd = path.join(fixtures, 'npm-monorepo');
29+
const result = await detectWorkspace(cwd);
30+
expect(result.packageManagerName).toBe('npm');
31+
expect(result.packageManagerVersion).toBe('10.19.0');
32+
expect(result.isMonorepo).toBe(true);
33+
expect(result.root).toBe(cwd);
34+
});
35+
36+
// FIXME: currently it will always find vite-plus, there is a problem here
37+
test.skip('should detect npm project successfully', async () => {
38+
const cwd = path.join(fixtures, 'npm-project');
39+
const result = await detectWorkspace(cwd);
40+
expect(result.packageManagerName).toBe('npm');
41+
expect(result.packageManagerVersion).toBe('10.19.0');
42+
expect(result.isMonorepo).toBe(false);
43+
expect(result.root).toBe(cwd);
44+
});
45+
46+
test('should detect workspace failed with not exists directory', async () => {
47+
const result = await detectWorkspace(path.join(tmpdir(), 'not-exists'));
48+
expect(result.packageManagerName).toBeUndefined();
49+
expect(result.packageManagerVersion).toBeUndefined();
50+
expect(result.isMonorepo).toBe(false);
51+
expect(result.root).toBeUndefined();
52+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { expect, test } from 'vitest';
2+
3+
import { downloadPackageManager } from '../index.js';
4+
5+
test('should download package manager successfully', async () => {
6+
const result = await downloadPackageManager({
7+
name: 'pnpm',
8+
version: 'latest',
9+
});
10+
expect(result.name).toBe('pnpm');
11+
expect(result.packageName).toBe('pnpm');
12+
expect(result.version).toMatch(/^\d+\.\d+\.\d+$/);
13+
expect(result.installDir).toBeTruthy();
14+
expect(result.binPrefix).toMatch(/bin$/);
15+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "npm-monorepo",
3+
"version": "1.0.0",
4+
"packageManager": "npm@10.19.0",
5+
"workspaces": [
6+
"packages/*"
7+
]
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "npm-project",
3+
"version": "1.0.0",
4+
"packageManager": "npm@10.19.0"
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "pnpm-monorepo",
3+
"version": "1.0.0",
4+
"packageManager": "pnpm@10.19.0"
5+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "pnpm-monorepo-foo"
3+
}

0 commit comments

Comments
 (0)