pm-dedup-core 2.1.0
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/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/buildIdentifiedFixesMap.js +6 -0
- package/dist/buildIdentifiedFixesMap.js.map +1 -0
- package/dist/buildLockstepClusters.js +119 -0
- package/dist/buildLockstepClusters.js.map +1 -0
- package/dist/buildLockstepClusters.test.js +71 -0
- package/dist/buildLockstepClusters.test.js.map +1 -0
- package/dist/clusterLabel.js +59 -0
- package/dist/clusterLabel.js.map +1 -0
- package/dist/clusterLabel.test.js +56 -0
- package/dist/clusterLabel.test.js.map +1 -0
- package/dist/compareCandidateVersions.js +18 -0
- package/dist/compareCandidateVersions.js.map +1 -0
- package/dist/compareCandidateVersions.test.js +32 -0
- package/dist/compareCandidateVersions.test.js.map +1 -0
- package/dist/createPackageFilter.js +81 -0
- package/dist/createPackageFilter.js.map +1 -0
- package/dist/createPackageFilter.test.js +115 -0
- package/dist/createPackageFilter.test.js.map +1 -0
- package/dist/definitions/buildIdentifiedFixesMap.d.ts +4 -0
- package/dist/definitions/buildIdentifiedFixesMap.d.ts.map +1 -0
- package/dist/definitions/buildLockstepClusters.d.ts +8 -0
- package/dist/definitions/buildLockstepClusters.d.ts.map +1 -0
- package/dist/definitions/buildLockstepClusters.test.d.ts +2 -0
- package/dist/definitions/buildLockstepClusters.test.d.ts.map +1 -0
- package/dist/definitions/clusterLabel.d.ts +8 -0
- package/dist/definitions/clusterLabel.d.ts.map +1 -0
- package/dist/definitions/clusterLabel.test.d.ts +2 -0
- package/dist/definitions/clusterLabel.test.d.ts.map +1 -0
- package/dist/definitions/compareCandidateVersions.d.ts +10 -0
- package/dist/definitions/compareCandidateVersions.d.ts.map +1 -0
- package/dist/definitions/compareCandidateVersions.test.d.ts +2 -0
- package/dist/definitions/compareCandidateVersions.test.d.ts.map +1 -0
- package/dist/definitions/createPackageFilter.d.ts +45 -0
- package/dist/definitions/createPackageFilter.d.ts.map +1 -0
- package/dist/definitions/createPackageFilter.test.d.ts +2 -0
- package/dist/definitions/createPackageFilter.test.d.ts.map +1 -0
- package/dist/definitions/duplicateSnapshot.d.ts +8 -0
- package/dist/definitions/duplicateSnapshot.d.ts.map +1 -0
- package/dist/definitions/fileSnapshot.d.ts +7 -0
- package/dist/definitions/fileSnapshot.d.ts.map +1 -0
- package/dist/definitions/findProjectRoot.d.ts +27 -0
- package/dist/definitions/findProjectRoot.d.ts.map +1 -0
- package/dist/definitions/findProjectRoot.test.d.ts +2 -0
- package/dist/definitions/findProjectRoot.test.d.ts.map +1 -0
- package/dist/definitions/identifyLockstepClusterFixes.d.ts +89 -0
- package/dist/definitions/identifyLockstepClusterFixes.d.ts.map +1 -0
- package/dist/definitions/identifyLockstepClusterFixes.test.d.ts +2 -0
- package/dist/definitions/identifyLockstepClusterFixes.test.d.ts.map +1 -0
- package/dist/definitions/identifyResolutionFixes.d.ts +28 -0
- package/dist/definitions/identifyResolutionFixes.d.ts.map +1 -0
- package/dist/definitions/identifyResolutionFixes.test.d.ts +2 -0
- package/dist/definitions/identifyResolutionFixes.test.d.ts.map +1 -0
- package/dist/definitions/index.d.ts +44 -0
- package/dist/definitions/index.d.ts.map +1 -0
- package/dist/definitions/packageDependenciesUtils.d.ts +47 -0
- package/dist/definitions/packageDependenciesUtils.d.ts.map +1 -0
- package/dist/definitions/packageDependenciesUtils.test.d.ts +2 -0
- package/dist/definitions/packageDependenciesUtils.test.d.ts.map +1 -0
- package/dist/definitions/packageFilterArgs.d.ts +74 -0
- package/dist/definitions/packageFilterArgs.d.ts.map +1 -0
- package/dist/definitions/packageFilterArgs.test.d.ts +2 -0
- package/dist/definitions/packageFilterArgs.test.d.ts.map +1 -0
- package/dist/definitions/packageStyles.d.ts +14 -0
- package/dist/definitions/packageStyles.d.ts.map +1 -0
- package/dist/definitions/packageStyles.test.d.ts +2 -0
- package/dist/definitions/packageStyles.test.d.ts.map +1 -0
- package/dist/definitions/parseBinArgs.d.ts +14 -0
- package/dist/definitions/parseBinArgs.d.ts.map +1 -0
- package/dist/definitions/parseBinArgs.test.d.ts +2 -0
- package/dist/definitions/parseBinArgs.test.d.ts.map +1 -0
- package/dist/definitions/planClusterApply.d.ts +39 -0
- package/dist/definitions/planClusterApply.d.ts.map +1 -0
- package/dist/definitions/planClusterApply.test.d.ts +2 -0
- package/dist/definitions/planClusterApply.test.d.ts.map +1 -0
- package/dist/definitions/renderApplyPlan.d.ts +22 -0
- package/dist/definitions/renderApplyPlan.d.ts.map +1 -0
- package/dist/definitions/renderApplyPlan.test.d.ts +2 -0
- package/dist/definitions/renderApplyPlan.test.d.ts.map +1 -0
- package/dist/definitions/renderDedupeSummary.d.ts +22 -0
- package/dist/definitions/renderDedupeSummary.d.ts.map +1 -0
- package/dist/definitions/renderDedupeSummary.test.d.ts +2 -0
- package/dist/definitions/renderDedupeSummary.test.d.ts.map +1 -0
- package/dist/definitions/renderDuplicatesReport.d.ts +39 -0
- package/dist/definitions/renderDuplicatesReport.d.ts.map +1 -0
- package/dist/definitions/renderDuplicatesReport.test.d.ts +2 -0
- package/dist/definitions/renderDuplicatesReport.test.d.ts.map +1 -0
- package/dist/definitions/reportColors.d.ts +25 -0
- package/dist/definitions/reportColors.d.ts.map +1 -0
- package/dist/definitions/reportColors.test.d.ts +2 -0
- package/dist/definitions/reportColors.test.d.ts.map +1 -0
- package/dist/definitions/reportText.d.ts +29 -0
- package/dist/definitions/reportText.d.ts.map +1 -0
- package/dist/definitions/selectExplainedPackages.d.ts +22 -0
- package/dist/definitions/selectExplainedPackages.d.ts.map +1 -0
- package/dist/definitions/selectExplainedPackages.test.d.ts +2 -0
- package/dist/definitions/selectExplainedPackages.test.d.ts.map +1 -0
- package/dist/definitions/unconditionalOverrides.d.ts +24 -0
- package/dist/definitions/unconditionalOverrides.d.ts.map +1 -0
- package/dist/definitions/unconditionalOverrides.test.d.ts +2 -0
- package/dist/definitions/unconditionalOverrides.test.d.ts.map +1 -0
- package/dist/definitions/versionsSnapshot.d.ts +40 -0
- package/dist/definitions/versionsSnapshot.d.ts.map +1 -0
- package/dist/definitions/versionsSnapshot.test.d.ts +2 -0
- package/dist/definitions/versionsSnapshot.test.d.ts.map +1 -0
- package/dist/definitions/workspaceManifest.d.ts +21 -0
- package/dist/definitions/workspaceManifest.d.ts.map +1 -0
- package/dist/definitions/workspaceManifest.test.d.ts +2 -0
- package/dist/definitions/workspaceManifest.test.d.ts.map +1 -0
- package/dist/duplicateSnapshot.js +10 -0
- package/dist/duplicateSnapshot.js.map +1 -0
- package/dist/fileSnapshot.js +21 -0
- package/dist/fileSnapshot.js.map +1 -0
- package/dist/findProjectRoot.js +48 -0
- package/dist/findProjectRoot.js.map +1 -0
- package/dist/findProjectRoot.test.js +53 -0
- package/dist/findProjectRoot.test.js.map +1 -0
- package/dist/identifyLockstepClusterFixes.js +352 -0
- package/dist/identifyLockstepClusterFixes.js.map +1 -0
- package/dist/identifyLockstepClusterFixes.test.js +372 -0
- package/dist/identifyLockstepClusterFixes.test.js.map +1 -0
- package/dist/identifyResolutionFixes.js +102 -0
- package/dist/identifyResolutionFixes.js.map +1 -0
- package/dist/identifyResolutionFixes.test.js +70 -0
- package/dist/identifyResolutionFixes.test.js.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/packageDependenciesUtils.js +91 -0
- package/dist/packageDependenciesUtils.js.map +1 -0
- package/dist/packageDependenciesUtils.test.js +539 -0
- package/dist/packageDependenciesUtils.test.js.map +1 -0
- package/dist/packageFilterArgs.js +65 -0
- package/dist/packageFilterArgs.js.map +1 -0
- package/dist/packageFilterArgs.test.js +93 -0
- package/dist/packageFilterArgs.test.js.map +1 -0
- package/dist/packageStyles.js +33 -0
- package/dist/packageStyles.js.map +1 -0
- package/dist/packageStyles.test.js +58 -0
- package/dist/packageStyles.test.js.map +1 -0
- package/dist/parseBinArgs.js +28 -0
- package/dist/parseBinArgs.js.map +1 -0
- package/dist/parseBinArgs.test.js +62 -0
- package/dist/parseBinArgs.test.js.map +1 -0
- package/dist/planClusterApply.js +69 -0
- package/dist/planClusterApply.js.map +1 -0
- package/dist/planClusterApply.test.js +152 -0
- package/dist/planClusterApply.test.js.map +1 -0
- package/dist/renderApplyPlan.js +51 -0
- package/dist/renderApplyPlan.js.map +1 -0
- package/dist/renderApplyPlan.test.js +106 -0
- package/dist/renderApplyPlan.test.js.map +1 -0
- package/dist/renderDedupeSummary.js +38 -0
- package/dist/renderDedupeSummary.js.map +1 -0
- package/dist/renderDedupeSummary.test.js +91 -0
- package/dist/renderDedupeSummary.test.js.map +1 -0
- package/dist/renderDuplicatesReport.js +375 -0
- package/dist/renderDuplicatesReport.js.map +1 -0
- package/dist/renderDuplicatesReport.test.js +598 -0
- package/dist/renderDuplicatesReport.test.js.map +1 -0
- package/dist/reportColors.js +51 -0
- package/dist/reportColors.js.map +1 -0
- package/dist/reportColors.test.js +49 -0
- package/dist/reportColors.test.js.map +1 -0
- package/dist/reportText.js +29 -0
- package/dist/reportText.js.map +1 -0
- package/dist/selectExplainedPackages.js +42 -0
- package/dist/selectExplainedPackages.js.map +1 -0
- package/dist/selectExplainedPackages.test.js +55 -0
- package/dist/selectExplainedPackages.test.js.map +1 -0
- package/dist/unconditionalOverrides.js +36 -0
- package/dist/unconditionalOverrides.js.map +1 -0
- package/dist/unconditionalOverrides.test.js +71 -0
- package/dist/unconditionalOverrides.test.js.map +1 -0
- package/dist/versionsSnapshot.js +49 -0
- package/dist/versionsSnapshot.js.map +1 -0
- package/dist/versionsSnapshot.test.js +79 -0
- package/dist/versionsSnapshot.test.js.map +1 -0
- package/dist/workspaceManifest.js +63 -0
- package/dist/workspaceManifest.js.map +1 -0
- package/dist/workspaceManifest.test.js +159 -0
- package/dist/workspaceManifest.test.js.map +1 -0
- package/package.json +68 -0
- package/src/buildIdentifiedFixesMap.ts +22 -0
- package/src/buildLockstepClusters.test.ts +90 -0
- package/src/buildLockstepClusters.ts +155 -0
- package/src/clusterLabel.test.ts +79 -0
- package/src/clusterLabel.ts +75 -0
- package/src/compareCandidateVersions.test.ts +39 -0
- package/src/compareCandidateVersions.ts +23 -0
- package/src/createPackageFilter.test.ts +156 -0
- package/src/createPackageFilter.ts +142 -0
- package/src/duplicateSnapshot.ts +24 -0
- package/src/fileSnapshot.ts +28 -0
- package/src/findProjectRoot.test.ts +66 -0
- package/src/findProjectRoot.ts +61 -0
- package/src/identifyLockstepClusterFixes.test.ts +462 -0
- package/src/identifyLockstepClusterFixes.ts +602 -0
- package/src/identifyResolutionFixes.test.ts +133 -0
- package/src/identifyResolutionFixes.ts +183 -0
- package/src/index.ts +123 -0
- package/src/packageDependenciesUtils.test.ts +593 -0
- package/src/packageDependenciesUtils.ts +156 -0
- package/src/packageFilterArgs.test.ts +116 -0
- package/src/packageFilterArgs.ts +98 -0
- package/src/packageStyles.test.ts +77 -0
- package/src/packageStyles.ts +42 -0
- package/src/parseBinArgs.test.ts +73 -0
- package/src/parseBinArgs.ts +33 -0
- package/src/planClusterApply.test.ts +163 -0
- package/src/planClusterApply.ts +105 -0
- package/src/renderApplyPlan.test.ts +136 -0
- package/src/renderApplyPlan.ts +95 -0
- package/src/renderDedupeSummary.test.ts +121 -0
- package/src/renderDedupeSummary.ts +78 -0
- package/src/renderDuplicatesReport.test.ts +724 -0
- package/src/renderDuplicatesReport.ts +645 -0
- package/src/reportColors.test.ts +57 -0
- package/src/reportColors.ts +78 -0
- package/src/reportText.ts +58 -0
- package/src/selectExplainedPackages.test.ts +67 -0
- package/src/selectExplainedPackages.ts +79 -0
- package/src/unconditionalOverrides.test.ts +99 -0
- package/src/unconditionalOverrides.ts +60 -0
- package/src/versionsSnapshot.test.ts +146 -0
- package/src/versionsSnapshot.ts +93 -0
- package/src/workspaceManifest.test.ts +220 -0
- package/src/workspaceManifest.ts +100 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Christophe Hurpeau <christophe@hurpeau.com> (https://christophe.hurpeau.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
pm-dedup-core
|
|
3
|
+
</h1>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
package manager utils
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://npmjs.org/package/pm-dedup-core"><img src="https://img.shields.io/npm/v/pm-dedup-core.svg?style=flat-square" alt="npm version"></a>
|
|
11
|
+
<a href="https://npmjs.org/package/pm-dedup-core"><img src="https://img.shields.io/npm/dw/pm-dedup-core.svg?style=flat-square" alt="npm downloads"></a>
|
|
12
|
+
<a href="https://npmjs.org/package/pm-dedup-core"><img src="https://img.shields.io/node/v/pm-dedup-core.svg?style=flat-square" alt="node version"></a>
|
|
13
|
+
<a href="https://npmjs.org/package/pm-dedup-core"><img src="https://img.shields.io/npm/types/pm-dedup-core.svg?style=flat-square" alt="types"></a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install --save pm-dedup-core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
The package-manager-agnostic core behind
|
|
25
|
+
[bun-dedup](../bun-dedup), [pnpm-dedup](../pnpm-dedup) and
|
|
26
|
+
[yarn-berry-deduplicate](../yarn-berry-deduplicate): everything about finding
|
|
27
|
+
duplicates, deciding what can be merged and rendering the result lives here.
|
|
28
|
+
The per-manager packages only parse their lockfile into the shapes below and
|
|
29
|
+
write the result back.
|
|
30
|
+
|
|
31
|
+
It is published so those packages can depend on it, not as a general-purpose
|
|
32
|
+
library: this is internal API and it changes with them. There is no default
|
|
33
|
+
export — import the named ones.
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
import {
|
|
37
|
+
buildLockstepClusters,
|
|
38
|
+
createPackageFilter,
|
|
39
|
+
findProjectRoot,
|
|
40
|
+
identifyResolutionFixes,
|
|
41
|
+
} from "pm-dedup-core";
|
|
42
|
+
|
|
43
|
+
const projectDir = findProjectRoot({ lockfileName: "bun.lock" }); // null when there is none
|
|
44
|
+
const filter = createPackageFilter({
|
|
45
|
+
includeScopes: ["@babel"],
|
|
46
|
+
exclude: ["@babel/runtime"],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
filter.selects("@babel/core"); // true
|
|
50
|
+
filter.rejectionReason("lodash"); // why it is left alone, for the report
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### What a package manager has to supply
|
|
54
|
+
|
|
55
|
+
The core never touches the filesystem for you and never guesses a lockfile path.
|
|
56
|
+
A caller reads its own lockfile and hands over:
|
|
57
|
+
|
|
58
|
+
- a **resolutions** list — one entry per resolved copy of a package
|
|
59
|
+
(`ResolutionEntry`, whose `package` is `npm` or another protocol).
|
|
60
|
+
- a **dependents map** — for each package name, who requires it and with which
|
|
61
|
+
range (`ResolutionDependentsMap`). This is where the managers differ most:
|
|
62
|
+
see the peer-dependency notes in each package's README.
|
|
63
|
+
- for the cluster pass, a **lockstep graph** (`LockstepGraph`), the dependency
|
|
64
|
+
edges between resolved copies.
|
|
65
|
+
|
|
66
|
+
### Areas
|
|
67
|
+
|
|
68
|
+
| Area | Exports |
|
|
69
|
+
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
70
|
+
| Project root | `findProjectRoot`, `resolveProjectDir` |
|
|
71
|
+
| CLI grammar | `parseBinArgs`, `packageFilterParseArgsOptions`, `packageFilterUsage`, `whyDuplicateParseArgsOptions`, `whyDuplicateUsage`, `toWhyDuplicateRequest` |
|
|
72
|
+
| Package filtering | `createPackageFilter`, `selectPackages`, `selectClusterFixes`, `describeSkippedClusterFix`, `selectExplainedPackages` |
|
|
73
|
+
| Names and ranges | `PackageDescriptorNameUtils`, `PackageDependencyDescriptorUtils`, `isNpmProtocol`, `isSemverComparable`, `createCandidateVersionComparator` |
|
|
74
|
+
| Fix identification | `identifyResolutionFixes`, `buildLockstepClusters`, `identifyLockstepClusterFixes`, `buildIdentifiedFixesMap`, `clusterLabel` |
|
|
75
|
+
| Applying | `planClusterApply`, `applyWorkspaceRangeEdit`, `nextSelector`, `partitionUnconditionalOverrides`, `captureFiles`, `restoreFiles` |
|
|
76
|
+
| Measuring a run | `buildVersionsSnapshot`, `diffVersionsSnapshots`, `countDuplicatedPackages`, `diffDuplicates` |
|
|
77
|
+
| Rendering | `renderDuplicatesReport`, `renderApplyPlan`, `renderDedupeSummary`, `createColorize`, `shouldColorize`, `stylePackageName`, `stylePackageReference` |
|
|
78
|
+
|
|
79
|
+
Two of these carry most of the behaviour:
|
|
80
|
+
|
|
81
|
+
- **`identifyResolutionFixes`** finds the merges reachable one package at a
|
|
82
|
+
time: a copy whose dependents' ranges all accept a version already in the
|
|
83
|
+
lockfile. This is what the lockfile pass applies.
|
|
84
|
+
- **`identifyLockstepClusterFixes`** finds the rest. `buildLockstepClusters`
|
|
85
|
+
groups packages that are always published at the same version, and a
|
|
86
|
+
duplicate inside such a family only goes away when the whole family moves —
|
|
87
|
+
which needs a real resolution, hence the separate cluster pass.
|
|
88
|
+
|
|
89
|
+
`resolveProjectDir` is the bin-facing half of `findProjectRoot`: it prints the
|
|
90
|
+
reason and sets `process.exitCode` when there is no lockfile, and returns
|
|
91
|
+
`null`, so callers guard with `if (projectDir === null) return;`. The notice
|
|
92
|
+
naming the lockfile goes to stderr and only when the root is not the working
|
|
93
|
+
directory, keeping a redirected report parseable.
|
|
94
|
+
|
|
95
|
+
### Colors
|
|
96
|
+
|
|
97
|
+
`shouldColorize(stream)` follows the terminal: `NO_COLOR` and a piped stream turn
|
|
98
|
+
colors off, `FORCE_COLOR` turns them on. Every renderer takes the resulting
|
|
99
|
+
`Colorize`, so a report piped to a file is plain text.
|
|
100
|
+
|
|
101
|
+
### Runtime
|
|
102
|
+
|
|
103
|
+
Node only (`>=22.18.0`), so the pnpm and yarn tools can depend on it: no
|
|
104
|
+
`Bun.*`, no `bun` imports. Tests run under `bun test` like the rest of the
|
|
105
|
+
monorepo.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { identifyResolutionFixes, } from "./identifyResolutionFixes.js";
|
|
2
|
+
export const buildIdentifiedFixesMap = (duplicatesResolutionsMap, dependents) => new Map(Object.entries(duplicatesResolutionsMap).map(([packageName, resolutions]) => [
|
|
3
|
+
packageName,
|
|
4
|
+
identifyResolutionFixes(resolutions, dependents),
|
|
5
|
+
]));
|
|
6
|
+
//# sourceMappingURL=buildIdentifiedFixesMap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildIdentifiedFixesMap.js","sourceRoot":"","sources":["../src/buildIdentifiedFixesMap.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,uBAAuB,GACxB,MAAM,8BAA8B,CAAC;AAItC,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,wBAAwC,EACxC,UAAsC,EACR,EAAE,CAChC,IAAI,GAAG,CACL,MAAM,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,GAAG,CAC1C,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC;IAC9B,WAAW;IACX,uBAAuB,CAAC,WAAW,EAAE,UAAU,CAAC;CACjD,CACF,CACF,CAAC"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import semver from "semver";
|
|
2
|
+
const createUnionFind = (names) => {
|
|
3
|
+
const parent = new Map(names.map((name) => [name, name]));
|
|
4
|
+
const find = (value) => {
|
|
5
|
+
let root = value;
|
|
6
|
+
while (parent.get(root) !== root) {
|
|
7
|
+
root = parent.get(root);
|
|
8
|
+
}
|
|
9
|
+
let cursor = value;
|
|
10
|
+
while (parent.get(cursor) !== root) {
|
|
11
|
+
const next = parent.get(cursor);
|
|
12
|
+
parent.set(cursor, root);
|
|
13
|
+
cursor = next;
|
|
14
|
+
}
|
|
15
|
+
return root;
|
|
16
|
+
};
|
|
17
|
+
const union = (a, b) => {
|
|
18
|
+
const rootA = find(a);
|
|
19
|
+
const rootB = find(b);
|
|
20
|
+
if (rootA !== rootB) {
|
|
21
|
+
parent.set(rootA, rootB);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
return { find, union };
|
|
25
|
+
};
|
|
26
|
+
// npm versions installed for each name, normalized, for anchor lookups.
|
|
27
|
+
const collectNpmVersions = (graph) => {
|
|
28
|
+
const versionsByName = new Map();
|
|
29
|
+
for (const [name, resolutions] of Object.entries(graph)) {
|
|
30
|
+
const versions = new Set();
|
|
31
|
+
for (const resolution of resolutions) {
|
|
32
|
+
if (!resolution.isNpm)
|
|
33
|
+
continue;
|
|
34
|
+
const parsed = semver.parse(resolution.version);
|
|
35
|
+
if (parsed)
|
|
36
|
+
versions.add(parsed.version);
|
|
37
|
+
}
|
|
38
|
+
versionsByName.set(name, versions);
|
|
39
|
+
}
|
|
40
|
+
return versionsByName;
|
|
41
|
+
};
|
|
42
|
+
// A dependency edge `A@version --(range)--> B` is "co-version" when the range
|
|
43
|
+
// is anchored on the same version A itself carries, regardless of the caret /
|
|
44
|
+
// tilde operator (`^8.61.0`, `8.61.0` and `~8.61.0` are all co-version with
|
|
45
|
+
// `8.61.0`). This distinguishes a lockstep-published family member from an
|
|
46
|
+
// ordinary external dependency such as `semver`/`debug`.
|
|
47
|
+
//
|
|
48
|
+
// The anchor also has to be a version of B the lock actually installs, or the
|
|
49
|
+
// equality is a coincidence rather than a lockstep pin: `d@1.0.1` requesting
|
|
50
|
+
// `type@^1.0.1` anchors on `1.0.1`, yet the only installed `type` versions are
|
|
51
|
+
// `1.2.0` and `2.7.2`, so `d` and `type` do not move together. Lockfiles that
|
|
52
|
+
// store resolved versions instead of ranges (pnpm) satisfy this by
|
|
53
|
+
// construction; it brings the range-storing ones in line.
|
|
54
|
+
const isCoVersion = (version, range, depVersions) => {
|
|
55
|
+
const min = (() => {
|
|
56
|
+
try {
|
|
57
|
+
return semver.minVersion(range);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
63
|
+
if (min === null)
|
|
64
|
+
return false;
|
|
65
|
+
return semver.eq(min, version) && depVersions.has(min.version);
|
|
66
|
+
};
|
|
67
|
+
// Detect families of packages published in lockstep (versions move together)
|
|
68
|
+
// via union-find over the realized resolution graph. Names A and B are unioned
|
|
69
|
+
// when a real dependency edge A->B exists AND across every installed resolution
|
|
70
|
+
// where A requests B they carry the same version, that version being one the
|
|
71
|
+
// lock installs for B. Returns connected components
|
|
72
|
+
// of size > 1, members sorted, components ordered by their first member.
|
|
73
|
+
export const buildLockstepClusters = (graph) => {
|
|
74
|
+
const names = Object.keys(graph);
|
|
75
|
+
const unionFind = createUnionFind(names);
|
|
76
|
+
const versionsByName = collectNpmVersions(graph);
|
|
77
|
+
for (const [name, resolutions] of Object.entries(graph)) {
|
|
78
|
+
const observationsByDep = new Map();
|
|
79
|
+
for (const resolution of resolutions) {
|
|
80
|
+
if (!resolution.isNpm)
|
|
81
|
+
continue;
|
|
82
|
+
for (const [depName, range] of Object.entries(resolution.dependencies)) {
|
|
83
|
+
if (depName === name)
|
|
84
|
+
continue;
|
|
85
|
+
// edge must point to a package that actually exists in the lock
|
|
86
|
+
if (!graph[depName])
|
|
87
|
+
continue;
|
|
88
|
+
let observations = observationsByDep.get(depName);
|
|
89
|
+
if (!observations) {
|
|
90
|
+
observations = [];
|
|
91
|
+
observationsByDep.set(depName, observations);
|
|
92
|
+
}
|
|
93
|
+
observations.push({ version: resolution.version, range });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const [depName, observations] of observationsByDep) {
|
|
97
|
+
const depVersions = versionsByName.get(depName) ?? new Set();
|
|
98
|
+
const allCoVersion = observations.every((observation) => isCoVersion(observation.version, observation.range, depVersions));
|
|
99
|
+
if (allCoVersion) {
|
|
100
|
+
unionFind.union(name, depName);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const componentsByRoot = new Map();
|
|
105
|
+
for (const name of names) {
|
|
106
|
+
const root = unionFind.find(name);
|
|
107
|
+
let members = componentsByRoot.get(root);
|
|
108
|
+
if (!members) {
|
|
109
|
+
members = [];
|
|
110
|
+
componentsByRoot.set(root, members);
|
|
111
|
+
}
|
|
112
|
+
members.push(name);
|
|
113
|
+
}
|
|
114
|
+
return [...componentsByRoot.values()]
|
|
115
|
+
.filter((members) => members.length > 1)
|
|
116
|
+
.map((members) => members.toSorted((a, b) => a.localeCompare(b)))
|
|
117
|
+
.toSorted((a, b) => a[0].localeCompare(b[0]));
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=buildLockstepClusters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildLockstepClusters.js","sourceRoot":"","sources":["../src/buildLockstepClusters.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAqB5B,MAAM,eAAe,GAAG,CAAC,KAAe,EAAa,EAAE;IACrD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAiB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAE1E,MAAM,IAAI,GAAG,CAAC,KAAa,EAAU,EAAE;QACrC,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACjC,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC;QAC3B,CAAC;QACD,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,OAAO,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACzB,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,CAAS,EAAQ,EAAE;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;YACpB,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzB,CAAC,CAAC;AAEF,wEAAwE;AACxE,MAAM,kBAAkB,GAAG,CAAC,KAAoB,EAA4B,EAAE;IAC5E,MAAM,cAAc,GAAG,IAAI,GAAG,EAAuB,CAAC;IAEtD,KAAK,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;QACnC,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;YACrC,IAAI,CAAC,UAAU,CAAC,KAAK;gBAAE,SAAS;YAChC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YAChD,IAAI,MAAM;gBAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC3C,CAAC;QACD,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC,CAAC;AAEF,8EAA8E;AAC9E,8EAA8E;AAC9E,4EAA4E;AAC5E,2EAA2E;AAC3E,yDAAyD;AACzD,EAAE;AACF,8EAA8E;AAC9E,6EAA6E;AAC7E,+EAA+E;AAC/E,8EAA8E;AAC9E,mEAAmE;AACnE,0DAA0D;AAC1D,MAAM,WAAW,GAAG,CAClB,OAAe,EACf,KAAa,EACb,WAAwB,EACf,EAAE;IACX,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE;QAChB,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IACL,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC/B,OAAO,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACjE,CAAC,CAAC;AAEF,6EAA6E;AAC7E,+EAA+E;AAC/E,gFAAgF;AAChF,6EAA6E;AAC7E,oDAAoD;AACpD,yEAAyE;AACzE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAoB,EAAc,EAAE;IACxE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,SAAS,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACzC,MAAM,cAAc,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAEjD,KAAK,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAG9B,CAAC;QAEJ,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;YACrC,IAAI,CAAC,UAAU,CAAC,KAAK;gBAAE,SAAS;YAEhC,KAAK,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBACvE,IAAI,OAAO,KAAK,IAAI;oBAAE,SAAS;gBAC/B,gEAAgE;gBAChE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;oBAAE,SAAS;gBAE9B,IAAI,YAAY,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAClD,IAAI,CAAC,YAAY,EAAE,CAAC;oBAClB,YAAY,GAAG,EAAE,CAAC;oBAClB,iBAAiB,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;gBAC/C,CAAC;gBACD,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;QAED,KAAK,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,IAAI,iBAAiB,EAAE,CAAC;YACxD,MAAM,WAAW,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,GAAG,EAAU,CAAC;YACrE,MAAM,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,EAAE,CACtD,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,CACjE,CAAC;YACF,IAAI,YAAY,EAAE,CAAC;gBACjB,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAoB,CAAC;IACrD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,EAAE,CAAC;YACb,gBAAgB,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;IAED,OAAO,CAAC,GAAG,gBAAgB,CAAC,MAAM,EAAE,CAAC;SAClC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;SACvC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;SAChE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;AACpD,CAAC,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { buildLockstepClusters, } from "./buildLockstepClusters.js";
|
|
3
|
+
const npm = (version, dependencies = {}) => ({
|
|
4
|
+
version,
|
|
5
|
+
isNpm: true,
|
|
6
|
+
dependencies,
|
|
7
|
+
});
|
|
8
|
+
describe("buildLockstepClusters", () => {
|
|
9
|
+
it("unions a co-versioned family across caret and exact pins", () => {
|
|
10
|
+
// a@1 and a@2 both request b/c at their own version; b requests c at its
|
|
11
|
+
// own version. `noise` is requested at an unrelated version by everyone.
|
|
12
|
+
const graph = {
|
|
13
|
+
a: [
|
|
14
|
+
npm("1.0.0", { b: "1.0.0", c: "^1.0.0", noise: "^3.0.0" }),
|
|
15
|
+
npm("2.0.0", { b: "2.0.0", c: "^2.0.0", noise: "^3.0.0" }),
|
|
16
|
+
],
|
|
17
|
+
b: [npm("1.0.0", { c: "1.0.0" }), npm("2.0.0", { c: "2.0.0" })],
|
|
18
|
+
c: [npm("1.0.0"), npm("2.0.0")],
|
|
19
|
+
noise: [npm("3.1.0"), npm("3.2.0")],
|
|
20
|
+
};
|
|
21
|
+
const clusters = buildLockstepClusters(graph);
|
|
22
|
+
expect(clusters).toEqual([["a", "b", "c"]]);
|
|
23
|
+
});
|
|
24
|
+
it("does not union when a version observation breaks co-version", () => {
|
|
25
|
+
// a@2 requests b at b's matching version, but a@1 requests b at a stale
|
|
26
|
+
// version: not every observation co-versions, so no union.
|
|
27
|
+
const graph = {
|
|
28
|
+
a: [npm("1.0.0", { b: "2.0.0" }), npm("2.0.0", { b: "2.0.0" })],
|
|
29
|
+
b: [npm("1.0.0"), npm("2.0.0")],
|
|
30
|
+
};
|
|
31
|
+
expect(buildLockstepClusters(graph)).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
it("does not union when the anchor version is not installed", () => {
|
|
34
|
+
// Real case: `d@1.0.1` requests `type@^1.0.1`, so the range anchors on
|
|
35
|
+
// d's own version by coincidence — no `type@1.0.1` is installed, the
|
|
36
|
+
// request resolves to `type@1.2.0`, and the two do not move together.
|
|
37
|
+
const graph = {
|
|
38
|
+
d: [npm("1.0.1", { type: "^1.0.1" })],
|
|
39
|
+
esniff: [npm("2.0.1", { d: "^1.0.1", type: "^2.7.2" })],
|
|
40
|
+
type: [npm("1.2.0"), npm("2.7.2")],
|
|
41
|
+
};
|
|
42
|
+
expect(buildLockstepClusters(graph)).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
it("unions when the anchor version is installed alongside others", () => {
|
|
45
|
+
// Same shape, but the lock does carry `b@1.0.0`: the pin is real, and the
|
|
46
|
+
// duplicate `b@1.2.0` is what the cluster pass exists to collapse.
|
|
47
|
+
const graph = {
|
|
48
|
+
a: [npm("1.0.0", { b: "^1.0.0" })],
|
|
49
|
+
b: [npm("1.0.0"), npm("1.2.0")],
|
|
50
|
+
};
|
|
51
|
+
expect(buildLockstepClusters(graph)).toEqual([["a", "b"]]);
|
|
52
|
+
});
|
|
53
|
+
it("keeps unioning pnpm-style edges that store the resolved version", () => {
|
|
54
|
+
// pnpm records the resolved version, so the anchor is installed by
|
|
55
|
+
// construction and detection is unchanged.
|
|
56
|
+
const graph = {
|
|
57
|
+
a: [npm("7.28.0", { b: "7.28.0" })],
|
|
58
|
+
b: [npm("7.28.0")],
|
|
59
|
+
};
|
|
60
|
+
expect(buildLockstepClusters(graph)).toEqual([["a", "b"]]);
|
|
61
|
+
});
|
|
62
|
+
it("ignores edges to packages absent from the graph and non-npm resolutions", () => {
|
|
63
|
+
const graph = {
|
|
64
|
+
a: [npm("1.0.0", { missing: "1.0.0", b: "1.0.0" })],
|
|
65
|
+
b: [npm("1.0.0")],
|
|
66
|
+
w: [{ version: "", isNpm: false, dependencies: { a: "1.0.0" } }],
|
|
67
|
+
};
|
|
68
|
+
expect(buildLockstepClusters(graph)).toEqual([["a", "b"]]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=buildLockstepClusters.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildLockstepClusters.test.js","sourceRoot":"","sources":["../src/buildLockstepClusters.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const leadingPrefixes = (member) => {
|
|
2
|
+
const scopeEnd = member.startsWith("@") ? member.indexOf("/") : -1;
|
|
3
|
+
const scope = scopeEnd === -1 ? null : member.slice(0, scopeEnd);
|
|
4
|
+
const rest = scope === null ? member : member.slice(scopeEnd + 1);
|
|
5
|
+
const segments = rest.split("-");
|
|
6
|
+
const withinRest = segments.map((_segment, index) => {
|
|
7
|
+
const joined = segments.slice(0, index + 1).join("-");
|
|
8
|
+
return scope === null ? joined : `${scope}/${joined}`;
|
|
9
|
+
});
|
|
10
|
+
return scope === null ? withinRest : [scope, ...withinRest];
|
|
11
|
+
};
|
|
12
|
+
const covers = (prefix, member) => member === prefix ||
|
|
13
|
+
member.startsWith(`${prefix}-`) ||
|
|
14
|
+
member.startsWith(`${prefix}/`);
|
|
15
|
+
/**
|
|
16
|
+
* The prefix shared by the most members. Ties go to the longest prefix, so a
|
|
17
|
+
* family named after a scope wins over the bare package name it also matches.
|
|
18
|
+
*/
|
|
19
|
+
const bestGroup = (members) => {
|
|
20
|
+
const candidates = [...new Set(members.flatMap(leadingPrefixes))];
|
|
21
|
+
let best = null;
|
|
22
|
+
for (const prefix of candidates.toSorted((a, b) => a.localeCompare(b))) {
|
|
23
|
+
const covered = members.filter((member) => covers(prefix, member));
|
|
24
|
+
if (best === null ||
|
|
25
|
+
covered.length > best.covered.length ||
|
|
26
|
+
(covered.length === best.covered.length &&
|
|
27
|
+
prefix.length > best.prefix.length)) {
|
|
28
|
+
best = { prefix, covered };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return best;
|
|
32
|
+
};
|
|
33
|
+
const formatGroup = ({ prefix, covered }) => {
|
|
34
|
+
if (covered.length === 1 && covered[0] === prefix)
|
|
35
|
+
return prefix;
|
|
36
|
+
if (prefix.startsWith("@") && !prefix.includes("/"))
|
|
37
|
+
return `${prefix}/*`;
|
|
38
|
+
return covered.includes(prefix) ? `${prefix}*` : `${prefix}-*`;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A readable identity for a lockstep cluster, derived from the prefixes its
|
|
42
|
+
* members share: `@typescript-eslint/* (+1 more)`, `metro* + @react-native/*`.
|
|
43
|
+
* A second group is only worth naming when it covers more than one member;
|
|
44
|
+
* anything left over is counted, never guessed at.
|
|
45
|
+
*/
|
|
46
|
+
export const clusterLabel = (members) => {
|
|
47
|
+
const first = bestGroup(members);
|
|
48
|
+
if (first === null)
|
|
49
|
+
return "";
|
|
50
|
+
const groups = [first];
|
|
51
|
+
const remaining = members.filter((member) => !covers(first.prefix, member));
|
|
52
|
+
const second = remaining.length > 0 ? bestGroup(remaining) : null;
|
|
53
|
+
if (second !== null && second.covered.length > 1)
|
|
54
|
+
groups.push(second);
|
|
55
|
+
const uncovered = members.filter((member) => !groups.some((group) => covers(group.prefix, member))).length;
|
|
56
|
+
const label = groups.map(formatGroup).join(" + ");
|
|
57
|
+
return uncovered === 0 ? label : `${label} (+${uncovered} more)`;
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=clusterLabel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clusterLabel.js","sourceRoot":"","sources":["../src/clusterLabel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it } from "bun:test";
|
|
2
|
+
import { strictEqual } from "node:assert/strict";
|
|
3
|
+
import { clusterLabel } from "./clusterLabel.js";
|
|
4
|
+
const typescriptEslint = [
|
|
5
|
+
"@typescript-eslint/eslint-plugin",
|
|
6
|
+
"@typescript-eslint/parser",
|
|
7
|
+
"@typescript-eslint/project-service",
|
|
8
|
+
"@typescript-eslint/scope-manager",
|
|
9
|
+
"@typescript-eslint/tsconfig-utils",
|
|
10
|
+
"@typescript-eslint/type-utils",
|
|
11
|
+
"@typescript-eslint/types",
|
|
12
|
+
"@typescript-eslint/typescript-estree",
|
|
13
|
+
"@typescript-eslint/utils",
|
|
14
|
+
"@typescript-eslint/visitor-keys",
|
|
15
|
+
"typescript-eslint",
|
|
16
|
+
];
|
|
17
|
+
const metroFamily = [
|
|
18
|
+
"@react-native/asset-utils",
|
|
19
|
+
"@react-native/babel-plugin-codegen",
|
|
20
|
+
"@react-native/babel-preset",
|
|
21
|
+
"@react-native/codegen",
|
|
22
|
+
"metro",
|
|
23
|
+
"metro-cache",
|
|
24
|
+
"metro-config",
|
|
25
|
+
"metro-core",
|
|
26
|
+
"ob1",
|
|
27
|
+
"react-native",
|
|
28
|
+
];
|
|
29
|
+
describe("clusterLabel", () => {
|
|
30
|
+
it("names a family by its shared scope", () => {
|
|
31
|
+
strictEqual(clusterLabel(typescriptEslint), "@typescript-eslint/* (+1 more)");
|
|
32
|
+
});
|
|
33
|
+
it("names the two largest groups and counts the rest", () => {
|
|
34
|
+
strictEqual(clusterLabel(metroFamily), "@react-native/* + metro* (+2 more)");
|
|
35
|
+
});
|
|
36
|
+
it("uses a dash prefix when no member carries the prefix itself", () => {
|
|
37
|
+
strictEqual(clusterLabel([
|
|
38
|
+
"babel-plugin-syntax-hermes-parser",
|
|
39
|
+
"hermes-estree",
|
|
40
|
+
"hermes-parser",
|
|
41
|
+
]), "hermes-* (+1 more)");
|
|
42
|
+
});
|
|
43
|
+
it("keeps the member name when it is the prefix", () => {
|
|
44
|
+
strictEqual(clusterLabel(["mini-metro", "mini-metro-config"]), "mini-metro*");
|
|
45
|
+
});
|
|
46
|
+
it("names a single member exactly", () => {
|
|
47
|
+
strictEqual(clusterLabel(["metro"]), "metro");
|
|
48
|
+
});
|
|
49
|
+
it("does not name a second group that covers one member", () => {
|
|
50
|
+
strictEqual(clusterLabel(["metro", "metro-config", "ob1"]), "metro* (+1 more)");
|
|
51
|
+
});
|
|
52
|
+
it("returns an empty label for no members", () => {
|
|
53
|
+
strictEqual(clusterLabel([]), "");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=clusterLabel.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clusterLabel.test.js","sourceRoot":"","sources":["../src/clusterLabel.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,gBAAgB,GAAG;IACvB,kCAAkC;IAClC,2BAA2B;IAC3B,oCAAoC;IACpC,kCAAkC;IAClC,mCAAmC;IACnC,+BAA+B;IAC/B,0BAA0B;IAC1B,sCAAsC;IACtC,0BAA0B;IAC1B,iCAAiC;IACjC,mBAAmB;CACpB,CAAC;AAEF,MAAM,WAAW,GAAG;IAClB,2BAA2B;IAC3B,oCAAoC;IACpC,4BAA4B;IAC5B,uBAAuB;IACvB,OAAO;IACP,aAAa;IACb,cAAc;IACd,YAAY;IACZ,KAAK;IACL,cAAc;CACf,CAAC;AAEF,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,WAAW,CACT,YAAY,CAAC,gBAAgB,CAAC,EAC9B,gCAAgC,CACjC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,WAAW,CACT,YAAY,CAAC,WAAW,CAAC,EACzB,oCAAoC,CACrC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,WAAW,CACT,YAAY,CAAC;YACX,mCAAmC;YACnC,eAAe;YACf,eAAe;SAChB,CAAC,EACF,oBAAoB,CACrB,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,WAAW,CACT,YAAY,CAAC,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAAC,EACjD,aAAa,CACd,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,WAAW,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,WAAW,CACT,YAAY,CAAC,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,CAAC,EAC9C,kBAAkB,CACnB,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import semver from "semver";
|
|
2
|
+
/**
|
|
3
|
+
* A version satisfying every dependent holds the maximal count, so it always
|
|
4
|
+
* sorts first: "the highest version that satisfies everyone" falls out of this
|
|
5
|
+
* ordering instead of needing a pass of its own.
|
|
6
|
+
*/
|
|
7
|
+
export const createCandidateVersionComparator = ({ satisfiedCountOf } = {}) => (versionA, versionB) => {
|
|
8
|
+
if (satisfiedCountOf) {
|
|
9
|
+
const satisfiesA = satisfiedCountOf(versionA);
|
|
10
|
+
const satisfiesB = satisfiedCountOf(versionB);
|
|
11
|
+
if (satisfiesB > satisfiesA)
|
|
12
|
+
return 1;
|
|
13
|
+
if (satisfiesB < satisfiesA)
|
|
14
|
+
return -1;
|
|
15
|
+
}
|
|
16
|
+
return semver.rcompare(versionA, versionB);
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=compareCandidateVersions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compareCandidateVersions.js","sourceRoot":"","sources":["../src/compareCandidateVersions.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAO5B;;;;GAIG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAC3C,CAAC,EAAE,gBAAgB,EAAE,GAAsC,EAAE,EAAE,EAAE,CACjE,CAAC,QAAgB,EAAE,QAAgB,EAAU,EAAE;IAC7C,IAAI,gBAAgB,EAAE,CAAC;QACrB,MAAM,UAAU,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,UAAU,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,UAAU,GAAG,UAAU;YAAE,OAAO,CAAC,CAAC;QACtC,IAAI,UAAU,GAAG,UAAU;YAAE,OAAO,CAAC,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC7C,CAAC,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { createCandidateVersionComparator } from "./compareCandidateVersions.js";
|
|
3
|
+
const satisfiedCounts = new Map([
|
|
4
|
+
["1.0.0", 1],
|
|
5
|
+
["1.5.0", 3],
|
|
6
|
+
["2.0.0", 3],
|
|
7
|
+
["3.0.0", 2],
|
|
8
|
+
]);
|
|
9
|
+
const satisfiedCountOf = (version) => satisfiedCounts.get(version) ?? 0;
|
|
10
|
+
describe("createCandidateVersionComparator", () => {
|
|
11
|
+
it("puts the version satisfying the most dependents first", () => {
|
|
12
|
+
const compare = createCandidateVersionComparator({ satisfiedCountOf });
|
|
13
|
+
expect(["1.0.0", "3.0.0", "1.5.0"].toSorted(compare)).toEqual([
|
|
14
|
+
"1.5.0",
|
|
15
|
+
"3.0.0",
|
|
16
|
+
"1.0.0",
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
it("breaks a tie on the highest version", () => {
|
|
20
|
+
const compare = createCandidateVersionComparator({ satisfiedCountOf });
|
|
21
|
+
expect(["1.5.0", "2.0.0"].toSorted(compare)).toEqual(["2.0.0", "1.5.0"]);
|
|
22
|
+
});
|
|
23
|
+
it("ranks on semver alone without a satisfied count", () => {
|
|
24
|
+
const compare = createCandidateVersionComparator();
|
|
25
|
+
expect(["1.0.0", "3.0.0", "1.5.0"].toSorted(compare)).toEqual([
|
|
26
|
+
"3.0.0",
|
|
27
|
+
"1.5.0",
|
|
28
|
+
"1.0.0",
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=compareCandidateVersions.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compareCandidateVersions.test.js","sourceRoot":"","sources":["../src/compareCandidateVersions.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AAEjF,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,CAAC,OAAO,EAAE,CAAC,CAAC;IACZ,CAAC,OAAO,EAAE,CAAC,CAAC;IACZ,CAAC,OAAO,EAAE,CAAC,CAAC;IACZ,CAAC,OAAO,EAAE,CAAC,CAAC;CACb,CAAC,CAAC;AACH,MAAM,gBAAgB,GAAG,CAAC,OAAe,EAAU,EAAE,CACnD,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpC,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;IAChD,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,OAAO,GAAG,gCAAgC,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAEvE,MAAM,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;YAC5D,OAAO;YACP,OAAO;YACP,OAAO;SACR,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,OAAO,GAAG,gCAAgC,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAEvE,MAAM,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,OAAO,GAAG,gCAAgC,EAAE,CAAC;QAEnD,MAAM,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;YAC5D,OAAO;YACP,OAAO;YACP,OAAO;SACR,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import picomatch from "picomatch";
|
|
2
|
+
import { clusterLabel } from "./clusterLabel.js";
|
|
3
|
+
const scopePattern = (scope) => `${scope.startsWith("@") ? scope : `@${scope}`}/**`;
|
|
4
|
+
const toPatterns = (names, scopes) => [
|
|
5
|
+
...names,
|
|
6
|
+
...scopes.map(scopePattern),
|
|
7
|
+
];
|
|
8
|
+
// picomatch throws on an empty pattern list, and "no pattern" has to mean "no
|
|
9
|
+
// opinion" rather than "matches nothing".
|
|
10
|
+
const compile = (patterns) => patterns.length === 0 ? () => false : picomatch(patterns);
|
|
11
|
+
/**
|
|
12
|
+
* Selects the packages a dedupe run is allowed to touch. Deduplicating a large
|
|
13
|
+
* lockfile in one pass produces a diff no one reviews, so every tool exposes
|
|
14
|
+
* this as the way to work through it a family at a time.
|
|
15
|
+
*
|
|
16
|
+
* A package is selected when the include list is empty or one of its patterns
|
|
17
|
+
* matches, and no exclude pattern matches — exclusion always wins, so a scope
|
|
18
|
+
* can be selected with a few of its packages held back.
|
|
19
|
+
*
|
|
20
|
+
* Patterns are globs with path semantics (`*` stops at `/`), the same as the
|
|
21
|
+
* `why-duplicate` filters: `@babel/*` matches `@babel/core`, a bare `*` does
|
|
22
|
+
* not. npm names carry no glob metacharacter, so a plain name is still an
|
|
23
|
+
* exact match.
|
|
24
|
+
*/
|
|
25
|
+
export const createPackageFilter = ({ include = [], exclude = [], includeScopes = [], excludeScopes = [], } = {}) => {
|
|
26
|
+
const includePatterns = toPatterns(include, includeScopes);
|
|
27
|
+
const excludePatterns = toPatterns(exclude, excludeScopes);
|
|
28
|
+
const isIncluded = compile(includePatterns);
|
|
29
|
+
const isExcluded = compile(excludePatterns);
|
|
30
|
+
const rejectionReason = (packageName) => {
|
|
31
|
+
if (isExcluded(packageName))
|
|
32
|
+
return "excluded";
|
|
33
|
+
if (includePatterns.length > 0 && !isIncluded(packageName)) {
|
|
34
|
+
return "not selected";
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
selectsEverything: includePatterns.length === 0 && excludePatterns.length === 0,
|
|
40
|
+
selects: (packageName) => rejectionReason(packageName) === undefined,
|
|
41
|
+
rejectionReason,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
// A map keyed by package name is where the filter applies exactly: an unselected
|
|
45
|
+
// package keeps every resolution it has, rather than being partly rewritten.
|
|
46
|
+
export const selectPackages = (packages, filter) => filter.selectsEverything
|
|
47
|
+
? packages
|
|
48
|
+
: Object.fromEntries(Object.entries(packages).filter(([packageName]) => filter.selects(packageName)));
|
|
49
|
+
/**
|
|
50
|
+
* A cluster fix moves its whole family in lockstep, so it is kept only when
|
|
51
|
+
* every member is selected: applying it for part of a family would move the
|
|
52
|
+
* rest anyway, past the filter the user set. Select a family with a scope, or
|
|
53
|
+
* by naming all of its members.
|
|
54
|
+
*/
|
|
55
|
+
export const selectClusterFixes = (fixes, filter) => {
|
|
56
|
+
if (filter.selectsEverything)
|
|
57
|
+
return { selected: fixes, skipped: [] };
|
|
58
|
+
const selected = [];
|
|
59
|
+
const skipped = [];
|
|
60
|
+
for (const fix of fixes) {
|
|
61
|
+
const blockedBy = fix.members.filter((member) => !filter.selects(member));
|
|
62
|
+
if (blockedBy.length === 0) {
|
|
63
|
+
selected.push(fix);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
skipped.push({ fix, blockedBy });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { selected, skipped };
|
|
70
|
+
};
|
|
71
|
+
// A family can block on a dozen members, and naming them all buries the point.
|
|
72
|
+
const namedBlockers = 3;
|
|
73
|
+
// Phrased for the `skipped` list of an apply plan, so both tools explain a
|
|
74
|
+
// filtered-out family the same way.
|
|
75
|
+
export const describeSkippedClusterFix = ({ fix, blockedBy, }) => {
|
|
76
|
+
const named = blockedBy.slice(0, namedBlockers).join(", ");
|
|
77
|
+
const rest = blockedBy.length - namedBlockers;
|
|
78
|
+
const blockers = rest > 0 ? `${named} (+${rest} more)` : named;
|
|
79
|
+
return `cluster ${clusterLabel(fix.members)}: ${blockers} not selected by the filter, and a family only converges as a whole`;
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=createPackageFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createPackageFilter.js","sourceRoot":"","sources":["../src/createPackageFilter.ts"],"names":[],"mappings":"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"}
|