fix(core): handle external node without default version when generating a pnpm pruned lockfile (#31503)
## Current Behavior When generating a pruned pnpm lockfile, if there's no external node with a default version for a given package and the dependency specification for the package includes a Semver range specifier, an error is thrown. ## Expected Behavior When generating a pruned pnpm lockfile, the parser should correctly handle the scenario where there's no external node with a default version for a given package, and the dependency specification for the package includes a Semver range specifier. ## Related Issue(s) Fixes #28627
This commit is contained in:
parent
f1c090b640
commit
e68d884d63
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"tmp": "^0.2.3",
|
||||
"lodash": "^4.17.0",
|
||||
"semver": "^7.3.0"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "test",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@nx/js": "21.1.2",
|
||||
"@nx/webpack": "^21.1.2",
|
||||
"@swc-node/register": "~1.9.1",
|
||||
"@swc/core": "~1.5.7",
|
||||
"@swc/helpers": "~0.5.11",
|
||||
"@types/node": "18.16.9",
|
||||
"nx": "21.1.2",
|
||||
"typescript": "~5.7.2"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,42 @@
|
||||
export default `lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
lodash:
|
||||
specifier: ^4.17.0
|
||||
version: 4.17.21
|
||||
semver:
|
||||
specifier: ^7.3.0
|
||||
version: 7.7.2
|
||||
tmp:
|
||||
specifier: ^0.2.3
|
||||
version: 0.2.3
|
||||
|
||||
packages:
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
semver@7.7.2:
|
||||
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
tmp@0.2.3:
|
||||
resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==}
|
||||
engines: {node: '>=14.14'}
|
||||
|
||||
snapshots:
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
semver@7.7.2: {}
|
||||
|
||||
tmp@0.2.3: {}
|
||||
`;
|
||||
@ -1583,4 +1583,69 @@ describe('pnpm LockFile utility', () => {
|
||||
expect(result).toEqual(prunedLockFile);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pnpm semver range specifier', () => {
|
||||
beforeEach(() => {
|
||||
const fileSys = {
|
||||
'node_modules/.modules.yaml': require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/pnpm-semver-range-specifier/.modules.yaml'
|
||||
)).default,
|
||||
};
|
||||
vol.fromJSON(fileSys, '/root');
|
||||
});
|
||||
|
||||
it('should correctly prune the lock file', () => {
|
||||
const lockFile = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/pnpm-semver-range-specifier/pnpm-lock.yaml'
|
||||
)).default;
|
||||
const expectedPrunedLockFile = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/pnpm-semver-range-specifier/pruned-pnpm-lock.yaml'
|
||||
)).default;
|
||||
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/pnpm-semver-range-specifier/app/package.json'
|
||||
));
|
||||
|
||||
let graph: ProjectGraph = {
|
||||
nodes: {},
|
||||
dependencies: {},
|
||||
externalNodes: {
|
||||
'npm:lodash': {
|
||||
type: 'npm',
|
||||
name: 'npm:lodash',
|
||||
data: { version: '4.17.21', packageName: 'lodash' },
|
||||
},
|
||||
'npm:semver@5.7.2': {
|
||||
type: 'npm',
|
||||
name: 'npm:semver@5.7.2',
|
||||
data: { version: '5.7.2', packageName: 'semver' },
|
||||
},
|
||||
'npm:semver@6.3.1': {
|
||||
type: 'npm',
|
||||
name: 'npm:semver@6.3.1',
|
||||
data: { version: '6.3.1', packageName: 'semver' },
|
||||
},
|
||||
'npm:semver@7.7.2': {
|
||||
type: 'npm',
|
||||
name: 'npm:semver@7.7.2',
|
||||
data: { version: '7.7.2', packageName: 'semver' },
|
||||
},
|
||||
'npm:tmp': {
|
||||
type: 'npm',
|
||||
name: 'npm:tmp',
|
||||
data: { version: '0.2.3', packageName: 'tmp' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const prunedGraph = pruneProjectGraph(graph, packageJson);
|
||||
const result = stringifyPnpmLockfile(prunedGraph, lockFile, packageJson);
|
||||
|
||||
expect(result).toEqual(expectedPrunedLockFile);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -26,6 +26,7 @@ import {
|
||||
} from '../../../config/project-graph';
|
||||
import { hashArray } from '../../../hasher/file-hasher';
|
||||
import { CreateDependenciesContext } from '../../../project-graph/plugins';
|
||||
import { findNodeMatchingVersion } from './project-graph-pruning';
|
||||
|
||||
// we use key => node map to avoid duplicate work when parsing keys
|
||||
let keyMap = new Map<string, Set<ProjectGraphExternalNode>>();
|
||||
@ -407,7 +408,7 @@ export function stringifyPnpmLockfile(
|
||||
const rootSnapshot = mapRootSnapshot(
|
||||
packageJson,
|
||||
packages,
|
||||
graph.externalNodes,
|
||||
graph,
|
||||
+lockfileVersion
|
||||
);
|
||||
const snapshots = mapSnapshots(
|
||||
@ -567,7 +568,7 @@ function versionIsAlias(
|
||||
function mapRootSnapshot(
|
||||
packageJson: NormalizedPackageJson,
|
||||
packages: PackageSnapshots,
|
||||
nodes: Record<string, ProjectGraphExternalNode>,
|
||||
graph: ProjectGraph,
|
||||
lockfileVersion: number
|
||||
): ProjectSnapshot {
|
||||
const snapshot: ProjectSnapshot = { specifiers: {} };
|
||||
@ -581,7 +582,16 @@ function mapRootSnapshot(
|
||||
Object.keys(packageJson[depType]).forEach((packageName) => {
|
||||
const version = packageJson[depType][packageName];
|
||||
const node =
|
||||
nodes[`npm:${packageName}@${version}`] || nodes[`npm:${packageName}`];
|
||||
graph.externalNodes[`npm:${packageName}@${version}`] ||
|
||||
(graph.externalNodes[`npm:${packageName}`] &&
|
||||
graph.externalNodes[`npm:${packageName}`].data.version === version
|
||||
? graph.externalNodes[`npm:${packageName}`]
|
||||
: findNodeMatchingVersion(graph, packageName, version));
|
||||
if (!node) {
|
||||
throw new Error(
|
||||
`Could not find external node for package ${packageName}@${version}.`
|
||||
);
|
||||
}
|
||||
snapshot.specifiers[packageName] = version;
|
||||
// peer dependencies are mapped to dependencies
|
||||
let section = depType === 'peerDependencies' ? 'dependencies' : depType;
|
||||
|
||||
@ -69,7 +69,7 @@ function normalizeDependencies(packageJson: PackageJson, graph: ProjectGraph) {
|
||||
return combinedDependencies;
|
||||
}
|
||||
|
||||
function findNodeMatchingVersion(
|
||||
export function findNodeMatchingVersion(
|
||||
graph: ProjectGraph,
|
||||
packageName: string,
|
||||
versionExpr: string
|
||||
@ -146,6 +146,11 @@ function rehoistNodes(
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!packagesToRehoist.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
// invert dependencies for easier traversal back
|
||||
const invertedGraph = reverse(builder.graph);
|
||||
const invBuilder = new ProjectGraphBuilder(invertedGraph, {});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user