@teambit/pnpm 1.0.1322 → 1.0.1323
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lockfile-deps-graph-converter.d.ts +21 -2
- package/dist/lockfile-deps-graph-converter.js +261 -18
- package/dist/lockfile-deps-graph-converter.js.map +1 -1
- package/dist/lockfile-deps-graph-converter.restore.spec.d.ts +1 -0
- package/dist/lockfile-deps-graph-converter.restore.spec.js +687 -0
- package/dist/lockfile-deps-graph-converter.restore.spec.js.map +1 -0
- package/dist/lockfile-deps-graph-converter.spec.js +33 -16
- package/dist/lockfile-deps-graph-converter.spec.js.map +1 -1
- package/dist/pnpm.package-manager.d.ts +7 -0
- package/dist/pnpm.package-manager.js +31 -6
- package/dist/pnpm.package-manager.js.map +1 -1
- package/dist/pnpm.package-manager.spec.js +197 -0
- package/dist/pnpm.package-manager.spec.js.map +1 -1
- package/dist/{preview-1793638233324.js → preview-1793641674677.js} +2 -2
- package/package.json +6 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ProjectManifest } from '@pnpm/types';
|
|
2
|
-
import { type LockfileFileProjectSnapshot } from '@pnpm/lockfile.types';
|
|
2
|
+
import { type LockfileFile, type LockfileFileProjectSnapshot } from '@pnpm/lockfile.types';
|
|
3
3
|
import { DependenciesGraph, type PackageAttributes, type DependencyEdge } from '@teambit/objects';
|
|
4
4
|
import { type CalcDepsGraphOptions, type CalcDepsGraphForComponentOptions, type ComponentIdByPkgName } from '@teambit/dependency-resolver';
|
|
5
5
|
import { type BitLockfileFile } from './lynx';
|
|
@@ -17,6 +17,8 @@ type ResolveFunction = (wantedDependency: {
|
|
|
17
17
|
preferredVersions?: Record<string, unknown>;
|
|
18
18
|
}) => Promise<{
|
|
19
19
|
resolution?: Record<string, unknown>;
|
|
20
|
+
/** The resolved package's manifest; a registry package's carries its tarball's integrity in `dist`. */
|
|
21
|
+
manifest?: unknown;
|
|
20
22
|
}>;
|
|
21
23
|
export declare function init(): Promise<void>;
|
|
22
24
|
export interface LockfileToGraphCache {
|
|
@@ -45,9 +47,26 @@ export declare function createFileVersionReplacer(componentIdByPkgName: Componen
|
|
|
45
47
|
* reaches, without crossing the N-API boundary or cloning the full lockfile.
|
|
46
48
|
*/
|
|
47
49
|
export declare function filterLockfileByImporters(lockfile: BitLockfileFile, importerIds: string[], importersOverride?: Record<string, LockfileFileProjectSnapshot>): BitLockfileFile;
|
|
48
|
-
export declare function convertGraphToLockfile(_graph: DependenciesGraph, { manifests, rootDir, resolve, }: {
|
|
50
|
+
export declare function convertGraphToLockfile(_graph: DependenciesGraph, { manifests, rootDir, resolve, onComponentPkgDropped, }: {
|
|
49
51
|
manifests: Record<string, ProjectManifest>;
|
|
50
52
|
rootDir: string;
|
|
51
53
|
resolve: ResolveFunction;
|
|
54
|
+
/** Called for each workspace-component package the lockfile leaves out, with why. */
|
|
55
|
+
onComponentPkgDropped?: (pkgId: string, reason: string) => void;
|
|
52
56
|
}): Promise<BitLockfileFile>;
|
|
57
|
+
/**
|
|
58
|
+
* The dep path a lockfile ref under `name` points at, or `undefined` for a
|
|
59
|
+
* `link:` ref, which points at no snapshot. An aliased ref
|
|
60
|
+
* (`real-name@1.0.0` under the alias) names the real package, and a `file:`
|
|
61
|
+
* ref a workspace component's own snapshot.
|
|
62
|
+
*/
|
|
63
|
+
export declare function refToDepPath(ref: string, name: string): string | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Set each snapshot's `optional` flag the way pnpm does: on when every path
|
|
66
|
+
* from an importer to it goes through an `optionalDependencies` edge, off
|
|
67
|
+
* otherwise. The graph does not record the flag, and an install that takes
|
|
68
|
+
* the lockfile as it is relies on it: a package reached only optionally may
|
|
69
|
+
* fail to build or to install, one reached otherwise may not.
|
|
70
|
+
*/
|
|
71
|
+
export declare function markOptionalOnlyEntries(lockfile: Pick<LockfileFile, 'importers' | 'snapshots'>): void;
|
|
53
72
|
export {};
|
|
@@ -9,6 +9,8 @@ exports.createFileVersionReplacer = createFileVersionReplacer;
|
|
|
9
9
|
exports.createLockfileToGraphCache = createLockfileToGraphCache;
|
|
10
10
|
exports.filterLockfileByImporters = filterLockfileByImporters;
|
|
11
11
|
exports.init = init;
|
|
12
|
+
exports.markOptionalOnlyEntries = markOptionalOnlyEntries;
|
|
13
|
+
exports.refToDepPath = refToDepPath;
|
|
12
14
|
function _path() {
|
|
13
15
|
const data = _interopRequireDefault(require("path"));
|
|
14
16
|
_path = function () {
|
|
@@ -23,13 +25,6 @@ function _lodash() {
|
|
|
23
25
|
};
|
|
24
26
|
return data;
|
|
25
27
|
}
|
|
26
|
-
function _semver() {
|
|
27
|
-
const data = _interopRequireDefault(require("semver"));
|
|
28
|
-
_semver = function () {
|
|
29
|
-
return data;
|
|
30
|
-
};
|
|
31
|
-
return data;
|
|
32
|
-
}
|
|
33
28
|
function _bitError() {
|
|
34
29
|
const data = require("@teambit/bit-error");
|
|
35
30
|
_bitError = function () {
|
|
@@ -44,6 +39,13 @@ function _componentPackageVersion() {
|
|
|
44
39
|
};
|
|
45
40
|
return data;
|
|
46
41
|
}
|
|
42
|
+
function _semver() {
|
|
43
|
+
const data = _interopRequireDefault(require("semver"));
|
|
44
|
+
_semver = function () {
|
|
45
|
+
return data;
|
|
46
|
+
};
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
47
49
|
function _objects() {
|
|
48
50
|
const data = require("@teambit/objects");
|
|
49
51
|
_objects = function () {
|
|
@@ -70,6 +72,23 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
|
|
|
70
72
|
* `resolveDependency`). Only the parts this module consumes are typed.
|
|
71
73
|
*/
|
|
72
74
|
|
|
75
|
+
/**
|
|
76
|
+
* The integrity a resolution carries: in `resolution` when the resolver
|
|
77
|
+
* reports one, else in the resolved manifest's `dist`, which is where
|
|
78
|
+
* `@pnpm/napi`'s `resolveDependency` reports a registry package's.
|
|
79
|
+
*/
|
|
80
|
+
function resolvedIntegrity(resolveResult) {
|
|
81
|
+
const {
|
|
82
|
+
resolution,
|
|
83
|
+
manifest
|
|
84
|
+
} = resolveResult;
|
|
85
|
+
if (resolution != null && typeof resolution.integrity === 'string' && resolution.integrity) {
|
|
86
|
+
return resolution.integrity;
|
|
87
|
+
}
|
|
88
|
+
const dist = manifest?.dist;
|
|
89
|
+
return typeof dist?.integrity === 'string' && dist.integrity ? dist.integrity : undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
// @pnpm/deps.path is ESM-only; load it through a .cjs shim so the require()
|
|
74
93
|
// chain in the build capsule's mocha runner doesn't trip on the transitive ESM
|
|
75
94
|
// import. Call `init()` once before invoking the public converters; helpers
|
|
@@ -563,7 +582,8 @@ function filterLockfileByImporters(lockfile, importerIds, importersOverride) {
|
|
|
563
582
|
async function convertGraphToLockfile(_graph, {
|
|
564
583
|
manifests,
|
|
565
584
|
rootDir,
|
|
566
|
-
resolve
|
|
585
|
+
resolve,
|
|
586
|
+
onComponentPkgDropped
|
|
567
587
|
}) {
|
|
568
588
|
await init();
|
|
569
589
|
const graphString = _graph.serialize();
|
|
@@ -644,6 +664,10 @@ async function convertGraphToLockfile(_graph, {
|
|
|
644
664
|
}
|
|
645
665
|
const rootEdge = graph.findRootEdge();
|
|
646
666
|
if (rootEdge) {
|
|
667
|
+
const workspaceVersions = new Map(Array.from(workspaceComponentDirs(manifests, rootDir).keys()).map(pkgId => {
|
|
668
|
+
const parsed = dp.parse(pkgId);
|
|
669
|
+
return [parsed.name, parsed.version];
|
|
670
|
+
}));
|
|
647
671
|
for (const [projectDir, manifest] of Object.entries(manifests)) {
|
|
648
672
|
const projectId = getLockfileImporterId(rootDir, projectDir);
|
|
649
673
|
lockfile.importers[projectId] = {
|
|
@@ -660,7 +684,7 @@ async function convertGraphToLockfile(_graph, {
|
|
|
660
684
|
specifier
|
|
661
685
|
};
|
|
662
686
|
} else {
|
|
663
|
-
const edgeId =
|
|
687
|
+
const edgeId = findImporterDepPath(graph, rootEdge, name, specifier, workspaceVersions);
|
|
664
688
|
if (edgeId) {
|
|
665
689
|
const parsed = dp.parse(edgeId);
|
|
666
690
|
const ref = depPathToRef(parsed);
|
|
@@ -675,6 +699,10 @@ async function convertGraphToLockfile(_graph, {
|
|
|
675
699
|
}
|
|
676
700
|
}
|
|
677
701
|
dedupeSnapshotDepsToRootManifest(lockfile, manifests[rootDir]);
|
|
702
|
+
linkWorkspaceComponents(lockfile, manifests, rootDir);
|
|
703
|
+
// Only what the importers reach is worth resolving: a graph holds entries
|
|
704
|
+
// no manifest here declares (an attached extension's dev dependencies, for
|
|
705
|
+
// one), and a registry lookup for them would be wasted or could fail.
|
|
678
706
|
dropUnreachableLockfileEntries(lockfile);
|
|
679
707
|
const pkgsToResolve = getPkgsToResolve(lockfile, manifests);
|
|
680
708
|
const failedWorkspaceComponentPkgs = new Set();
|
|
@@ -701,16 +729,15 @@ async function convertGraphToLockfile(_graph, {
|
|
|
701
729
|
// auth failures) so real install failures aren't silently masked.
|
|
702
730
|
if (err.code === 'ERR_PNPM_NO_MATCHING_VERSION' && graph.packages.get(pkgToResolve.pkgId)?.component != null) {
|
|
703
731
|
failedWorkspaceComponentPkgs.add(pkgToResolve.pkgId);
|
|
732
|
+
onComponentPkgDropped?.(pkgToResolve.pkgId, `the registry has no such version: ${err.message}`);
|
|
704
733
|
return;
|
|
705
734
|
}
|
|
706
735
|
throw err;
|
|
707
736
|
}
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
} = resolveResult;
|
|
711
|
-
if (resolution != null && 'integrity' in resolution && resolution.integrity) {
|
|
737
|
+
const integrity = resolvedIntegrity(resolveResult);
|
|
738
|
+
if (integrity) {
|
|
712
739
|
lockfile.packages[pkgToResolve.pkgId].resolution = {
|
|
713
|
-
integrity
|
|
740
|
+
integrity
|
|
714
741
|
};
|
|
715
742
|
}
|
|
716
743
|
}
|
|
@@ -731,6 +758,9 @@ async function convertGraphToLockfile(_graph, {
|
|
|
731
758
|
// validation below — that path means the graph itself is genuinely broken.
|
|
732
759
|
for (const [pkgId, pkg] of Object.entries(lockfile.packages)) {
|
|
733
760
|
if ((pkg.resolution == null || Object.keys(pkg.resolution).length === 0) && graph.packages.get(pkgId)?.component != null) {
|
|
761
|
+
if (!failedWorkspaceComponentPkgs.has(pkgId)) {
|
|
762
|
+
onComponentPkgDropped?.(pkgId, 'its resolution has no integrity');
|
|
763
|
+
}
|
|
734
764
|
failedWorkspaceComponentPkgs.add(pkgId);
|
|
735
765
|
}
|
|
736
766
|
}
|
|
@@ -738,6 +768,7 @@ async function convertGraphToLockfile(_graph, {
|
|
|
738
768
|
scrubPkgsFromLockfile(lockfile, failedWorkspaceComponentPkgs);
|
|
739
769
|
}
|
|
740
770
|
dropUnreachableLockfileEntries(lockfile);
|
|
771
|
+
markOptionalOnlyEntries(lockfile);
|
|
741
772
|
// Validate the generated lockfile
|
|
742
773
|
for (const [depPath, pkg] of Object.entries(lockfile.packages)) {
|
|
743
774
|
if (pkg.resolution == null || Object.keys(pkg.resolution).length === 0) {
|
|
@@ -760,6 +791,67 @@ async function convertGraphToLockfile(_graph, {
|
|
|
760
791
|
return deps;
|
|
761
792
|
}
|
|
762
793
|
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* The dep path a restored importer entry for `name@specifier` points at: the
|
|
797
|
+
* root edge neighbour the manifest's dependency matches, or, when the manifest
|
|
798
|
+
* asks for a version the graph holds only deeper in the tree (an env peer a
|
|
799
|
+
* capsule manifest took from the graph), the snapshot of that version,
|
|
800
|
+
* preferring the peer variant the component's own dependencies were resolved
|
|
801
|
+
* against. Nothing when the graph holds no such version.
|
|
802
|
+
*/
|
|
803
|
+
function findImporterDepPath(graph, rootEdge, name, specifier, workspaceVersions) {
|
|
804
|
+
const sameName = rootEdge.neighbours.filter(directDep => directDep.name === name);
|
|
805
|
+
// A merged graph keeps one neighbour per distinct specifier of a package,
|
|
806
|
+
// so the one recorded for this very specifier or version comes first; a
|
|
807
|
+
// range is matched only when none was, and among several resolutions that
|
|
808
|
+
// satisfy it the highest version, which is what a resolution would pick.
|
|
809
|
+
const recorded = sameName.find(directDep => directDep.specifier === specifier) ?? sameName.find(directDep => dp.removeSuffix(directDep.id) === `${name}@${specifier}`);
|
|
810
|
+
if (recorded) return recorded.id;
|
|
811
|
+
const satisfying = sameName.filter(directDep => specifierMatchesDirectDep(name, specifier, directDep, workspaceVersions));
|
|
812
|
+
if (satisfying.length > 0) {
|
|
813
|
+
return satisfying.reduce((highest, directDep) => _semver().default.gt(dp.parse(directDep.id).version, dp.parse(highest.id).version) ? directDep : highest).id;
|
|
814
|
+
}
|
|
815
|
+
const pkgId = `${name}@${specifier}`;
|
|
816
|
+
const candidates = graph.edges.filter(edge => edge.id !== _objects().DependenciesGraph.ROOT_EDGE_ID && (edge.attr?.pkgId ?? dp.removeSuffix(edge.id)) === pkgId).map(edge => edge.id);
|
|
817
|
+
if (candidates.length === 0) return graph.packages.has(pkgId) ? pkgId : undefined;
|
|
818
|
+
const peerIdsOfDirectDeps = new Set();
|
|
819
|
+
const stack = rootEdge.neighbours.map(({
|
|
820
|
+
id
|
|
821
|
+
}) => id);
|
|
822
|
+
while (stack.length > 0) {
|
|
823
|
+
for (const peerId of (0, _objects().splitDependenciesGraphPeerIds)(stack.pop())) {
|
|
824
|
+
peerIdsOfDirectDeps.add(peerId);
|
|
825
|
+
stack.push(peerId);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return candidates.find(id => peerIdsOfDirectDeps.has(id)) ?? candidates[0];
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Whether the manifest's `specifier` for `name`, which no direct dependency
|
|
833
|
+
* was recorded for, is satisfied by the direct dependency the graph recorded
|
|
834
|
+
* under another specifier: a workspace reference the graph resolved to the
|
|
835
|
+
* version of the workspace component of that name, or a range the resolved
|
|
836
|
+
* version satisfies, read the way pnpm reads ranges, so a prerelease
|
|
837
|
+
* satisfies only a range that names one.
|
|
838
|
+
*/
|
|
839
|
+
function specifierMatchesDirectDep(name, specifier, directDep, workspaceVersions) {
|
|
840
|
+
const {
|
|
841
|
+
version
|
|
842
|
+
} = dp.parse(directDep.id);
|
|
843
|
+
if (version == null) return false;
|
|
844
|
+
if (specifier.startsWith('workspace:')) {
|
|
845
|
+
// The entry becomes the workspace component's `file:` record, so only the
|
|
846
|
+
// version the workspace holds can stand for it, and the range, when the
|
|
847
|
+
// reference carries one, has to accept that version.
|
|
848
|
+
if (workspaceVersions.get(name) !== version) return false;
|
|
849
|
+
const range = specifier.slice('workspace:'.length);
|
|
850
|
+
if (range === '' || range === '*' || range === '^' || range === '~') return true;
|
|
851
|
+
return _semver().default.validRange(range) != null && _semver().default.satisfies(version, range);
|
|
852
|
+
}
|
|
853
|
+
return _semver().default.validRange(specifier) != null && _semver().default.satisfies(version, specifier);
|
|
854
|
+
}
|
|
763
855
|
function getPkgsToResolve(lockfile, manifests) {
|
|
764
856
|
const pkgsToResolve = [];
|
|
765
857
|
const pkgsInTheWorkspaces = new Map();
|
|
@@ -802,8 +894,8 @@ function dropUnreachableLockfileEntries(lockfile) {
|
|
|
802
894
|
if (!snapshot) continue;
|
|
803
895
|
for (const depType of ['dependencies', 'optionalDependencies']) {
|
|
804
896
|
for (const [name, ref] of Object.entries(snapshot[depType] ?? {})) {
|
|
805
|
-
|
|
806
|
-
stack.push(
|
|
897
|
+
const subDepPath = refToDepPath(ref, name);
|
|
898
|
+
if (subDepPath != null) stack.push(subDepPath);
|
|
807
899
|
}
|
|
808
900
|
}
|
|
809
901
|
}
|
|
@@ -813,8 +905,8 @@ function dropUnreachableLockfileEntries(lockfile) {
|
|
|
813
905
|
for (const [name, {
|
|
814
906
|
version
|
|
815
907
|
}] of Object.entries(importer[depType] ?? {})) {
|
|
816
|
-
|
|
817
|
-
visit(
|
|
908
|
+
const depPath = refToDepPath(version, name);
|
|
909
|
+
if (depPath != null) visit(depPath);
|
|
818
910
|
}
|
|
819
911
|
}
|
|
820
912
|
}
|
|
@@ -832,6 +924,157 @@ function dropUnreachableLockfileEntries(lockfile) {
|
|
|
832
924
|
lockfile.bit.depsRequiringBuild = lockfile.bit.depsRequiringBuild.filter(depPath => reachablePackages.has(dp.removeSuffix(depPath)));
|
|
833
925
|
}
|
|
834
926
|
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* The workspace components among `manifests`, keyed by the `name@version`
|
|
930
|
+
* id the graph records them under, with the lockfile-relative directory pnpm
|
|
931
|
+
* records them at. The root project is not a component.
|
|
932
|
+
*/
|
|
933
|
+
function workspaceComponentDirs(manifests, rootDir) {
|
|
934
|
+
const dirs = new Map();
|
|
935
|
+
for (const [projectDir, {
|
|
936
|
+
name,
|
|
937
|
+
version
|
|
938
|
+
}] of Object.entries(manifests)) {
|
|
939
|
+
if (!name || !version) continue;
|
|
940
|
+
const relativeDir = getLockfileImporterId(rootDir, projectDir);
|
|
941
|
+
if (relativeDir === '.') continue;
|
|
942
|
+
dirs.set(`${name}@${(0, _componentPackageVersion().snapToSemver)(version)}`, relativeDir);
|
|
943
|
+
}
|
|
944
|
+
return dirs;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* The inverse of `createFileVersionReplacer`: rewrites every `name@version`
|
|
949
|
+
* of a workspace component in `dirs`, wherever it stands in a package id, a
|
|
950
|
+
* dep path or a peer suffix, to the `name@file:<dir>` pnpm records for it.
|
|
951
|
+
*/
|
|
952
|
+
function createWorkspaceComponentReplacer(dirs) {
|
|
953
|
+
if (dirs.size === 0) return s => s;
|
|
954
|
+
const ids = Array.from(dirs.keys()).map(_lodash().escapeRegExp);
|
|
955
|
+
// An id starts a dep path or a peer suffix segment, or follows the alias in
|
|
956
|
+
// an aliased dep path, and ends where its own peer suffix or the enclosing
|
|
957
|
+
// segment does.
|
|
958
|
+
const pattern = new RegExp(`(?<=^|[(@])(${ids.join('|')})(?=[()]|$)`, 'g');
|
|
959
|
+
return s => s.replace(pattern, id => `${id.slice(0, id.lastIndexOf('@'))}@file:${dirs.get(id)}`);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Record the workspace components the graph resolved to, at the version the
|
|
964
|
+
* workspace holds them, the way pnpm does: as `file:` directory packages, with
|
|
965
|
+
* the graph's resolution of their dependencies as their snapshot. pnpm links
|
|
966
|
+
* every dependency on such a version to the workspace component
|
|
967
|
+
* (`linkWorkspacePackages: deep`), transitive ones included, so a registry
|
|
968
|
+
* entry for it would only be resolved again, and pnpm accepts the snapshot
|
|
969
|
+
* while the component's manifest still declares the dependencies it records.
|
|
970
|
+
* Other versions of the same component stay registry packages.
|
|
971
|
+
*/
|
|
972
|
+
function linkWorkspaceComponents(lockfile, manifests, rootDir) {
|
|
973
|
+
const dirs = workspaceComponentDirs(manifests, rootDir);
|
|
974
|
+
if (dirs.size === 0) return;
|
|
975
|
+
const replace = createWorkspaceComponentReplacer(dirs);
|
|
976
|
+
const replaceRef = (name, ref) => ref.startsWith('link:') ? ref : replace(`${name}@${ref}`).slice(name.length + 1);
|
|
977
|
+
const packages = {};
|
|
978
|
+
for (const [pkgId, pkg] of Object.entries(lockfile.packages)) {
|
|
979
|
+
const directory = dirs.get(pkgId);
|
|
980
|
+
if (directory == null) {
|
|
981
|
+
packages[pkgId] = pkg;
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
// The id carries the name and the version, and the directory is the
|
|
985
|
+
// resolution. The rest (peers, engines, bins, ...) describes the package
|
|
986
|
+
// either way.
|
|
987
|
+
packages[replace(pkgId)] = _objectSpread(_objectSpread({}, (0, _lodash().omit)(pkg, ['resolution', 'name', 'version'])), {}, {
|
|
988
|
+
resolution: {
|
|
989
|
+
directory,
|
|
990
|
+
type: 'directory'
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
lockfile.packages = packages;
|
|
995
|
+
const snapshots = {};
|
|
996
|
+
for (const [depPath, snapshot] of Object.entries(lockfile.snapshots ?? {})) {
|
|
997
|
+
for (const depType of ['dependencies', 'optionalDependencies']) {
|
|
998
|
+
const deps = snapshot[depType];
|
|
999
|
+
if (!deps) continue;
|
|
1000
|
+
for (const [name, ref] of Object.entries(deps)) {
|
|
1001
|
+
deps[name] = replaceRef(name, ref);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
snapshots[replace(depPath)] = snapshot;
|
|
1005
|
+
}
|
|
1006
|
+
lockfile.snapshots = snapshots;
|
|
1007
|
+
for (const importer of Object.values(lockfile.importers ?? {})) {
|
|
1008
|
+
for (const depType of ['dependencies', 'devDependencies', 'optionalDependencies']) {
|
|
1009
|
+
for (const [name, dep] of Object.entries(importer[depType] ?? {})) {
|
|
1010
|
+
dep.version = replaceRef(name, dep.version);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (lockfile.bit?.depsRequiringBuild) {
|
|
1015
|
+
lockfile.bit.depsRequiringBuild = lockfile.bit.depsRequiringBuild.map(replace);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* The dep path a lockfile ref under `name` points at, or `undefined` for a
|
|
1021
|
+
* `link:` ref, which points at no snapshot. An aliased ref
|
|
1022
|
+
* (`real-name@1.0.0` under the alias) names the real package, and a `file:`
|
|
1023
|
+
* ref a workspace component's own snapshot.
|
|
1024
|
+
*/
|
|
1025
|
+
function refToDepPath(ref, name) {
|
|
1026
|
+
ensureInitialized();
|
|
1027
|
+
return dp.refToRelative(ref, name) ?? undefined;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Set each snapshot's `optional` flag the way pnpm does: on when every path
|
|
1032
|
+
* from an importer to it goes through an `optionalDependencies` edge, off
|
|
1033
|
+
* otherwise. The graph does not record the flag, and an install that takes
|
|
1034
|
+
* the lockfile as it is relies on it: a package reached only optionally may
|
|
1035
|
+
* fail to build or to install, one reached otherwise may not.
|
|
1036
|
+
*/
|
|
1037
|
+
function markOptionalOnlyEntries(lockfile) {
|
|
1038
|
+
const snapshots = lockfile.snapshots ?? {};
|
|
1039
|
+
// Walk (dep path, reached optionally): `dependencies` edges keep the
|
|
1040
|
+
// context, `optionalDependencies` edges always enter it.
|
|
1041
|
+
const visited = new Set();
|
|
1042
|
+
const reachedNonOptionally = new Set();
|
|
1043
|
+
const reachedOptionally = new Set();
|
|
1044
|
+
const queue = [];
|
|
1045
|
+
for (const importer of Object.values(lockfile.importers ?? {})) {
|
|
1046
|
+
for (const [depType, optional] of [['dependencies', false], ['devDependencies', false], ['optionalDependencies', true]]) {
|
|
1047
|
+
for (const [name, {
|
|
1048
|
+
version
|
|
1049
|
+
}] of Object.entries(importer[depType] ?? {})) {
|
|
1050
|
+
const depPath = refToDepPath(version, name);
|
|
1051
|
+
if (depPath != null) queue.push([depPath, optional]);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
while (queue.length > 0) {
|
|
1056
|
+
const [depPath, optional] = queue.pop();
|
|
1057
|
+
const visitKey = `${optional ? 'o' : 'r'}:${depPath}`;
|
|
1058
|
+
if (visited.has(visitKey)) continue;
|
|
1059
|
+
visited.add(visitKey);
|
|
1060
|
+
(optional ? reachedOptionally : reachedNonOptionally).add(depPath);
|
|
1061
|
+
const snapshot = snapshots[depPath];
|
|
1062
|
+
if (!snapshot) continue;
|
|
1063
|
+
for (const [depType, edgeOptional] of [['dependencies', optional], ['optionalDependencies', true]]) {
|
|
1064
|
+
for (const [name, ref] of Object.entries(snapshot[depType] ?? {})) {
|
|
1065
|
+
const subDepPath = refToDepPath(ref, name);
|
|
1066
|
+
if (subDepPath != null) queue.push([subDepPath, edgeOptional]);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
for (const [depPath, snapshot] of Object.entries(snapshots)) {
|
|
1071
|
+
if (reachedOptionally.has(depPath) && !reachedNonOptionally.has(depPath)) {
|
|
1072
|
+
snapshot.optional = true;
|
|
1073
|
+
} else {
|
|
1074
|
+
delete snapshot.optional;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
835
1078
|
function dedupeSnapshotDepsToRootManifest(lockfile, rootManifest) {
|
|
836
1079
|
const overridden = new Set(overriddenPackageNames(lockfile.overrides));
|
|
837
1080
|
const rootDeps = new Map(Object.entries(nonOptionalDependencies(rootManifest)).filter(([name]) => !overridden.has(name)));
|