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);
|
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';
|
} from '../../../config/project-graph';
|
||||||
import { hashArray } from '../../../hasher/file-hasher';
|
import { hashArray } from '../../../hasher/file-hasher';
|
||||||
import { CreateDependenciesContext } from '../../../project-graph/plugins';
|
import { CreateDependenciesContext } from '../../../project-graph/plugins';
|
||||||
|
import { findNodeMatchingVersion } from './project-graph-pruning';
|
||||||
|
|
||||||
// we use key => node map to avoid duplicate work when parsing keys
|
// we use key => node map to avoid duplicate work when parsing keys
|
||||||
let keyMap = new Map<string, Set<ProjectGraphExternalNode>>();
|
let keyMap = new Map<string, Set<ProjectGraphExternalNode>>();
|
||||||
@ -407,7 +408,7 @@ export function stringifyPnpmLockfile(
|
|||||||
const rootSnapshot = mapRootSnapshot(
|
const rootSnapshot = mapRootSnapshot(
|
||||||
packageJson,
|
packageJson,
|
||||||
packages,
|
packages,
|
||||||
graph.externalNodes,
|
graph,
|
||||||
+lockfileVersion
|
+lockfileVersion
|
||||||
);
|
);
|
||||||
const snapshots = mapSnapshots(
|
const snapshots = mapSnapshots(
|
||||||
@ -567,7 +568,7 @@ function versionIsAlias(
|
|||||||
function mapRootSnapshot(
|
function mapRootSnapshot(
|
||||||
packageJson: NormalizedPackageJson,
|
packageJson: NormalizedPackageJson,
|
||||||
packages: PackageSnapshots,
|
packages: PackageSnapshots,
|
||||||
nodes: Record<string, ProjectGraphExternalNode>,
|
graph: ProjectGraph,
|
||||||
lockfileVersion: number
|
lockfileVersion: number
|
||||||
): ProjectSnapshot {
|
): ProjectSnapshot {
|
||||||
const snapshot: ProjectSnapshot = { specifiers: {} };
|
const snapshot: ProjectSnapshot = { specifiers: {} };
|
||||||
@ -581,7 +582,16 @@ function mapRootSnapshot(
|
|||||||
Object.keys(packageJson[depType]).forEach((packageName) => {
|
Object.keys(packageJson[depType]).forEach((packageName) => {
|
||||||
const version = packageJson[depType][packageName];
|
const version = packageJson[depType][packageName];
|
||||||
const node =
|
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;
|
snapshot.specifiers[packageName] = version;
|
||||||
// peer dependencies are mapped to dependencies
|
// peer dependencies are mapped to dependencies
|
||||||
let section = depType === 'peerDependencies' ? 'dependencies' : depType;
|
let section = depType === 'peerDependencies' ? 'dependencies' : depType;
|
||||||
|
|||||||
@ -69,7 +69,7 @@ function normalizeDependencies(packageJson: PackageJson, graph: ProjectGraph) {
|
|||||||
return combinedDependencies;
|
return combinedDependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findNodeMatchingVersion(
|
export function findNodeMatchingVersion(
|
||||||
graph: ProjectGraph,
|
graph: ProjectGraph,
|
||||||
packageName: string,
|
packageName: string,
|
||||||
versionExpr: string
|
versionExpr: string
|
||||||
@ -146,6 +146,11 @@ function rehoistNodes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!packagesToRehoist.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// invert dependencies for easier traversal back
|
// invert dependencies for easier traversal back
|
||||||
const invertedGraph = reverse(builder.graph);
|
const invertedGraph = reverse(builder.graph);
|
||||||
const invBuilder = new ProjectGraphBuilder(invertedGraph, {});
|
const invBuilder = new ProjectGraphBuilder(invertedGraph, {});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user