Adds Windows support#322
Conversation
✅ Deploy Preview for yarn-v6 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Windows zip keeps nested paths
- Modified Windows archive step to copy files to a temporary directory first, then archive from there to create a flat zip structure matching Linux/macOS builds.
Or push these changes by commenting:
@cursor push 070e900f78
Preview (070e900f78)
diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml
--- a/.github/workflows/releases.yml
+++ b/.github/workflows/releases.yml
@@ -89,11 +89,12 @@
if: runner.os == 'Windows'
shell: pwsh
run: |
- Compress-Archive -Path `
- target/${{matrix.target}}/release-lto-nodebug/yarn-bin${{matrix.ext}}, `
- target/${{matrix.target}}/release-lto-nodebug/yarn${{matrix.ext}}, `
- target/${{matrix.target}}/release-lto-nodebug/LICENSE.md `
- -DestinationPath yarn-${{matrix.target}}.zip
+ $tmpDir = New-Item -ItemType Directory -Path "tmp-archive-${{matrix.target}}"
+ Copy-Item -Path "target/${{matrix.target}}/release-lto-nodebug/yarn-bin${{matrix.ext}}" -Destination $tmpDir
+ Copy-Item -Path "target/${{matrix.target}}/release-lto-nodebug/yarn${{matrix.ext}}" -Destination $tmpDir
+ Copy-Item -Path "target/${{matrix.target}}/release-lto-nodebug/LICENSE.md" -Destination $tmpDir
+ Compress-Archive -Path "$tmpDir/*" -DestinationPath "yarn-${{matrix.target}}.zip"
+ Remove-Item -Recurse -Force $tmpDir
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7You can send follow-ups to the cloud agent here.
⏱️ Benchmark Resultsgatsby install-full-cold
📊 Raw benchmark data (gatsby install-full-cold)Base times: 4.451s, 4.462s, 4.415s, 4.436s, 4.594s, 4.509s, 4.508s, 4.470s, 4.492s, 4.492s, 4.505s, 4.450s, 4.611s, 4.533s, 4.526s, 4.521s, 4.447s, 4.528s, 4.442s, 4.535s, 4.521s, 4.338s, 4.493s, 4.479s, 4.456s, 4.548s, 4.490s, 4.493s, 4.431s, 4.509s Head times: 4.379s, 4.522s, 4.555s, 4.445s, 4.524s, 4.420s, 4.462s, 4.499s, 4.525s, 4.498s, 4.504s, 4.361s, 4.443s, 4.400s, 4.462s, 4.471s, 4.445s, 4.479s, 4.695s, 4.529s, 4.451s, 4.492s, 4.423s, 4.458s, 4.669s, 4.606s, 4.623s, 4.526s, 4.516s, 4.509s gatsby install-cache-only
📊 Raw benchmark data (gatsby install-cache-only)Base times: 1.310s, 1.329s, 1.284s, 1.325s, 1.324s, 1.303s, 1.326s, 1.315s, 1.295s, 1.314s, 1.318s, 1.325s, 1.310s, 1.321s, 1.321s, 1.313s, 1.291s, 1.298s, 1.326s, 1.320s, 1.300s, 1.380s, 1.335s, 1.276s, 1.314s, 1.278s, 1.306s, 1.330s, 1.305s, 1.304s Head times: 1.323s, 1.320s, 1.271s, 1.294s, 1.291s, 1.280s, 1.294s, 1.291s, 1.283s, 1.284s, 1.290s, 1.274s, 1.301s, 1.289s, 1.282s, 1.303s, 1.286s, 1.274s, 1.291s, 1.356s, 1.293s, 1.269s, 1.296s, 1.274s, 1.292s, 1.288s, 1.279s, 1.285s, 1.288s, 1.331s gatsby install-cache-and-lock (warm, with lockfile)
📊 Raw benchmark data (gatsby install-cache-and-lock (warm, with lockfile))Base times: 0.356s, 0.359s, 0.364s, 0.356s, 0.353s, 0.351s, 0.355s, 0.354s, 0.354s, 0.352s, 0.350s, 0.355s, 0.356s, 0.354s, 0.350s, 0.350s, 0.353s, 0.361s, 0.355s, 0.354s, 0.355s, 0.352s, 0.374s, 0.367s, 0.360s, 0.359s, 0.363s, 0.363s, 0.370s, 0.368s Head times: 0.351s, 0.352s, 0.354s, 0.353s, 0.352s, 0.354s, 0.354s, 0.379s, 0.358s, 0.351s, 0.353s, 0.354s, 0.351s, 0.358s, 0.358s, 0.348s, 0.351s, 0.350s, 0.376s, 0.358s, 0.351s, 0.353s, 0.350s, 0.353s, 0.351s, 0.358s, 0.347s, 0.356s, 0.350s, 0.353s |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Windows daemon kill ignores children
- Replaced single-process TerminateProcess with taskkill /T to terminate the entire process tree and added wait loop to verify termination before returning.
Or push these changes by commenting:
@cursor push 4e17f2c392
Preview (4e17f2c392)
diff --git a/packages/zpm-switch/src/daemons.rs b/packages/zpm-switch/src/daemons.rs
--- a/packages/zpm-switch/src/daemons.rs
+++ b/packages/zpm-switch/src/daemons.rs
@@ -165,8 +165,27 @@
#[cfg(windows)]
{
- // On Windows, just use TerminateProcess (no graceful shutdown)
- kill_process(pid)
+ // On Windows, use taskkill /T to terminate the process tree
+ // /T terminates the process and all its child processes
+ // /F forces termination without graceful shutdown
+ let result = std::process::Command::new("taskkill")
+ .args(["/PID", &pid.to_string(), "/T", "/F"])
+ .output();
+
+ if result.is_err() {
+ return false;
+ }
+
+ // Wait for the process to actually terminate
+ for _ in 0..10 {
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ if !is_process_alive(pid) {
+ return true;
+ }
+ }
+
+ // Return false if we couldn't verify termination
+ !is_process_alive(pid)
}
#[cfg(not(any(unix, windows)))]You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Switch sibling misses exe suffix
- Added std::env::consts::EXE_SUFFIX to the sibling yarn path lookup so Windows correctly finds yarn.exe.
Or push these changes by commenting:
@cursor push 5e2c164043
Preview (5e2c164043)
diff --git a/packages/zpm/src/daemon/client.rs b/packages/zpm/src/daemon/client.rs
--- a/packages/zpm/src/daemon/client.rs
+++ b/packages/zpm/src/daemon/client.rs
@@ -493,7 +493,7 @@
let current_dir
= current_exe.dirname().ok_or(())?;
let sibling_switch
- = current_dir.with_join_str("yarn");
+ = current_dir.with_join_str(&format!("yarn{}", std::env::consts::EXE_SUFFIX));
if sibling_switch.fs_exists() {
Ok(sibling_switch.to_native_string())You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Junction sync skips up-to-date check
- Added directory check for junctions since Rust reports them as directories rather than symlinks, enabling proper freshness detection.
Or push these changes by commenting:
@cursor push ea996118a8
Preview (ea996118a8)
diff --git a/packages/zpm-sync/src/lib.rs b/packages/zpm-sync/src/lib.rs
--- a/packages/zpm-sync/src/lib.rs
+++ b/packages/zpm-sync/src/lib.rs
@@ -320,7 +320,7 @@
SyncNode::Symlink {target_path} => {
let symlink_target
- = metadata.is_symlink()
+ = (metadata.is_symlink() || (self.link_type == LinkType::Junction && metadata.is_dir()))
.then(|| path.fs_read_link())
.transpose()?;You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Pnpm junction replace uses remove_file
- Replaced fs_rm_file() with fs_rm() at all three link creation sites to properly handle both files and directory junctions on Windows.
Or push these changes by commenting:
@cursor push bd18209e5c
Preview (bd18209e5c)
diff --git a/packages/zpm/src/linker/pnpm.rs b/packages/zpm/src/linker/pnpm.rs
--- a/packages/zpm/src/linker/pnpm.rs
+++ b/packages/zpm/src/linker/pnpm.rs
@@ -207,7 +207,7 @@
.relative_to(&link_abs_dirname);
let link_path = link_abs_path
- .fs_rm_file()
+ .fs_rm()
.ok_missing()?
.unwrap_or(&link_abs_path)
.fs_create_parent()?;
@@ -253,7 +253,7 @@
.relative_to(&link_abs_dirname);
let link_path = link_abs_path
- .fs_rm_file()
+ .fs_rm()
.ok_missing()?
.unwrap_or(&link_abs_path)
.fs_create_parent()?;
@@ -326,7 +326,7 @@
.relative_to(&link_abs_dirname);
let link_path = link_abs_path
- .fs_rm_file()
+ .fs_rm()
.ok_missing()?
.unwrap_or(&link_abs_path)
.fs_create_parent()?;You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Daemon registry lacks Windows ACLs
- Implemented Windows ACL support in fs_set_mode(0o600) using SDDL to restrict daemon registry files to owner-only access, preventing other local users from reading daemon credentials including authToken.
Or push these changes by commenting:
@cursor push 72c8ba6ba4
Preview (72c8ba6ba4)
diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -117,7 +117,7 @@
serde_json = { version = "1.0.145", features = ["preserve_order"] }
serde = { version = "1.0.207", features = ["derive"] }
wax = { git = "https://github.com/arcanis/wax.git" }
-windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_System_Threading"] }
+windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_Security", "Win32_Security_Authorization"] }
winnow = "0.7"
url = "2.5.7"
urlencoding = "2.1.3"
diff --git a/packages/zpm-utils/src/path.rs b/packages/zpm-utils/src/path.rs
--- a/packages/zpm-utils/src/path.rs
+++ b/packages/zpm-utils/src/path.rs
@@ -542,7 +542,77 @@
self.fs_set_permissions(std::fs::Permissions::from_mode(mode))?;
}
- #[cfg(not(unix))]
+ #[cfg(windows)]
+ {
+ // On Windows, apply ACLs for mode 0o600 (owner-only read/write)
+ if mode == 0o600 {
+ use std::ffi::OsStr;
+ use std::os::windows::ffi::OsStrExt;
+ use windows_sys::Win32::Security::Authorization::{
+ SetNamedSecurityInfoW, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
+ PROTECTED_DACL_SECURITY_INFORMATION,
+ };
+ use windows_sys::Win32::Security::{
+ ConvertStringSecurityDescriptorToSecurityDescriptorW,
+ PSECURITY_DESCRIPTOR, SDDL_REVISION_1,
+ };
+ use windows_sys::Win32::System::Memory::LocalFree;
+ use windows_sys::core::PWSTR;
+
+ // SDDL string for owner-only full access, protected from inheritance
+ // D:P(A;;FA;;;OW) means:
+ // D: - DACL
+ // P - Protected (don't inherit from parent)
+ // A - Allow ACE
+ // ;; - No object/inherited object type
+ // FA - FILE_ALL_ACCESS
+ // ;;; - No flags/object/inherited object
+ // OW - Owner Rights SID
+ let sddl = "D:P(A;;FA;;;OW)\0";
+ let sddl_wide: Vec<u16> = OsStr::new(sddl).encode_wide().collect();
+
+ unsafe {
+ let mut sd: PSECURITY_DESCRIPTOR = std::ptr::null_mut();
+ let result = ConvertStringSecurityDescriptorToSecurityDescriptorW(
+ sddl_wide.as_ptr(),
+ SDDL_REVISION_1,
+ &mut sd,
+ std::ptr::null_mut(),
+ );
+
+ if result == 0 {
+ return Err(std::io::Error::last_os_error().into());
+ }
+
+ // Convert path to wide string
+ let path_wide: Vec<u16> = OsStr::new(&self.path)
+ .encode_wide()
+ .chain(std::iter::once(0))
+ .collect();
+
+ // Apply the security descriptor to the file
+ let set_result = SetNamedSecurityInfoW(
+ path_wide.as_ptr() as PWSTR,
+ SE_FILE_OBJECT,
+ DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
+ std::ptr::null_mut(),
+ std::ptr::null_mut(),
+ std::ptr::null_mut(),
+ std::ptr::null_mut(),
+ );
+
+ // Free the security descriptor
+ LocalFree(sd as isize);
+
+ if set_result != 0 {
+ return Err(std::io::Error::from_raw_os_error(set_result as i32).into());
+ }
+ }
+ }
+ // For other modes on Windows, we currently don't have an implementation
+ }
+
+ #[cfg(not(any(unix, windows)))]
let _ = mode;
Ok(self)You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Install script misses ARM64 Windows
- Added pattern matching for ARM64 Windows environments (MINGW/MSYS/Cygwin with ARM64/aarch64) to use x86_64-pc-windows-msvc target via emulation.
Or push these changes by commenting:
@cursor push 039a87be1d
Preview (039a87be1d)
diff --git a/install-script.sh b/install-script.sh
--- a/install-script.sh
+++ b/install-script.sh
@@ -77,6 +77,9 @@
'MINGW64_NT'*' x86_64' | 'MSYS_NT'*' x86_64' | 'CYGWIN_NT'*' x86_64')
target=x86_64-pc-windows-msvc
;;
+'MINGW64_NT'*' ARM64' | 'MINGW64_NT'*' aarch64' | 'MSYS_NT'*' ARM64' | 'MSYS_NT'*' aarch64' | 'CYGWIN_NT'*' ARM64' | 'CYGWIN_NT'*' aarch64')
+ target=x86_64-pc-windows-msvc
+ ;;
'Linux aarch64' | 'Linux arm64')
target=aarch64-unknown-linux-musl
;;You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: UNC extended prefix lost
- Modified to_portable_path and from_portable_path to preserve the \?\UNC\ extended prefix using /unc/?/UNC/ portable representation.
- ✅ Fixed: Junction sync deletes real dirs
- Restructured symlink check logic to only treat actual junctions (where fs_read_link succeeds) as link candidates, preventing deletion of regular directories.
Or push these changes by commenting:
@cursor push 828dde4cd8
Preview (828dde4cd8)
diff --git a/packages/zpm-sync/src/lib.rs b/packages/zpm-sync/src/lib.rs
--- a/packages/zpm-sync/src/lib.rs
+++ b/packages/zpm-sync/src/lib.rs
@@ -319,16 +319,17 @@
},
SyncNode::Symlink {target_path} => {
- let is_link_candidate
- = metadata.is_symlink() || (self.link_type == LinkType::Junction && metadata.is_dir());
-
let symlink_target
- = if is_link_candidate {
+ = if metadata.is_symlink() {
match path.fs_read_link() {
Ok(target) => Some(target),
- Err(_) if self.link_type == LinkType::Junction && metadata.is_dir() && !metadata.is_symlink() => None,
Err(error) => return Err(error.into()),
}
+ } else if self.link_type == LinkType::Junction && metadata.is_dir() {
+ match path.fs_read_link() {
+ Ok(target) => Some(target),
+ Err(_) => None,
+ }
} else {
None
};
diff --git a/packages/zpm-utils/src/path.rs b/packages/zpm-utils/src/path.rs
--- a/packages/zpm-utils/src/path.rs
+++ b/packages/zpm-utils/src/path.rs
@@ -11,7 +11,7 @@
let value = value.replace('\\', "/");
if let Some(value) = value.strip_prefix("//?/UNC/") {
- format!("/unc/{}", value)
+ format!("/unc/?/UNC/{}", value)
} else if value.starts_with("//?/")
&& value.as_bytes().get(5) == Some(&b':')
&& value.as_bytes().get(4).map_or(false, u8::is_ascii_alphabetic)
@@ -38,6 +38,10 @@
value[1..].replace('/', "\\")
} else if let Some(value) = value.strip_prefix("/unc/.dot/") {
format!("\\\\.\\{}", value.replace('/', "\\"))
+ } else if let Some(value) = value.strip_prefix("/unc/?/UNC/") {
+ format!("\\\\?\\UNC\\{}", value.replace('/', "\\"))
+ } else if let Some(value) = value.strip_prefix("/unc/?/") {
+ format!("\\\\?\\{}", value.replace('/', "\\"))
} else if let Some(value) = value.strip_prefix("/unc/") {
format!("\\\\{}", value.replace('/', "\\"))
} else {
@@ -1452,12 +1456,13 @@
assert_eq!(to_portable_path(r"\\server\share\project"), "/unc/server/share/project");
assert_eq!(to_portable_path(r"\\.\pipe\yarn"), "/unc/.dot/pipe/yarn");
assert_eq!(to_portable_path(r"\\?\C:\work\project"), "/C:/work/project");
- assert_eq!(to_portable_path(r"\\?\UNC\server\share\project"), "/unc/server/share/project");
+ assert_eq!(to_portable_path(r"\\?\UNC\server\share\project"), "/unc/?/UNC/server/share/project");
assert_eq!(from_portable_path("/C:/work/project"), r"C:\work\project");
assert_eq!(from_portable_path("/unc/server/share/project"), r"\\server\share\project");
assert_eq!(from_portable_path("/unc/.dot/pipe/yarn"), r"\\.\pipe\yarn");
assert_eq!(from_portable_path("/unc/?/C:/work/project"), r"\\?\C:\work\project");
+ assert_eq!(from_portable_path("/unc/?/UNC/server/share/project"), r"\\?\UNC\server\share\project");
}
#[test]You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Junction check errors on missing targets
- Modified junction canonicalization logic to return false on failures instead of propagating errors, restoring self-healing behavior for broken junctions.
Or push these changes by commenting:
@cursor push 4a5820ac01
Preview (4a5820ac01)
diff --git a/packages/zpm-sync/src/lib.rs b/packages/zpm-sync/src/lib.rs
--- a/packages/zpm-sync/src/lib.rs
+++ b/packages/zpm-sync/src/lib.rs
@@ -340,7 +340,10 @@
path.dirname().unwrap_or_default().with_join(target_path)
};
- actual_target.fs_canonicalize()? == expected_target.fs_canonicalize()?
+ match (actual_target.fs_canonicalize(), expected_target.fs_canonicalize()) {
+ (Ok(actual_canonical), Ok(expected_canonical)) => actual_canonical == expected_canonical,
+ _ => false,
+ }
},
(_, Some(actual_target)) => actual_target == *target_path,
(_, None) => false,You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Junction mode relative link churn
- Applied canonical path comparison to relative symlink targets in junction mode by converting them to absolute paths before canonicalization, preventing unnecessary link recreation.
Or push these changes by commenting:
@cursor push 701221b217
Preview (701221b217)
diff --git a/packages/zpm-sync/src/lib.rs b/packages/zpm-sync/src/lib.rs
--- a/packages/zpm-sync/src/lib.rs
+++ b/packages/zpm-sync/src/lib.rs
@@ -333,14 +333,20 @@
let is_symlink_up_to_date
= match (self.link_type, symlink_target) {
- (LinkType::Junction, Some(actual_target)) if actual_target.is_absolute() => {
+ (LinkType::Junction, Some(actual_target)) => {
let expected_target = if target_path.is_absolute() {
target_path.clone()
} else {
path.dirname().unwrap_or_default().with_join(target_path)
};
- match (actual_target.fs_canonicalize(), expected_target.fs_canonicalize()) {
+ let actual_full = if actual_target.is_absolute() {
+ actual_target
+ } else {
+ path.dirname().unwrap_or_default().with_join(&actual_target)
+ };
+
+ match (actual_full.fs_canonicalize(), expected_target.fs_canonicalize()) {
(Ok(actual_canonical), Ok(expected_canonical)) => actual_canonical == expected_canonical,
_ => false,
}You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Portal paths no longer canonical
- Changed all workspace loading and portal resolution calls to use canonical_destination instead of destination_path for consistent path normalization.
- ✅ Fixed: Cross-drive relative portals broken
- Replaced relative_to with relative_to_if_same_root in add_resolution to handle cross-drive paths correctly on Windows.
Or push these changes by commenting:
@cursor push 301db3f46c
Preview (301db3f46c)
diff --git a/packages/zpm/src/commands/link.rs b/packages/zpm/src/commands/link.rs
--- a/packages/zpm/src/commands/link.rs
+++ b/packages/zpm/src/commands/link.rs
@@ -79,7 +79,7 @@
}
let target_workspace
- = Workspace::from_root_path(&destination_path)?;
+ = Workspace::from_root_path(&canonical_destination)?;
if self.all {
let child_workspaces
@@ -87,7 +87,7 @@
if let Some(name) = &target_workspace.manifest.name {
if self.private || !target_workspace.manifest.private.unwrap_or(false) {
- self.add_resolution(&mut document, name, &destination_path, root_path)?;
+ self.add_resolution(&mut document, name, &canonical_destination, root_path)?;
}
}
@@ -105,9 +105,9 @@
} else {
let name
= target_workspace.manifest.name.as_ref()
- .ok_or_else(|| Error::LinkedPackageMissingName(destination_path.clone()))?;
+ .ok_or_else(|| Error::LinkedPackageMissingName(canonical_destination.clone()))?;
- self.add_resolution(&mut document, name, &destination_path, root_path)?;
+ self.add_resolution(&mut document, name, &canonical_destination, root_path)?;
}
}
@@ -118,7 +118,7 @@
fn add_resolution(&self, document: &mut JsonDocument, name: &Ident, workspace_path: &Path, root_path: &Path) -> Result<(), Error> {
let portal_path = if self.relative {
- workspace_path.relative_to(root_path)
+ workspace_path.relative_to_if_same_root(root_path)
} else {
workspace_path.clone()
};You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 7f9c5d5. Configure here.


Summary
winLinkTypewith real NTFS junction support in the node-modules and pnpm linkersx86_64-pc-windows-msvcexecutableswindows-latestValidation
cargo build -rcargo check --workspace --all-targetscargo xwin check --workspace --all-targets --target x86_64-pc-windows-msvcbash -n install-script.shPathIteratorexpectations that disagree with the implementation and its doc examples about emitting the empty relative rootNote
High Risk
Large cross-cutting changes to path handling, linking, installs, and process management on a new platform; mistakes could affect resolution,
node_moduleslayout, or daemon behavior on all OSes where shared code paths changed.Overview
Adds first-class Windows (
x86_64-pc-windows-msvc) support across build, release, install, and runtime.CI & distribution: Build/test matrices include
windows-latestwith.exeartifacts; Windows release zips use PowerShell; npm publish mapswin32and.exebinaries;install-script.shdetects MSYS/Git Bash and installsyarn.exe.Core portability:
zpm_utils::Pathgains portable ↔ native conversion (drive letters, UNC),to_native_string/to_path_buf, Windows symlinks and junctions (junctioncrate), ACL-based0o600files, and cross-drive-saferelative_to_if_same_root. Process helpers move tozpm-utils(alive/terminate,PATHEXT/.cmdresolution, portable exit codes). Script execution and subprocess spawning use native paths and Windows shims.Linking & sync:
winLinkType(symlinks vs junctions) is honored in node-modules and pnpm linkers;zpm-synccan create junctions and compares link targets correctly on Windows.Node & daemons: Builtin
@yarnpkg/nodeadds win-x64 / win-arm64 zip variants; lockfile reuse re-resolves when variant coverage is missing for configured systems. Switch daemon lifecycle usestaskkillon Windows; MSVC build sets an 8 MiB stack foryarn-bin.Behavior fixes: CRLF in JSON whitespace; parent tasks inherit subtask failure exit codes; git diff path normalization for Windows; acceptance tests extended for Windows node dist and architectures.
Reviewed by Cursor Bugbot for commit 51c1b87. Bugbot is set up for automated code reviews on this repo. Configure here.