mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-28 01:45:30 -04:00
fix(pacquet): support WSL Windows binaries in sh shims (#12409)
- Port the shell shim behavior from pnpm/cmd-shim#56 to pacquet. - Generate `basedir_win` with Cygwin/MSYS/WSL2 handling and use it only when invoking `.exe` runtime branches. - Preserve POSIX target paths for non-`.exe` runtime branches and add the `.cmd`/`.bat` `/C` runtime fallback. - Gate MSYS-specific cmd switch escaping behind an `$msys` runtime flag, so MSYS gets `//C` while WSL2 and other shells keep `/C`. - Bump `@zkochan/cmd-shim` to 9.0.6.
This commit is contained in:
7
.changeset/update-zkochan-cmd-shim.md
Normal file
7
.changeset/update-zkochan-cmd-shim.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/bins.linker": patch
|
||||
"@pnpm/exe": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Updated `@zkochan/cmd-shim` to v9.0.6.
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
/// Detected runtime for a target script.
|
||||
///
|
||||
/// Mirrors the return shape of `searchScriptRuntime` in
|
||||
/// <https://github.com/pnpm/cmd-shim/blob/0d79ca9534/src/index.ts>.
|
||||
/// <https://github.com/pnpm/cmd-shim/blob/e8560a8405/src/index.ts>.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ScriptRuntime {
|
||||
/// The interpreter to invoke. `None` means "exec the file directly".
|
||||
@@ -54,7 +54,8 @@ pub fn search_script_runtime<Sys: FsReadHead>(path: &Path) -> io::Result<Option<
|
||||
}
|
||||
|
||||
if let Some(prog) = extension_program(extension) {
|
||||
return Ok(Some(ScriptRuntime { prog: Some(prog.to_string()), args: String::new() }));
|
||||
let args = if prog == "cmd" { "/C" } else { "" };
|
||||
return Ok(Some(ScriptRuntime { prog: Some(prog.to_string()), args: args.to_string() }));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
@@ -164,11 +165,12 @@ fn strip_env_prefix(input: &str) -> (&str, bool) {
|
||||
///
|
||||
/// The shim is a pure `/bin/sh` script that:
|
||||
///
|
||||
/// 1. Resolves `basedir` to its own directory (with a `cygpath` fixup for
|
||||
/// MSYS-style POSIX shells on Windows).
|
||||
/// 2. If the runtime program is colocated at `$basedir/<prog>` (a rare case,
|
||||
/// only true when the runtime was bundled alongside the shim), prefer that
|
||||
/// binary; otherwise fall through to the system PATH.
|
||||
/// 1. Resolves `basedir` to its own directory and keeps `basedir_win` for
|
||||
/// native Windows binaries reached from Cygwin/MSYS/WSL2.
|
||||
/// 2. If the runtime program is colocated at `$basedir/<prog>.exe` or
|
||||
/// `$basedir/<prog>` (a rare case, only true when the runtime was bundled
|
||||
/// alongside the shim), prefer that binary; otherwise fall through to the
|
||||
/// system PATH.
|
||||
/// 3. Forwards `"$@"` to the resolved interpreter, with the target script as
|
||||
/// the first positional argument.
|
||||
///
|
||||
@@ -189,18 +191,52 @@ pub fn generate_sh_shim(
|
||||
} else {
|
||||
format!("\"$basedir/{sh_target}\"")
|
||||
};
|
||||
let quoted_target_win = if Path::new(&sh_target).is_absolute() {
|
||||
format!("\"{sh_target}\"")
|
||||
} else {
|
||||
format!("\"$basedir_win/{sh_target}\"")
|
||||
};
|
||||
|
||||
match runtime {
|
||||
Some(ScriptRuntime { prog: Some(prog), args }) => {
|
||||
// `sh_long_prog` is the `"$basedir/<prog>"` form upstream uses.
|
||||
// It always carries the leading `$basedir/` and quotes; never
|
||||
// just the program name on its own.
|
||||
let sh_long_prog = format!("\"$basedir/{prog}\"");
|
||||
writeln!(
|
||||
sh,
|
||||
"if [ -x {sh_long_prog} ]; then\n exec {sh_long_prog} {args} {quoted_target} \"$@\"\nelse\n exec {prog} {args} {quoted_target} \"$@\"\nfi",
|
||||
)
|
||||
.unwrap();
|
||||
let prog_base = strip_exe_suffix(prog).unwrap_or(prog);
|
||||
let prog_has_exe = prog_base.len() != prog.len();
|
||||
let prog_exe = if prog_has_exe { prog.clone() } else { format!("{prog}.exe") };
|
||||
let sh_long_prog_exe = format!("\"$basedir/{prog_exe}\"");
|
||||
let exec_block = |exec_args: &str| {
|
||||
let mut block = String::new();
|
||||
if prog_has_exe {
|
||||
writeln!(
|
||||
block,
|
||||
"if [ -x {sh_long_prog_exe} ]; then\n exec {sh_long_prog_exe} {exec_args} {quoted_target_win} \"$@\"\nelse\n exec {prog_exe} {exec_args} {quoted_target_win} \"$@\"\nfi",
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
let sh_long_prog = format!("\"$basedir/{prog}\"");
|
||||
writeln!(
|
||||
block,
|
||||
"if [ -n \"$exe\" ] && [ -x {sh_long_prog_exe} ]; then\n exec {sh_long_prog_exe} {exec_args} {quoted_target_win} \"$@\"\nelif [ -x {sh_long_prog} ]; then\n exec {sh_long_prog} {exec_args} {quoted_target} \"$@\"\nelif command -v {prog} >/dev/null 2>&1; then\n exec {prog} {exec_args} {quoted_target} \"$@\"\nelif [ -n \"$exe\" ] && command -v {prog_exe} >/dev/null 2>&1; then\n exec {prog_exe} {exec_args} {quoted_target_win} \"$@\"\nelse\n exec {prog} {exec_args} {quoted_target} \"$@\"\nfi",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
block
|
||||
};
|
||||
|
||||
let msys_args = prog_base
|
||||
.eq_ignore_ascii_case("cmd")
|
||||
.then(|| escape_msys_cmd_switches(args))
|
||||
.filter(|escaped_args| escaped_args != args);
|
||||
if let Some(msys_args) = msys_args {
|
||||
writeln!(
|
||||
sh,
|
||||
"if [ -n \"$msys\" ]; then\n{}else\n{}fi",
|
||||
indent_shell_block(&exec_block(&msys_args)),
|
||||
indent_shell_block(&exec_block(args)),
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
sh.push_str(&exec_block(args));
|
||||
}
|
||||
}
|
||||
// No runtime detected, so exec the target directly. Upstream still
|
||||
// emits `exit $?` on this branch for parity with non-execve POSIX
|
||||
@@ -343,17 +379,72 @@ fn relative_target_windows(target_path: &Path, shim_path: &Path) -> String {
|
||||
|
||||
const SH_SHIM_HEADER: &str = r#"#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
basedir_win="$basedir"
|
||||
exe=""
|
||||
msys=""
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
case `uname -a` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir_win=`cygpath -w "$basedir"`
|
||||
fi
|
||||
exe=".exe"
|
||||
msys="true"
|
||||
;;
|
||||
*WSL2*)
|
||||
if command -v wslpath > /dev/null 2>&1; then
|
||||
basedir_win="$(wslpath -w "$basedir" 2> /dev/null)"
|
||||
if [ $? -ne 0 ] || [ -z "$basedir_win" ]; then
|
||||
basedir_win="$basedir"
|
||||
else
|
||||
exe=".exe"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
"#;
|
||||
|
||||
fn indent_shell_block(script: &str) -> String {
|
||||
script
|
||||
.split('\n')
|
||||
.map(|line| if line.is_empty() { String::new() } else { format!(" {line}") })
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn escape_msys_cmd_switches(args: &str) -> String {
|
||||
let mut escaped = String::with_capacity(args.len());
|
||||
let mut chars = args.char_indices();
|
||||
let mut at_boundary = true;
|
||||
|
||||
while let Some((_, ch)) = chars.next() {
|
||||
if ch == '/' && at_boundary {
|
||||
let mut lookahead = chars.clone();
|
||||
if let Some((_, switch @ ('C' | 'c' | 'K' | 'k'))) = lookahead.next()
|
||||
&& lookahead.next().is_none_or(|(_, next)| next.is_whitespace())
|
||||
{
|
||||
escaped.push('/');
|
||||
escaped.push('/');
|
||||
escaped.push(switch);
|
||||
chars.next();
|
||||
at_boundary = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
escaped.push(ch);
|
||||
at_boundary = ch.is_whitespace();
|
||||
}
|
||||
|
||||
escaped
|
||||
}
|
||||
|
||||
fn strip_exe_suffix(prog: &str) -> Option<&str> {
|
||||
let suffix_start = prog.len().checked_sub(4)?;
|
||||
prog.as_bytes()[suffix_start..].eq_ignore_ascii_case(b".exe").then(|| &prog[..suffix_start])
|
||||
}
|
||||
|
||||
/// Trailing `# cmd-shim-target=<rel>` marker. Upstream uses it to detect
|
||||
/// whether an existing shim already targets the same source without
|
||||
/// re-parsing its body. Pacquet uses [`is_shim_pointing_at`] for the same
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::{
|
||||
ScriptRuntime, extension_program, generate_cmd_shim, generate_pwsh_shim, generate_sh_shim,
|
||||
is_shim_pointing_at, parse_shebang, parse_shebang_from_bytes, read_head_filled,
|
||||
relative_target, search_script_runtime,
|
||||
ScriptRuntime, escape_msys_cmd_switches, extension_program, generate_cmd_shim,
|
||||
generate_pwsh_shim, generate_sh_shim, is_shim_pointing_at, parse_shebang,
|
||||
parse_shebang_from_bytes, read_head_filled, relative_target, search_script_runtime,
|
||||
strip_exe_suffix,
|
||||
};
|
||||
use crate::{
|
||||
capabilities::{FsReadHead, Host},
|
||||
@@ -59,10 +60,9 @@ fn relative_target_traverses_into_sibling_package() {
|
||||
assert_eq!(relative_target(target, shim), "../foo/bin/cli.js");
|
||||
}
|
||||
|
||||
/// Shim body for the typical `#!/usr/bin/env node` case must match the
|
||||
/// exec template upstream produces verbatim, including the double space
|
||||
/// between `$basedir/node` and the quoted target path (upstream's
|
||||
/// `${args}` interpolates to empty between two literal spaces).
|
||||
/// Shim body for the typical `#!/usr/bin/env node` case must preserve
|
||||
/// the generated exec block shape, including the double space between
|
||||
/// `$basedir/node` and the quoted target path when `args` is empty.
|
||||
#[test]
|
||||
fn generate_sh_shim_matches_pnpm_typical_case() {
|
||||
let target = Path::new("/proj/node_modules/typescript/bin/tsc");
|
||||
@@ -72,8 +72,34 @@ fn generate_sh_shim_matches_pnpm_typical_case() {
|
||||
|
||||
assert!(body.starts_with("#!/bin/sh\n"), "shebang must come first");
|
||||
assert!(
|
||||
body.contains("if [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/../typescript/bin/tsc\" \"$@\"\nelse\n exec node \"$basedir/../typescript/bin/tsc\" \"$@\"\nfi\n"),
|
||||
"exec block must match pnpm's generateShShim template, body was:\n{body}",
|
||||
body.contains(
|
||||
r#"basedir_win="$basedir"
|
||||
exe=""
|
||||
msys=""
|
||||
|
||||
case `uname -a` in"#
|
||||
),
|
||||
"header must track a Windows-form basedir for WSL2/Cygwin, body was:\n{body}",
|
||||
);
|
||||
assert!(
|
||||
body.contains(r#"basedir_win="$(wslpath -w "$basedir" 2> /dev/null)""#),
|
||||
"header must convert WSL2 basedir with wslpath, body was:\n{body}",
|
||||
);
|
||||
assert!(
|
||||
body.contains(r#"basedir_win=`cygpath -w "$basedir"`"#),
|
||||
"MSYS branch must only update the Windows-form basedir, body was:\n{body}",
|
||||
);
|
||||
assert!(
|
||||
!body.contains("basedir=`cygpath"),
|
||||
"MSYS branch must keep the POSIX basedir unchanged, body was:\n{body}",
|
||||
);
|
||||
assert!(
|
||||
body.contains("else\n exe=\".exe\"\n fi"),
|
||||
"WSL2 branch must enable .exe fallback only after wslpath succeeds, body was:\n{body}",
|
||||
);
|
||||
assert!(
|
||||
body.contains("if [ -n \"$exe\" ] && [ -x \"$basedir/node.exe\" ]; then\n exec \"$basedir/node.exe\" \"$basedir_win/../typescript/bin/tsc\" \"$@\"\nelif [ -x \"$basedir/node\" ]; then\n exec \"$basedir/node\" \"$basedir/../typescript/bin/tsc\" \"$@\"\nelif command -v node >/dev/null 2>&1; then\n exec node \"$basedir/../typescript/bin/tsc\" \"$@\"\nelif [ -n \"$exe\" ] && command -v node.exe >/dev/null 2>&1; then\n exec node.exe \"$basedir_win/../typescript/bin/tsc\" \"$@\"\nelse\n exec node \"$basedir/../typescript/bin/tsc\" \"$@\"\nfi\n"),
|
||||
"exec block must preserve the generated sh shim fallback order, body was:\n{body}",
|
||||
);
|
||||
assert!(
|
||||
body.ends_with("# cmd-shim-target=/proj/node_modules/typescript/bin/tsc\n"),
|
||||
@@ -277,6 +303,80 @@ fn search_script_runtime_falls_back_to_extension() {
|
||||
assert_eq!(rt.prog.as_deref(), Some("node"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_script_runtime_falls_back_to_cmd_with_c_switch() {
|
||||
use tempfile::tempdir;
|
||||
let tmp = tempdir().unwrap();
|
||||
|
||||
for filename in ["script.cmd", "script.bat"] {
|
||||
let path = tmp.path().join(filename);
|
||||
std::fs::write(&path, "echo off\r\n").unwrap();
|
||||
|
||||
let rt = search_script_runtime::<Host>(&path).unwrap().expect("extension fallback");
|
||||
assert_eq!(rt.prog.as_deref(), Some("cmd"));
|
||||
assert_eq!(rt.args, "/C");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_msys_cmd_switches_escapes_only_standalone_cmd_switches() {
|
||||
assert_eq!(escape_msys_cmd_switches("/C"), "//C");
|
||||
assert_eq!(escape_msys_cmd_switches(" /c\t/K "), " //c\t//K ");
|
||||
assert_eq!(
|
||||
escape_msys_cmd_switches("--flag /Config path/C /C:bad"),
|
||||
"--flag /Config path/C /C:bad",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strip_exe_suffix_is_case_insensitive() {
|
||||
assert_eq!(strip_exe_suffix("cmd.exe"), Some("cmd"));
|
||||
assert_eq!(strip_exe_suffix("cmd.EXE"), Some("cmd"));
|
||||
assert_eq!(strip_exe_suffix("\u{e5}.exe"), Some("\u{e5}"));
|
||||
assert_eq!(strip_exe_suffix("node"), None);
|
||||
assert_eq!(strip_exe_suffix("\u{e5}\u{e5}x"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_sh_shim_uses_windows_target_only_for_exe_branches() {
|
||||
let target = Path::new("/proj/node_modules/foo/src.bat");
|
||||
let shim = Path::new("/proj/node_modules/.bin/foo");
|
||||
let runtime = ScriptRuntime { prog: Some("cmd".into()), args: "/C".into() };
|
||||
let body = generate_sh_shim(target, shim, Some(&runtime));
|
||||
|
||||
assert!(
|
||||
body.contains("if [ -n \"$msys\" ]; then\n if [ -n \"$exe\" ] && [ -x \"$basedir/cmd.exe\" ]; then\n exec \"$basedir/cmd.exe\" //C \"$basedir_win/../foo/src.bat\" \"$@\"\n elif [ -x \"$basedir/cmd\" ]; then\n exec \"$basedir/cmd\" //C \"$basedir/../foo/src.bat\" \"$@\"\n elif command -v cmd >/dev/null 2>&1; then\n exec cmd //C \"$basedir/../foo/src.bat\" \"$@\"\n elif [ -n \"$exe\" ] && command -v cmd.exe >/dev/null 2>&1; then\n exec cmd.exe //C \"$basedir_win/../foo/src.bat\" \"$@\"\n else\n exec cmd //C \"$basedir/../foo/src.bat\" \"$@\"\n fi\nelse\n if [ -n \"$exe\" ] && [ -x \"$basedir/cmd.exe\" ]; then\n exec \"$basedir/cmd.exe\" /C \"$basedir_win/../foo/src.bat\" \"$@\"\n elif [ -x \"$basedir/cmd\" ]; then\n exec \"$basedir/cmd\" /C \"$basedir/../foo/src.bat\" \"$@\"\n elif command -v cmd >/dev/null 2>&1; then\n exec cmd /C \"$basedir/../foo/src.bat\" \"$@\"\n elif [ -n \"$exe\" ] && command -v cmd.exe >/dev/null 2>&1; then\n exec cmd.exe /C \"$basedir_win/../foo/src.bat\" \"$@\"\n else\n exec cmd /C \"$basedir/../foo/src.bat\" \"$@\"\n fi\nfi\n"),
|
||||
"cmd sh shim must escape switches only for MSYS and use Windows-form targets only for .exe execution branches, body was:\n{body}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_sh_shim_checks_path_before_exe_fallback() {
|
||||
let target = Path::new("/proj/node_modules/foo/src.sh");
|
||||
let shim = Path::new("/proj/node_modules/.bin/foo");
|
||||
let runtime = ScriptRuntime { prog: Some("sh".into()), args: String::new() };
|
||||
let body = generate_sh_shim(target, shim, Some(&runtime));
|
||||
|
||||
assert!(
|
||||
body.contains("elif command -v sh >/dev/null 2>&1; then\n exec sh \"$basedir/../foo/src.sh\" \"$@\"\nelif [ -n \"$exe\" ] && command -v sh.exe >/dev/null 2>&1; then\n exec sh.exe \"$basedir_win/../foo/src.sh\" \"$@\"\nelse\n exec sh \"$basedir/../foo/src.sh\" \"$@\"\nfi\n"),
|
||||
"PATH fallback must prefer POSIX runtimes and gate .exe fallback, body was:\n{body}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_sh_shim_does_not_append_exe_twice() {
|
||||
let target = Path::new("/proj/node_modules/foo/src.bat");
|
||||
let shim = Path::new("/proj/node_modules/.bin/foo");
|
||||
let runtime = ScriptRuntime { prog: Some("cmd.exe".into()), args: "/C".into() };
|
||||
let body = generate_sh_shim(target, shim, Some(&runtime));
|
||||
|
||||
assert!(!body.contains("cmd.exe.exe"), "explicit .exe runtime must not double suffix:\n{body}");
|
||||
assert!(
|
||||
body.contains("if [ -n \"$msys\" ]; then\n if [ -x \"$basedir/cmd.exe\" ]; then\n exec \"$basedir/cmd.exe\" //C \"$basedir_win/../foo/src.bat\" \"$@\"\n else\n exec cmd.exe //C \"$basedir_win/../foo/src.bat\" \"$@\"\n fi\nelse\n if [ -x \"$basedir/cmd.exe\" ]; then\n exec \"$basedir/cmd.exe\" /C \"$basedir_win/../foo/src.bat\" \"$@\"\n else\n exec cmd.exe /C \"$basedir_win/../foo/src.bat\" \"$@\"\n fi\nfi\n"),
|
||||
"explicit .exe runtime must use Windows-form targets and escape switches only for MSYS, body was:\n{body}",
|
||||
);
|
||||
}
|
||||
|
||||
/// [`search_script_runtime`] returns `Ok(None)` when neither shebang nor
|
||||
/// extension yields a runtime. Pure no-runtime path.
|
||||
#[test]
|
||||
|
||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -522,8 +522,8 @@ catalogs:
|
||||
specifier: ^4.1.6
|
||||
version: 4.1.7
|
||||
'@zkochan/cmd-shim':
|
||||
specifier: ^9.0.3
|
||||
version: 9.0.3
|
||||
specifier: ^9.0.6
|
||||
version: 9.0.6
|
||||
'@zkochan/retry':
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0
|
||||
@@ -1608,7 +1608,7 @@ importers:
|
||||
version: link:../../workspace/project-manifest-reader
|
||||
'@zkochan/cmd-shim':
|
||||
specifier: 'catalog:'
|
||||
version: 9.0.3
|
||||
version: 9.0.6
|
||||
'@zkochan/rimraf':
|
||||
specifier: 'catalog:'
|
||||
version: 4.0.0
|
||||
@@ -8043,7 +8043,7 @@ importers:
|
||||
version: link:../../../__utils__/jest-config
|
||||
'@zkochan/cmd-shim':
|
||||
specifier: 'catalog:'
|
||||
version: 9.0.3
|
||||
version: 9.0.6
|
||||
execa:
|
||||
specifier: 'catalog:'
|
||||
version: safe-execa@0.3.0
|
||||
@@ -12582,8 +12582,8 @@ packages:
|
||||
resolution: {integrity: sha512-E5mgrRS8Kk80n19Xxmrx5qO9UG03FyZd8Me5gxYi++VPZsOv8+OsclA+0Fth4KTDCrQ/FkJryNFKJ6/642lo4g==}
|
||||
engines: {node: '>=18.12'}
|
||||
|
||||
'@zkochan/cmd-shim@9.0.3':
|
||||
resolution: {integrity: sha512-u2kKE7N/UapZ0RyAQTNcnSQ9SYbvtcgwzfXl3WHcoJvja69T15Dou//ZU6Bsk3p0M6fFWD5ip+uwkJy61TvaMA==}
|
||||
'@zkochan/cmd-shim@9.0.6':
|
||||
resolution: {integrity: sha512-Anjtn1GHeHMpG8jndikUt/eMb1BVHS8RnYPrXxcvP7FZjKXS1JoQilD7Ympyz7WI/p1gTQzXME4z6CKpY5Jghg==}
|
||||
engines: {node: '>=22.13'}
|
||||
|
||||
'@zkochan/diable@1.0.2':
|
||||
@@ -20463,7 +20463,7 @@ snapshots:
|
||||
graceful-fs: 4.2.11(patch_hash=68ebc232025360cb3dcd3081f4067f4e9fc022ab6b6f71a3230e86c7a5b337d1)
|
||||
is-windows: 1.0.2
|
||||
|
||||
'@zkochan/cmd-shim@9.0.3':
|
||||
'@zkochan/cmd-shim@9.0.6':
|
||||
dependencies:
|
||||
cmd-extension: 1.0.2
|
||||
graceful-fs: 4.2.11(patch_hash=68ebc232025360cb3dcd3081f4067f4e9fc022ab6b6f71a3230e86c7a5b337d1)
|
||||
|
||||
@@ -149,7 +149,7 @@ catalog:
|
||||
'@yarnpkg/nm': 4.0.7
|
||||
'@yarnpkg/parsers': 3.0.3
|
||||
'@yarnpkg/pnp': ^4.1.6
|
||||
'@zkochan/cmd-shim': ^9.0.3
|
||||
'@zkochan/cmd-shim': ^9.0.6
|
||||
'@zkochan/retry': ^0.2.0
|
||||
'@zkochan/rimraf': ^4.0.0
|
||||
'@zkochan/table': ^2.0.1
|
||||
@@ -369,7 +369,6 @@ minimumReleaseAgeExclude:
|
||||
- '@pnpm/*'
|
||||
- '@rushstack/worker-pool@0.7.18'
|
||||
- '@zkochan/*'
|
||||
- '@zkochan/cmd-shim@9.0.3'
|
||||
- better-path-resolve
|
||||
- body-parser@2.2.1
|
||||
- can-link
|
||||
|
||||
Reference in New Issue
Block a user