mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-29 18:35:18 -04:00
fix: preserve node runtime version prefix (#12444)
This commit is contained in:
6
.changeset/runtime-node-prefix.md
Normal file
6
.changeset/runtime-node-prefix.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/engine.runtime.node-resolver": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Preserve the existing Node.js runtime version prefix when resolving `node@runtime:<range>` to a concrete version.
|
||||
@@ -64,7 +64,7 @@ export async function resolveNodeRuntime (
|
||||
throw new PnpmError('NODEJS_VERSION_NOT_FOUND', `Could not find a Node.js version that satisfies ${versionSpec}`)
|
||||
}
|
||||
const variants = await readNodeAssets(ctx.fetchFromRegistry, nodeMirrorBaseUrl, version, releaseChannel)
|
||||
const range = version === versionSpec ? version : `^${version}`
|
||||
const range = createNodeRuntimeVersionSpec(versionSpec, version, wantedDependency)
|
||||
return {
|
||||
id: `node@runtime:${version}` as PkgResolutionId,
|
||||
normalizedBareSpecifier: `runtime:${range}`,
|
||||
@@ -96,6 +96,23 @@ export async function resolveLatestNodeRuntime (
|
||||
return { latestManifest: { name: 'node', version } }
|
||||
}
|
||||
|
||||
function createNodeRuntimeVersionSpec (
|
||||
versionSpec: string,
|
||||
resolvedVersion: string,
|
||||
wantedDependency: WantedDependency
|
||||
): string {
|
||||
if (resolvedVersion === versionSpec || semver.parse(resolvedVersion)?.prerelease.length) {
|
||||
return resolvedVersion
|
||||
}
|
||||
const source = wantedDependency.prevSpecifier?.startsWith('runtime:')
|
||||
? wantedDependency.prevSpecifier.substring('runtime:'.length)
|
||||
: versionSpec
|
||||
const spec = source.includes('/') ? source.split('/', 2)[1] : source
|
||||
if (spec.startsWith('^')) return `^${resolvedVersion}`
|
||||
if (spec.startsWith('~')) return `~${resolvedVersion}`
|
||||
return resolvedVersion
|
||||
}
|
||||
|
||||
async function readNodeAssets (fetch: FetchFromRegistry, nodeMirrorBaseUrl: string, version: string, releaseChannel: string): Promise<PlatformAssetResolution[]> {
|
||||
// The mirror is repository-configurable, so the SHASUMS file's hashes are only
|
||||
// trustworthy once its OpenPGP signature is verified against the Node.js
|
||||
|
||||
40
engine/runtime/node-resolver/test/resolveNodeRuntime.test.ts
Normal file
40
engine/runtime/node-resolver/test/resolveNodeRuntime.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { expect, test } from '@jest/globals'
|
||||
import type { FetchFromRegistry } from '@pnpm/fetching.types'
|
||||
|
||||
import { resolveNodeRuntime } from '../lib/index.js'
|
||||
|
||||
const MIRROR = 'https://node.example/download/rc/'
|
||||
|
||||
const fetch: FetchFromRegistry = async (url) => {
|
||||
switch (url) {
|
||||
case `${MIRROR}index.json`:
|
||||
return new Response(JSON.stringify([
|
||||
{ version: 'v22.11.0', lts: false },
|
||||
{ version: 'v22.10.0', lts: false },
|
||||
]))
|
||||
case `${MIRROR}v22.11.0/SHASUMS256.txt`:
|
||||
return new Response('ed52239294ad517fbe91a268146d5d2aa8a17d2d62d64873e43219078ba71c4e node-v22.11.0-linux-x64.tar.gz\n')
|
||||
default:
|
||||
throw new Error(`Unexpected URL: ${url}`)
|
||||
}
|
||||
}
|
||||
|
||||
test.each([
|
||||
['runtime:rc/22', undefined, 'runtime:22.11.0'],
|
||||
['runtime:rc/^22', undefined, 'runtime:^22.11.0'],
|
||||
['runtime:rc/22', 'runtime:~22.0.0', 'runtime:~22.11.0'],
|
||||
['runtime:rc/^22', 'runtime:22.0.0', 'runtime:22.11.0'],
|
||||
])('resolveNodeRuntime() preserves runtime version prefix (%s, previous %s)', async (bareSpecifier, prevSpecifier, expected) => {
|
||||
const resolution = await resolveNodeRuntime({
|
||||
fetchFromRegistry: fetch,
|
||||
nodeDownloadMirrors: {
|
||||
rc: MIRROR,
|
||||
},
|
||||
}, {
|
||||
alias: 'node',
|
||||
bareSpecifier,
|
||||
prevSpecifier,
|
||||
})
|
||||
|
||||
expect(resolution?.normalizedBareSpecifier).toBe(expected)
|
||||
})
|
||||
@@ -13,6 +13,7 @@ use std::{
|
||||
|
||||
use derive_more::{Display, Error};
|
||||
use miette::Diagnostic;
|
||||
use node_semver::Version;
|
||||
use pacquet_crypto_shasums_file::{
|
||||
FetchShasumsFileError, FetchVerifiedNodeShasumsError, fetch_shasums_file,
|
||||
fetch_verified_node_shasums_file,
|
||||
@@ -153,7 +154,11 @@ impl NodeResolver {
|
||||
as ResolveError
|
||||
})?;
|
||||
let variants = self.read_node_assets(&mirror, &version, &parsed.release_channel).await?;
|
||||
let range = if version == version_spec { version.clone() } else { format!("^{version}") };
|
||||
let range = normalize_node_runtime_version_specifier(
|
||||
version_spec,
|
||||
&version,
|
||||
wanted_dependency.prev_specifier.as_deref(),
|
||||
);
|
||||
let resolution = LockfileResolution::Variations(VariationsResolution { variants });
|
||||
let manifest = serde_json::json!({
|
||||
"name": "node",
|
||||
@@ -260,6 +265,30 @@ fn bare_runtime_spec<'a>(wanted: &'a WantedDependency, expected_alias: &str) ->
|
||||
wanted.bare_specifier.as_deref().and_then(|spec| spec.strip_prefix(BARE_SPEC_PREFIX))
|
||||
}
|
||||
|
||||
fn normalize_node_runtime_version_specifier(
|
||||
version_spec: &str,
|
||||
resolved_version: &str,
|
||||
prev_specifier: Option<&str>,
|
||||
) -> String {
|
||||
if resolved_version == version_spec
|
||||
|| matches!(Version::parse(resolved_version), Ok(version) if !version.pre_release.is_empty())
|
||||
{
|
||||
return resolved_version.to_string();
|
||||
}
|
||||
let source = prev_specifier
|
||||
.and_then(|specifier| specifier.strip_prefix(BARE_SPEC_PREFIX))
|
||||
.unwrap_or(version_spec);
|
||||
let spec = source.split_once('/').map_or(source, |(_, spec)| spec);
|
||||
let prefix = if spec.starts_with('^') {
|
||||
"^"
|
||||
} else if spec.starts_with('~') {
|
||||
"~"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
format!("{prefix}{resolved_version}")
|
||||
}
|
||||
|
||||
/// Read the asset list for one mirror version and decode each row
|
||||
/// into a [`PlatformAssetResolution`].
|
||||
///
|
||||
|
||||
@@ -5,8 +5,8 @@ use pacquet_resolving_resolver_base::{ResolveOptions, Resolver, WantedDependency
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::{
|
||||
NodeResolver, NodeResolverError, bin_spec_for_platform, parse_node_file_name,
|
||||
read_node_assets_from_mirror,
|
||||
NodeResolver, NodeResolverError, bin_spec_for_platform,
|
||||
normalize_node_runtime_version_specifier, parse_node_file_name, read_node_assets_from_mirror,
|
||||
};
|
||||
|
||||
fn resolver() -> NodeResolver {
|
||||
@@ -105,6 +105,27 @@ fn bin_spec_is_a_named_map() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_runtime_spec_preserves_version_prefix() {
|
||||
let cases = [
|
||||
("22", None, "22.11.0"),
|
||||
("^22", None, "^22.11.0"),
|
||||
("22", Some("runtime:~22.0.0"), "~22.11.0"),
|
||||
("^22", Some("runtime:22.0.0"), "22.11.0"),
|
||||
("rc/^22", None, "^22.11.0"),
|
||||
("22", Some("runtime:^22.0.0-rc.0"), "^22.11.0"),
|
||||
];
|
||||
for (version_spec, prev_specifier, expected) in cases {
|
||||
assert_eq!(
|
||||
normalize_node_runtime_version_specifier(version_spec, "22.11.0", prev_specifier),
|
||||
expected,
|
||||
"version_spec={version_spec:?}, prev_specifier={prev_specifier:?}",
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(normalize_node_runtime_version_specifier("^22", "22.0.0-rc.0", None), "22.0.0-rc.0");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn release_asset_reader_requires_signature_when_requested() {
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
|
||||
Reference in New Issue
Block a user