fix: preserve user-defined npm_config_* env vars in lifecycle scripts (#12400)

* fix: preserve user-defined npm_config_* env vars in lifecycle scripts

* fix: use released `@pnpm/npm-lifecycle` and port npm_config_* filter to pacquet

Pin the catalog to the released `@pnpm/npm-lifecycle` ^1100.0.0 instead of a
mutable PR-head ref, regenerating the lockfile to the immutable registry
tarball.

Port the upstream env filter to pacquet's make_env so user-defined
npm_config_* vars (e.g. npm_config_platform_arch) survive lifecycle scripts
while (npm|pnpm)_config_* auth keys are still stripped, matching
`@pnpm/npm-lifecycle` 9e2ac78148.

Harden the new TS test to save/restore npm_config_platform_arch.

* test(executor): restore env vars to pre-test value in lifecycle EnvGuard

The guard removed the seeded var unconditionally on drop, which would
discard any value the process env already had. Capture the original via
var_os and restore it (or remove only when originally absent).

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
Felipe Santos
2026-06-14 09:35:44 -03:00
committed by GitHub
parent 0fe6ea9a8a
commit 23716ed9b0
10 changed files with 211 additions and 96 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/exec.lifecycle": patch
"pnpm": patch
---
User-defined `npm_config_*` environment variables are now preserved during lifecycle script execution. Previously, all `npm_`-prefixed env vars were stripped, which caused user-set variables like `npm_config_platform_arch` to be lost [pnpm/pnpm#12399](https://github.com/pnpm/pnpm/issues/12399).

View File

@@ -1,13 +0,0 @@
const value = process.env['npm_config_frozen_lockfile']
switch(value) {
case undefined:
process.stdout.write('unset')
break
case '':
process.stdout.write('empty string')
break
default:
process.stdout.write('string: ' + value)
break
}

View File

@@ -1,5 +1,5 @@
{
"name": "inspect-frozen-lockfile",
"name": "inspect-npm-config-env",
"version": "1.0.0",
"scripts": {
"postinstall": "node postinstall.js | test-ipc-server-client ./test.sock"

View File

@@ -0,0 +1,5 @@
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith('npm_config_') && key !== 'npm_config_node_gyp') {
process.stdout.write(`${key}=${value}\n`)
}
}

View File

@@ -62,18 +62,28 @@ test('runLifecycleHook() passes newline correctly', async () => {
])
})
test('runLifecycleHook() does not set npm_config env vars', async () => {
const pkgRoot = f.find('inspect-frozen-lockfile')
test('runLifecycleHook() does not set npm_config env vars but preserves user-defined ones', async () => {
const pkgRoot = f.find('inspect-npm-config-env')
await using server = await createTestIpcServer(path.join(pkgRoot, 'test.sock'))
const { default: pkg } = await import(path.join(pkgRoot, 'package.json'))
await runLifecycleHook('postinstall', pkg, {
depPath: '/inspect-frozen-lockfile/1.0.0',
pkgRoot,
rootModulesDir,
unsafePerm: true,
})
const prevPlatformArch = process.env.npm_config_platform_arch
process.env.npm_config_platform_arch = 'x64'
try {
await runLifecycleHook('postinstall', pkg, {
depPath: '/inspect-npm-config-env/1.0.0',
pkgRoot,
rootModulesDir,
unsafePerm: true,
})
} finally {
if (prevPlatformArch === undefined) {
delete process.env.npm_config_platform_arch
} else {
process.env.npm_config_platform_arch = prevPlatformArch
}
}
expect(server.getLines()).toStrictEqual(['unset'])
expect(server.getLines()).toStrictEqual(['npm_config_platform_arch=x64'])
})
test('runPostinstallHooks()', async () => {

View File

@@ -332,9 +332,10 @@ fn missing_manifest_returns_false() {
/// End-to-end check that the spawned child sees `npm_lifecycle_event`,
/// `npm_lifecycle_script`, `INIT_CWD`, `npm_package_name`, and
/// `npm_package_version`, and does NOT see leaked `npm_config_*` keys
/// from this process's env. Adapts the upstream test at
/// <https://github.com/pnpm/pnpm/blob/b4f8f47ac2/exec/lifecycle/test/index.ts#L65-L77>
/// `npm_package_version`; that a user-defined `npm_config_*` var from
/// this process's env is PRESERVED; and that a `npm_config_*` auth var
/// is stripped. Adapts the upstream test at
/// <https://github.com/pnpm/pnpm/blob/b4f8f47ac2/exec/lifecycle/test/index.ts#L65-L82>
/// to a file-dump model so we don't need an IPC fixture.
///
/// Unix-only: relies on `printf` and `$VAR` expansion, which `cmd`
@@ -343,23 +344,41 @@ fn missing_manifest_returns_false() {
/// [`crate::make_env`].
#[cfg(unix)]
#[test]
fn child_sees_stamped_npm_package_and_no_leaked_npm_config() {
/// RAII guard that removes a process env var on drop, so an
/// assertion failure can't leak the seed into sibling tests.
struct EnvGuard(&'static str);
fn child_sees_stamped_npm_package_and_preserves_user_config() {
/// RAII guard that restores a process env var to its pre-test
/// value on drop, so an assertion failure can't leak the seed
/// into sibling tests — nor clobber a value the env already had.
struct EnvGuard {
key: &'static str,
prev: Option<std::ffi::OsString>,
}
impl EnvGuard {
fn new(key: &'static str) -> Self {
EnvGuard { key, prev: std::env::var_os(key) }
}
}
impl Drop for EnvGuard {
fn drop(&mut self) {
// SAFETY: nextest runs each test in its own thread, so the
// only risk is sibling tests calling `env::vars()`
// concurrently — this `Drop` still runs on panic.
unsafe { std::env::remove_var(self.0) }
unsafe {
match self.prev.take() {
Some(value) => std::env::set_var(self.key, value),
None => std::env::remove_var(self.key),
}
}
}
}
let _guard = EnvGuard("npm_config_should_be_stripped");
let _user_guard = EnvGuard::new("npm_config_platform_arch");
let _auth_guard = EnvGuard::new("npm_config__authtoken");
// SAFETY: nextest runs each test in its own thread, so the only
// risk is sibling tests calling `env::vars()` concurrently — the
// guard's `Drop` removes the var even on panic.
unsafe { std::env::set_var("npm_config_should_be_stripped", "leak") };
// guards' `Drop` removes the vars even on panic.
unsafe {
std::env::set_var("npm_config_platform_arch", "x64");
std::env::set_var("npm_config__authtoken", "should-not-leak");
}
let dir = tempdir().expect("create temp dir");
let pkg_root = dir.path();
@@ -374,7 +393,7 @@ fn child_sees_stamped_npm_package_and_no_leaked_npm_config() {
// printf so the line endings are deterministic across
// shells.
"postinstall": format!(
"printf 'stage=%s\\nscript=%s\\nname=%s\\nver=%s\\nconfig=%s\\ninit_cwd=%s\\nleak=%s\\n' \"$npm_lifecycle_event\" \"$npm_lifecycle_script\" \"$npm_package_name\" \"$npm_package_version\" \"$npm_package_config_myKey\" \"$INIT_CWD\" \"$npm_config_should_be_stripped\" > {}",
"printf 'stage=%s\\nscript=%s\\nname=%s\\nver=%s\\nconfig=%s\\ninit_cwd=%s\\nuser=%s\\nauth=%s\\n' \"$npm_lifecycle_event\" \"$npm_lifecycle_script\" \"$npm_package_name\" \"$npm_package_version\" \"$npm_package_config_myKey\" \"$INIT_CWD\" \"$npm_config_platform_arch\" \"$npm_config__authtoken\" > {}",
dump_path.display(),
),
},
@@ -413,7 +432,8 @@ fn child_sees_stamped_npm_package_and_no_leaked_npm_config() {
("ver", "9.9.9"),
("config", "myValue"),
("init_cwd", expected_init_cwd.as_ref()),
("leak", ""), // stripped — child sees empty string
("user", "x64"), // user-defined npm_config_* is preserved
("auth", ""), // auth npm_config_* is stripped — child sees empty string
];
for (k, v) in expected_pairs {
let line = format!("{k}={v}\n");

View File

@@ -8,8 +8,8 @@ use std::{
/// Inputs needed to build the env for a single lifecycle hook spawn.
///
/// Mirrors the union of `makeEnv` inputs and the per-call additions in
/// `lifecycle()` from `@pnpm/npm-lifecycle@d2d8e790` at
/// <https://github.com/pnpm/npm-lifecycle/blob/d2d8e790/index.js#L52-L113>
/// `lifecycle()` from `@pnpm/npm-lifecycle@9e2ac78148` at
/// <https://github.com/pnpm/npm-lifecycle/blob/9e2ac78148/index.js#L52-L113>
/// plus the wrapper's `extraEnv` additions at
/// <https://github.com/pnpm/pnpm/blob/b4f8f47ac2/exec/lifecycle/src/runLifecycleHook.ts#L119-L124>.
pub struct EnvOptions<'a> {
@@ -38,9 +38,9 @@ pub struct EnvBuild {
/// process env to inherit from.
///
/// Ports `makeEnv` + the surrounding env block in `lifecycle()` from
/// `@pnpm/npm-lifecycle@d2d8e790`:
/// - `index.js:73-104` for the post-`makeEnv` stamping.
/// - `index.js:354-414` for `makeEnv` itself (parent-env filter,
/// `@pnpm/npm-lifecycle@9e2ac78148`:
/// - `index.js:74-104` for the post-`makeEnv` stamping.
/// - `index.js:354-423` for `makeEnv` itself (parent-env filter,
/// `npm_package_*` recursion, multi-line escaping).
///
/// Plus the wrapper's `extraEnv` additions at
@@ -54,12 +54,14 @@ pub fn build_env(
manifest: &Value,
parent_env: HashMap<String, String>,
) -> EnvBuild {
// 1. Start from the parent env, stripping every `npm_*` key
// plus the per-call stamps we re-derive (`NODE`, `TMPDIR`,
// `INIT_CWD`, `PNPM_SCRIPT_SRC_DIR`). Mirrors the
// `!i.match(/^npm_/)` filter at index.js:359 — `pnpm_*` keys
// such as `PNPM_HOME` are intentionally NOT in the filter
// (upstream doesn't strip them either).
// 1. Start from the parent env, stripping `npm_package_*` (we
// regenerate them below) and the `(npm|pnpm)_config_*` auth
// keys, plus the per-call stamps we re-derive (`NODE`,
// `TMPDIR`, `INIT_CWD`, `PNPM_SCRIPT_SRC_DIR`). User-defined
// `npm_config_*` such as `npm_config_platform_arch` are
// preserved. Mirrors the parent-env filter at index.js:359-367
// — `pnpm_*` keys such as `PNPM_HOME` are intentionally NOT in
// the filter (upstream doesn't strip them either).
let mut env = filter_parent_env(parent_env);
// 2. `npm_package_*` recursive stamp. Top-level keeps only
@@ -126,14 +128,15 @@ pub fn build_env(
EnvBuild { env, tmpdir }
}
/// Keep PATH (handled by the caller) and everything that does not
/// start with `npm_`; drop NODE / TMPDIR / `INIT_CWD` /
/// `PNPM_SCRIPT_SRC_DIR` because we re-derive them.
/// Keep PATH (handled by the caller) and every key that is not an
/// `npm_package_*` stamp, a `(npm|pnpm)_config_*` auth key, or one of
/// the per-call stamps we re-derive (NODE / TMPDIR / `INIT_CWD` /
/// `PNPM_SCRIPT_SRC_DIR`).
///
/// On Windows the comparison is case-insensitive because Rust's
/// `Command::env` treats env keys case-insensitively on that
/// platform (see [`Command::env`] docs). Leaving e.g. `NPM_CONFIG_FOO`
/// alongside our `npm_*` inserts would collapse at spawn time with
/// platform (see [`Command::env`] docs). Leaving e.g. `NPM_PACKAGE_FOO`
/// alongside our stamped inserts would collapse at spawn time with
/// an unpredictable winner.
///
/// [`Command::env`]: https://doc.rust-lang.org/std/process/struct.Command.html#method.env
@@ -141,35 +144,62 @@ fn filter_parent_env(env: HashMap<String, String>) -> HashMap<String, String> {
env.into_iter().filter(|(k, _)| !is_stamping_key(k, cfg!(windows))).collect()
}
/// Mirrors `!i.match(/^npm_/)` at index.js:359 plus the per-call
/// Mirrors the parent-env filter at index.js:359-367 plus the per-call
/// stamps we always re-derive (`NODE`, `TMPDIR`, `INIT_CWD`,
/// `PNPM_SCRIPT_SRC_DIR`). Only the `npm_*` prefix is stripped
/// `pnpm_*` keys (e.g. `PNPM_HOME`, feature flags) are upstream-
/// preserved and pacquet does the same.
/// `PNPM_SCRIPT_SRC_DIR`). A key is stripped when it is:
/// - an `npm_package_*` key (regenerated by [`stamp_package`]), or
/// - a `(npm|pnpm)_config_*` auth key — one whose remainder starts
/// with `_`, `/`, or `@`, or contains `:_` (e.g. `_authToken`,
/// `//registry.npmjs.org/:_password`) — so credentials never leak
/// into dependency lifecycle scripts, or
/// - one of the per-call stamps above.
///
/// User-defined config such as `npm_config_platform_arch` and `pnpm_*`
/// keys (e.g. `PNPM_HOME`) are preserved, matching upstream.
///
/// `is_windows` toggles case-insensitive matching so test code can
/// drive both branches without `#[cfg(windows)]` gating the test
/// bodies. Production callers pass `cfg!(windows)`.
fn is_stamping_key(key: &str, is_windows: bool) -> bool {
if strip_env_prefix(key, "npm_package_", is_windows).is_some() {
return true;
}
if let Some(rest) = strip_env_prefix(key, "npm_config_", is_windows)
.or_else(|| strip_env_prefix(key, "pnpm_config_", is_windows))
&& (rest.starts_with(['_', '/', '@']) || rest.contains(":_"))
{
return true;
}
if is_windows {
// Byte-level prefix check: `key[..4]` would panic if byte 4
// lands inside a multi-byte UTF-8 codepoint (e.g. a key
// containing `𐀀` whose first byte sits at index 0). Since
// the prefix we want is pure ASCII, comparing bytes
// sidesteps the UTF-8 boundary question entirely.
if key.as_bytes().get(..4).is_some_and(|b| b.eq_ignore_ascii_case(b"npm_")) {
return true;
}
return ["NODE", "TMPDIR", "INIT_CWD", "PNPM_SCRIPT_SRC_DIR"]
.iter()
.any(|name| key.eq_ignore_ascii_case(name));
}
if key.starts_with("npm_") {
return true;
}
matches!(key, "NODE" | "TMPDIR" | "INIT_CWD" | "PNPM_SCRIPT_SRC_DIR")
}
/// Return the slice of `key` after `prefix` when `key` starts with it
/// — case-sensitively on POSIX, case-insensitively on Windows (where
/// `Command::env` collapses key case). Returns `None` otherwise.
///
/// The Windows branch compares bytes rather than chars so it never
/// panics on a non-ASCII key whose UTF-8 representation crosses the
/// prefix boundary; `prefix` is ASCII, so `prefix.len()` is a valid
/// char boundary whenever the bytes match.
fn strip_env_prefix<'key>(key: &'key str, prefix: &str, is_windows: bool) -> Option<&'key str> {
if is_windows {
if key
.as_bytes()
.get(..prefix.len())
.is_some_and(|b| b.eq_ignore_ascii_case(prefix.as_bytes()))
{
return key.get(prefix.len()..);
}
return None;
}
key.strip_prefix(prefix)
}
/// Look up the `PATH` value from `env` case-insensitively. On
/// Windows the system variable is typically `Path`, not `PATH`;
/// returning the value here lets the rest of `build_env` stay

View File

@@ -30,21 +30,33 @@ fn base_opts<'a>(
}
/// Ports `test('makeEnv')` from
/// <https://github.com/pnpm/npm-lifecycle/blob/d2d8e790/test/index.js#L97-L124>.
/// <https://github.com/pnpm/npm-lifecycle/blob/9e2ac78148/test/index.js#L97-L153>.
///
/// Four invariants we mirror:
/// Invariants we mirror:
/// - top-level `npm_package_name` is set from the manifest's `name`,
/// - package-local config like `_myPackage` keys are NOT promoted to
/// `npm_package_config_*`,
/// - `npm_*` keys leaked from the parent env are stripped (upstream's
/// `!i.match(/^npm_/)` filter at `index.js:359`),
/// - user-defined `npm_config_*` keys (e.g. `npm_config_platform_arch`)
/// leaked from the parent env are PRESERVED,
/// - `(npm|pnpm)_config_*` auth keys (`_auth*`, scope/registry-scoped)
/// are stripped so credentials never leak,
/// - `npm_package_*` keys leaked from the parent env are stripped
/// (they're regenerated from the manifest),
/// - everything else passes through — including `pnpm_*` keys like
/// `PNPM_HOME`, which upstream does not filter.
#[test]
fn make_env_stamps_top_level_keys_and_strips_npm_config_leakage() {
fn make_env_preserves_user_config_and_strips_auth_and_package_leakage() {
let mut parent = HashMap::new();
parent.insert("PATH".into(), "/usr/bin".into());
parent.insert("npm_config_enteente".into(), "should-be-stripped".into());
parent.insert("npm_config_platform_arch".into(), "x64".into());
parent.insert("npm_config__auth".into(), "should-not-leak".into());
parent.insert("npm_config__authToken".into(), "should-not-leak".into());
parent.insert("npm_config__password".into(), "should-not-leak".into());
parent.insert("npm_config_//registry.npmjs.org/:_authToken".into(), "should-not-leak".into());
parent.insert("npm_config_@scope:registry".into(), "https://example.com".into());
parent.insert("pnpm_config__authToken".into(), "should-not-leak".into());
parent.insert("pnpm_config_//registry.npmjs.org/:_authToken".into(), "should-not-leak".into());
parent.insert("npm_package_name".into(), "should-be-regenerated".into());
parent.insert("PNPM_HOME".into(), "/opt/pnpm".into());
parent.insert("HOME".into(), "/home/me".into());
@@ -67,11 +79,27 @@ fn make_env_stamps_top_level_keys_and_strips_npm_config_leakage() {
!built.env.contains_key("npm_package__myPackage_secret"),
"underscore-prefixed manifest keys must be ignored",
);
assert!(
!built.env.contains_key("npm_config_enteente"),
"npm_config_* must be stripped from parent env: {:?}",
assert_eq!(
built.env.get("npm_config_platform_arch").map(String::as_str),
Some("x64"),
"user-defined npm_config_* vars from the parent env are preserved: {:?}",
built.env,
);
for stripped in [
"npm_config__auth",
"npm_config__authToken",
"npm_config__password",
"npm_config_//registry.npmjs.org/:_authToken",
"npm_config_@scope:registry",
"pnpm_config__authToken",
"pnpm_config_//registry.npmjs.org/:_authToken",
] {
assert!(
!built.env.contains_key(stripped),
"auth config key {stripped} must be stripped: {:?}",
built.env,
);
}
assert_eq!(
built.env.get("PNPM_HOME").map(String::as_str),
Some("/opt/pnpm"),
@@ -251,15 +279,33 @@ fn sanitize_env_key_matches_upstream_regex() {
assert_eq!(sanitize_env_key("npm_package_já"), "npm_package_j_");
}
/// On POSIX, env keys are case-sensitive: `NPM_CONFIG_FOO` is a
/// different variable than `npm_config_foo`, so only the lowercase
/// `npm_` prefix matches — matching upstream's `/^npm_/` regex
/// On POSIX, env keys are case-sensitive: `NPM_PACKAGE_FOO` is a
/// different variable than `npm_package_foo`, so only the lowercase
/// prefixes match — matching upstream's case-sensitive regexes
/// exactly.
#[test]
fn is_stamping_key_is_case_sensitive_on_posix() {
assert!(is_stamping_key("npm_config_user_agent", false));
assert!(!is_stamping_key("NPM_CONFIG_USER_AGENT", false));
// npm_package_* are regenerated from the manifest.
assert!(is_stamping_key("npm_package_name", false));
assert!(!is_stamping_key("NPM_PACKAGE_NAME", false));
// User-defined config is preserved.
assert!(!is_stamping_key("npm_config_user_agent", false));
assert!(!is_stamping_key("npm_config_platform_arch", false));
assert!(!is_stamping_key("pnpm_config_registry", false));
// Auth config is stripped: remainder starts with `_`/`/`/`@` or
// contains `:_`.
assert!(is_stamping_key("npm_config__auth", false));
assert!(is_stamping_key("npm_config__authToken", false));
assert!(is_stamping_key("npm_config_@scope:registry", false));
assert!(is_stamping_key("npm_config_//registry.npmjs.org/:_authToken", false));
assert!(is_stamping_key("npm_config_foo:_bar", false));
assert!(is_stamping_key("pnpm_config__authToken", false));
assert!(!is_stamping_key("NPM_CONFIG__AUTH", false));
// Non-package, non-config npm_* keys are preserved (they get
// overwritten by the per-call stamps when relevant).
assert!(!is_stamping_key("npm_lifecycle_event", false));
assert!(!is_stamping_key("Npm_Lifecycle_Event", false));
// Per-call stamps we always re-derive.
assert!(is_stamping_key("NODE", false));
assert!(!is_stamping_key("Node", false));
assert!(!is_stamping_key("node", false));
@@ -291,22 +337,33 @@ fn is_stamping_key_handles_non_ascii_keys_without_panicking() {
}
/// On Windows, Rust's `Command::env` treats env keys
/// case-insensitively, so `NPM_CONFIG_FOO` and `npm_config_foo`
/// refer to the same variable. We must strip the entire family
/// case-insensitively or our `npm_*` inserts collide at spawn time
/// with an unpredictable winner.
/// case-insensitively, so `NPM_PACKAGE_FOO` and `npm_package_foo`
/// refer to the same variable. We must strip each stamped family
/// case-insensitively or our inserts collide at spawn time with an
/// unpredictable winner.
#[test]
fn is_stamping_key_is_case_insensitive_on_windows() {
assert!(is_stamping_key("npm_config_user_agent", true));
assert!(is_stamping_key("NPM_CONFIG_USER_AGENT", true));
assert!(is_stamping_key("Npm_Lifecycle_Event", true));
// npm_package_* stripped case-insensitively.
assert!(is_stamping_key("npm_package_name", true));
assert!(is_stamping_key("NPM_PACKAGE_NAME", true));
// User-defined config preserved, in any case.
assert!(!is_stamping_key("npm_config_platform_arch", true));
assert!(!is_stamping_key("NPM_CONFIG_PLATFORM_ARCH", true));
// Auth config stripped case-insensitively (the prefix is matched
// CI; the `_`/`/`/`@`/`:_` markers are ASCII and case-agnostic).
assert!(is_stamping_key("npm_config__auth", true));
assert!(is_stamping_key("NPM_CONFIG__AUTH", true));
assert!(is_stamping_key("npm_config_@scope:registry", true));
// Non-package, non-config npm_* keys are preserved.
assert!(!is_stamping_key("Npm_Lifecycle_Event", true));
// Per-call stamps we always re-derive.
assert!(is_stamping_key("NODE", true));
assert!(is_stamping_key("Node", true));
assert!(is_stamping_key("node", true));
assert!(is_stamping_key("tmpdir", true));
assert!(is_stamping_key("init_cwd", true));
assert!(is_stamping_key("pnpm_script_src_dir", true));
// Edge: short keys shouldn't accidentally match `npm_`.
// Edge: short keys shouldn't accidentally match a prefix.
assert!(!is_stamping_key("NPM", true));
assert!(!is_stamping_key("npm", true));
// pnpm_* keys other than the well-known stamps still survive.

12
pnpm-lock.yaml generated
View File

@@ -354,8 +354,8 @@ catalogs:
specifier: ^0.3.1
version: 0.3.1
'@pnpm/npm-lifecycle':
specifier: 1100.0.0-1
version: 1100.0.0-1
specifier: ^1100.0.0
version: 1100.0.0
'@pnpm/npm-package-arg':
specifier: ^2.0.0
version: 2.0.0
@@ -4383,7 +4383,7 @@ importers:
version: link:../../fetching/directory-fetcher
'@pnpm/npm-lifecycle':
specifier: 'catalog:'
version: 1100.0.0-1(typanion@3.14.0)
version: 1100.0.0(typanion@3.14.0)
'@pnpm/pkg-manifest.reader':
specifier: workspace:*
version: link:../../pkg-manifest/reader
@@ -11646,8 +11646,8 @@ packages:
resolution: {integrity: sha512-5jW/GNLdZMiw+PJ8FYSvOghoApSjsORNIro2fj8j6NHAqJxJjcHekC5/NsKaawoI5LAkU/XDDVjNC71Yz+uS1w==}
engines: {node: '>=18.12'}
'@pnpm/npm-lifecycle@1100.0.0-1':
resolution: {integrity: sha512-sqXZFikFaJfwY8K+Gg9oPMxxYnJ9O7OkMxXAWNPBUEiZh8XK/DaNFlbNOJ3X8P0WKS5nDE9A6TiOBWrhTrpS+A==}
'@pnpm/npm-lifecycle@1100.0.0':
resolution: {integrity: sha512-igdq1MDnShKMFLdA9uBkMZ8lI/65FUMeh7mjgd1x710qjk9JtF8bV2iYOs/hbBz6IFo98adyQvg1Trx1n5dwIw==}
engines: {node: '>=22.13'}
'@pnpm/npm-package-arg@2.0.0':
@@ -19234,7 +19234,7 @@ snapshots:
- supports-color
- typanion
'@pnpm/npm-lifecycle@1100.0.0-1(typanion@3.14.0)':
'@pnpm/npm-lifecycle@1100.0.0(typanion@3.14.0)':
dependencies:
'@pnpm/byline': 1.0.0
'@pnpm/error': 1000.1.0

View File

@@ -91,7 +91,7 @@ catalog:
'@pnpm/logger': '^1100.0.0'
'@pnpm/meta-updater': 2.0.6
'@pnpm/nopt': ^0.3.1
'@pnpm/npm-lifecycle': 1100.0.0-1
'@pnpm/npm-lifecycle': ^1100.0.0
'@pnpm/npm-package-arg': ^2.0.0
'@pnpm/os.env.path-extender': ^3.0.1
'@pnpm/patch-package': 0.0.1