@pnpm/installing.deps-resolver 1100.2.7 → 1100.2.8
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/CHANGELOG.md +4409 -0
- package/lib/hoistPeers.d.ts +7 -2
- package/lib/hoistPeers.js +18 -12
- package/lib/resolveDependencies.js +33 -1
- package/lib/resolvePeers.js +82 -0
- package/package.json +30 -30
package/lib/hoistPeers.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { PreferredVersions } from '@pnpm/resolving.resolver-base';
|
|
2
|
-
|
|
2
|
+
/** One workspace-root dependency that a missing peer can be satisfied with. */
|
|
3
|
+
export interface HoistableRootDep {
|
|
4
|
+
alias: string;
|
|
5
|
+
pkgName: string;
|
|
6
|
+
normalizedBareSpecifier?: string;
|
|
7
|
+
}
|
|
3
8
|
export declare function hoistPeers(opts: {
|
|
4
9
|
autoInstallPeers: boolean;
|
|
5
10
|
allPreferredVersions?: PreferredVersions;
|
|
6
|
-
workspaceRootDeps:
|
|
11
|
+
workspaceRootDeps: HoistableRootDep[];
|
|
7
12
|
}, missingRequiredPeers: Array<[string, {
|
|
8
13
|
range: string;
|
|
9
14
|
}]>): Record<string, string>;
|
package/lib/hoistPeers.js
CHANGED
|
@@ -9,7 +9,7 @@ export function hoistPeers(opts, missingRequiredPeers) {
|
|
|
9
9
|
continue;
|
|
10
10
|
}
|
|
11
11
|
const rootDep = opts.workspaceRootDeps
|
|
12
|
-
.filter((rootDep) => rootDep.
|
|
12
|
+
.filter((rootDep) => rootDep.pkgName === peerName)
|
|
13
13
|
.sort((rootDep1, rootDep2) => lexCompare(rootDep1.alias, rootDep2.alias))[0];
|
|
14
14
|
if (rootDep?.normalizedBareSpecifier) {
|
|
15
15
|
dependencies[peerName] = rootDep.normalizedBareSpecifier;
|
|
@@ -27,22 +27,28 @@ export function hoistPeers(opts, missingRequiredPeers) {
|
|
|
27
27
|
nonVersions.push(spec);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
// Dedupe onto a preferred version only when it actually satisfies the
|
|
31
|
+
// wanted peer range. Picking the highest preferred version regardless of
|
|
32
|
+
// the range lets a version resolved for one importer be auto-installed as
|
|
33
|
+
// another importer's peer even though nothing in that importer's closure
|
|
34
|
+
// accepts it, silently producing a peer graph that mixes incompatible
|
|
35
|
+
// majors. Ranges that are not semver (workspace:, npm: aliases, dist-tags)
|
|
36
|
+
// cannot be checked, so they keep the dedupe-to-highest behavior.
|
|
37
|
+
const isSemverRange = semver.validRange(range, { includePrerelease: true }) != null;
|
|
38
|
+
const satisfyingVersion = isSemverRange
|
|
37
39
|
? semver.maxSatisfying(versions, range, { includePrerelease: true })
|
|
38
40
|
: null;
|
|
39
41
|
if (satisfyingVersion) {
|
|
40
42
|
dependencies[peerName] = [satisfyingVersion, ...nonVersions].join(' || ');
|
|
41
43
|
}
|
|
42
|
-
else if (
|
|
43
|
-
//
|
|
44
|
-
// Use the range directly so pnpm resolves it from the registry
|
|
45
|
-
|
|
44
|
+
else if (isSemverRange && versions.length > 0) {
|
|
45
|
+
// Preferred versions exist but none satisfies the wanted range.
|
|
46
|
+
// Use the range directly so pnpm resolves it from the registry rather
|
|
47
|
+
// than installing a version the peer explicitly rejects. Without
|
|
48
|
+
// autoInstallPeers, hoist nothing and leave the peer missing.
|
|
49
|
+
if (opts.autoInstallPeers) {
|
|
50
|
+
dependencies[peerName] = range;
|
|
51
|
+
}
|
|
46
52
|
}
|
|
47
53
|
else {
|
|
48
54
|
dependencies[peerName] = [semver.maxSatisfying(versions, '*', { includePrerelease: true }), ...nonVersions]
|
|
@@ -54,7 +54,7 @@ export async function resolveRootDependencies(ctx, importers) {
|
|
|
54
54
|
let workspaceRootDeps;
|
|
55
55
|
if (ctx.resolvePeersFromWorkspaceRoot) {
|
|
56
56
|
const rootImporterIndex = importers.findIndex(({ options }) => options.parentIds[0] === '.');
|
|
57
|
-
workspaceRootDeps = pkgAddressesByImportersWithoutPeers[rootImporterIndex]?.pkgAddresses ?? [];
|
|
57
|
+
workspaceRootDeps = getHoistableRootDeps(importers[rootImporterIndex], pkgAddressesByImportersWithoutPeers[rootImporterIndex]?.pkgAddresses ?? []);
|
|
58
58
|
}
|
|
59
59
|
else {
|
|
60
60
|
workspaceRootDeps = [];
|
|
@@ -150,6 +150,38 @@ export async function resolveRootDependencies(ctx, importers) {
|
|
|
150
150
|
time,
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Lists the workspace-root dependencies that `hoistPeers` may satisfy a
|
|
155
|
+
* missing peer with. A root dependency reused from the lockfile skips full
|
|
156
|
+
* resolution, so it has no address (or an address without a
|
|
157
|
+
* normalizedBareSpecifier); its wanted specifier is used instead, so that
|
|
158
|
+
* re-resolving with a lockfile hoists the same version as a fresh install of
|
|
159
|
+
* the same manifest.
|
|
160
|
+
*/
|
|
161
|
+
function getHoistableRootDeps(rootImporter, rootPkgAddresses) {
|
|
162
|
+
const wantedSpecifierByAlias = new Map();
|
|
163
|
+
for (const wantedDep of rootImporter?.wantedDependencies ?? []) {
|
|
164
|
+
if (wantedDep.alias && wantedDep.bareSpecifier) {
|
|
165
|
+
wantedSpecifierByAlias.set(wantedDep.alias, wantedDep.bareSpecifier);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const rootDeps = rootPkgAddresses.map((pkgAddress) => ({
|
|
169
|
+
alias: pkgAddress.alias,
|
|
170
|
+
pkgName: pkgAddress.pkg.name,
|
|
171
|
+
normalizedBareSpecifier: pkgAddress.normalizedBareSpecifier ?? wantedSpecifierByAlias.get(pkgAddress.alias),
|
|
172
|
+
}));
|
|
173
|
+
const coveredAliases = new Set(rootDeps.map(({ alias }) => alias));
|
|
174
|
+
for (const [alias, bareSpecifier] of wantedSpecifierByAlias) {
|
|
175
|
+
if (coveredAliases.has(alias))
|
|
176
|
+
continue;
|
|
177
|
+
rootDeps.push({
|
|
178
|
+
alias,
|
|
179
|
+
pkgName: unwrapPackageName(alias, bareSpecifier).pkgName,
|
|
180
|
+
normalizedBareSpecifier: bareSpecifier,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return rootDeps;
|
|
184
|
+
}
|
|
153
185
|
async function resolveDependenciesOfImporters(ctx, importers) {
|
|
154
186
|
const pickLowestVersion = ctx.resolutionMode === 'time-based' || ctx.resolutionMode === 'lowest-direct';
|
|
155
187
|
const resolveResults = await Promise.all(importers.map(async (importer) => {
|
package/lib/resolvePeers.js
CHANGED
|
@@ -13,6 +13,9 @@ export async function resolvePeers(opts) {
|
|
|
13
13
|
const depGraph = {};
|
|
14
14
|
const pathsByNodeId = new Map();
|
|
15
15
|
const pathsByNodeIdPromises = new Map();
|
|
16
|
+
const awaitedPeerNodeIdsByNodeId = new Map();
|
|
17
|
+
const peersCacheOwnerByNodeId = new Map();
|
|
18
|
+
const cycleBrokenNodeIds = new Set();
|
|
16
19
|
const depPathsByPkgId = new Map();
|
|
17
20
|
const nodeIdsByPreviousDepPath = opts.resolvedPeerProviderPaths == null
|
|
18
21
|
? new Map()
|
|
@@ -76,6 +79,9 @@ export async function resolvePeers(opts) {
|
|
|
76
79
|
parentDepPathsChain: [],
|
|
77
80
|
pathsByNodeId,
|
|
78
81
|
pathsByNodeIdPromises,
|
|
82
|
+
awaitedPeerNodeIdsByNodeId,
|
|
83
|
+
peersCacheOwnerByNodeId,
|
|
84
|
+
cycleBrokenNodeIds,
|
|
79
85
|
depPathsByPkgId,
|
|
80
86
|
nodeIdsByPreviousDepPath,
|
|
81
87
|
resolvedPeerProviderPaths: opts.resolvedPeerProviderPaths,
|
|
@@ -102,6 +108,9 @@ export async function resolvePeers(opts) {
|
|
|
102
108
|
// its cycle analysis only sees the children of one call, and providers
|
|
103
109
|
// frequently peer-depend on each other, so resolving them one by one
|
|
104
110
|
// would leave their dep path calculations awaiting each other forever.
|
|
111
|
+
// A peer cycle that spans this call and the traversal above is still
|
|
112
|
+
// invisible here; breakDepPathAwaitCycles resolves those before the
|
|
113
|
+
// finishing promises are awaited.
|
|
105
114
|
const prunedProviderChildren = {};
|
|
106
115
|
for (const [alias, nodeId] of Object.entries(hoistedProviderChildren)) {
|
|
107
116
|
if (parentPkgsOfNode.has(nodeId))
|
|
@@ -122,6 +131,14 @@ export async function resolvePeers(opts) {
|
|
|
122
131
|
};
|
|
123
132
|
}
|
|
124
133
|
}
|
|
134
|
+
breakDepPathAwaitCycles({
|
|
135
|
+
awaitedPeerNodeIdsByNodeId,
|
|
136
|
+
peersCacheOwnerByNodeId,
|
|
137
|
+
cycleBrokenNodeIds,
|
|
138
|
+
pathsByNodeId,
|
|
139
|
+
pathsByNodeIdPromises,
|
|
140
|
+
dependenciesTree: opts.dependenciesTree,
|
|
141
|
+
});
|
|
125
142
|
await Promise.all(finishingList);
|
|
126
143
|
const depGraphWithResolvedChildren = resolveChildren(depGraph);
|
|
127
144
|
function resolveChildren(depGraph) {
|
|
@@ -168,6 +185,62 @@ export async function resolvePeers(opts) {
|
|
|
168
185
|
pathsByNodeId,
|
|
169
186
|
};
|
|
170
187
|
}
|
|
188
|
+
// Cycle analysis inside resolvePeersOfChildren only sees the children of a
|
|
189
|
+
// single call, but a dep path calculation may await a node resolved in a
|
|
190
|
+
// different call or at a different tree level (a hoisted peer provider and
|
|
191
|
+
// a consumer that peer-depend on each other, https://github.com/pnpm/pnpm/issues/12921).
|
|
192
|
+
// Such an await cycle never settles on its own, so before the finishing
|
|
193
|
+
// promises are awaited, walk the recorded await edges and resolve every
|
|
194
|
+
// dep path promise on a cycle to the peer's `name@version` — the same
|
|
195
|
+
// collapse in-call cycle detection applies (see calculateDepPath).
|
|
196
|
+
// A node that hit the peers cache awaits its dep path from the node that
|
|
197
|
+
// created the cache entry, so it borrows that owner's await edges.
|
|
198
|
+
function breakDepPathAwaitCycles(opts) {
|
|
199
|
+
const isSettled = (nodeId) => opts.pathsByNodeId.has(nodeId) || opts.cycleBrokenNodeIds.has(nodeId);
|
|
200
|
+
const keysByNodeId = new Map();
|
|
201
|
+
const nodeIdsByKey = new Map();
|
|
202
|
+
function keyOf(nodeId) {
|
|
203
|
+
let key = keysByNodeId.get(nodeId);
|
|
204
|
+
if (key == null) {
|
|
205
|
+
key = String(keysByNodeId.size);
|
|
206
|
+
keysByNodeId.set(nodeId, key);
|
|
207
|
+
nodeIdsByKey.set(key, nodeId);
|
|
208
|
+
}
|
|
209
|
+
return key;
|
|
210
|
+
}
|
|
211
|
+
const graphEntries = [];
|
|
212
|
+
const awaitingNodeIds = new Set([
|
|
213
|
+
...opts.awaitedPeerNodeIdsByNodeId.keys(),
|
|
214
|
+
...opts.peersCacheOwnerByNodeId.keys(),
|
|
215
|
+
]);
|
|
216
|
+
for (const nodeId of awaitingNodeIds) {
|
|
217
|
+
if (isSettled(nodeId))
|
|
218
|
+
continue;
|
|
219
|
+
const cacheOwnerNodeId = opts.peersCacheOwnerByNodeId.get(nodeId);
|
|
220
|
+
const awaitedNodeIds = opts.awaitedPeerNodeIdsByNodeId.get(nodeId) ??
|
|
221
|
+
(cacheOwnerNodeId == null ? undefined : opts.awaitedPeerNodeIdsByNodeId.get(cacheOwnerNodeId));
|
|
222
|
+
if (awaitedNodeIds == null)
|
|
223
|
+
continue;
|
|
224
|
+
const liveTargets = [];
|
|
225
|
+
for (const awaitedNodeId of awaitedNodeIds) {
|
|
226
|
+
if (!isSettled(awaitedNodeId)) {
|
|
227
|
+
liveTargets.push(keyOf(awaitedNodeId));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (liveTargets.length > 0) {
|
|
231
|
+
graphEntries.push([keyOf(nodeId), liveTargets]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (graphEntries.length === 0)
|
|
235
|
+
return;
|
|
236
|
+
const { cycles } = analyzeGraph(graphEntries);
|
|
237
|
+
for (const key of new Set(cycles.flat())) {
|
|
238
|
+
const nodeId = nodeIdsByKey.get(key);
|
|
239
|
+
const { name, version } = opts.dependenciesTree.get(nodeId).resolvedPackage;
|
|
240
|
+
opts.cycleBrokenNodeIds.add(nodeId);
|
|
241
|
+
opts.pathsByNodeIdPromises.get(nodeId)?.resolve(`${name}@${version}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
171
244
|
function nodeDepsCount(node) {
|
|
172
245
|
return Object.keys(node.children).length + node.resolvedPeerNames.size;
|
|
173
246
|
}
|
|
@@ -389,6 +462,7 @@ async function resolvePeersOfNode(currentAlias, nodeId, parentParentPkgs, ctx) {
|
|
|
389
462
|
wantedRange,
|
|
390
463
|
});
|
|
391
464
|
}
|
|
465
|
+
ctx.peersCacheOwnerByNodeId.set(nodeId, hit.ownerNodeId);
|
|
392
466
|
return {
|
|
393
467
|
missingPeers: hit.missingPeers,
|
|
394
468
|
finishing: (async () => {
|
|
@@ -451,6 +525,7 @@ async function resolvePeersOfNode(currentAlias, nodeId, parentParentPkgs, ctx) {
|
|
|
451
525
|
missingPeers: allMissingPeers,
|
|
452
526
|
depPath: pDefer(),
|
|
453
527
|
resolvedPeers: allResolvedPeers,
|
|
528
|
+
ownerNodeId: nodeId,
|
|
454
529
|
};
|
|
455
530
|
if (ctx.peersCache.has(resolvedPackage.pkgIdWithPatchHash)) {
|
|
456
531
|
ctx.peersCache.get(resolvedPackage.pkgIdWithPatchHash).push(cache);
|
|
@@ -512,6 +587,7 @@ async function resolvePeersOfNode(currentAlias, nodeId, parentParentPkgs, ctx) {
|
|
|
512
587
|
if (cyclicPeerAliases.has(pendingPeer.alias)) {
|
|
513
588
|
const { name, version } = ctx.dependenciesTree.get(pendingPeer.nodeId)?.resolvedPackage;
|
|
514
589
|
const id = `${name}@${version}`;
|
|
590
|
+
ctx.cycleBrokenNodeIds.add(pendingPeer.nodeId);
|
|
515
591
|
ctx.pathsByNodeIdPromises.get(pendingPeer.nodeId)?.resolve(id);
|
|
516
592
|
return id;
|
|
517
593
|
}
|
|
@@ -521,6 +597,12 @@ async function resolvePeersOfNode(currentAlias, nodeId, parentParentPkgs, ctx) {
|
|
|
521
597
|
return { name: peerNode.resolvedPackage.name, version: peerNode.resolvedPackage.version };
|
|
522
598
|
}
|
|
523
599
|
}
|
|
600
|
+
let awaitedPeerNodeIds = ctx.awaitedPeerNodeIdsByNodeId.get(nodeId);
|
|
601
|
+
if (awaitedPeerNodeIds == null) {
|
|
602
|
+
awaitedPeerNodeIds = new Set();
|
|
603
|
+
ctx.awaitedPeerNodeIdsByNodeId.set(nodeId, awaitedPeerNodeIds);
|
|
604
|
+
}
|
|
605
|
+
awaitedPeerNodeIds.add(pendingPeer.nodeId);
|
|
524
606
|
return ctx.pathsByNodeIdPromises.get(pendingPeer.nodeId).promise;
|
|
525
607
|
})),
|
|
526
608
|
], ctx.peersSuffixMaxLength);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/installing.deps-resolver",
|
|
3
|
-
"version": "1100.2.
|
|
3
|
+
"version": "1100.2.8",
|
|
4
4
|
"description": "Resolves dependency graph of a package",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -27,38 +27,23 @@
|
|
|
27
27
|
"!*.map"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@pnpm/util.lex-comparator": "^4.0.1",
|
|
31
|
-
"@yarnpkg/core": "4.8.0",
|
|
32
|
-
"graph-cycles": "3.0.0",
|
|
33
|
-
"is-inner-link": "^5.0.0",
|
|
34
|
-
"is-subdir": "^2.0.0",
|
|
35
|
-
"normalize-path": "^3.0.0",
|
|
36
|
-
"p-defer": "^4.0.1",
|
|
37
|
-
"path-exists": "^5.0.0",
|
|
38
|
-
"promise-share": "^2.0.1",
|
|
39
|
-
"ramda": "npm:@pnpm/ramda@0.28.1",
|
|
40
|
-
"rename-overwrite": "^7.0.1",
|
|
41
|
-
"safe-promise-defer": "^2.0.0",
|
|
42
|
-
"semver": "^7.8.4",
|
|
43
|
-
"semver-range-intersect": "^0.3.1",
|
|
44
|
-
"validate-npm-package-name": "7.0.2",
|
|
45
|
-
"version-selector-type": "^3.0.0",
|
|
46
30
|
"@pnpm/catalogs.resolver": "1100.0.0",
|
|
47
|
-
"@pnpm/config.version-policy": "1100.1.6",
|
|
48
31
|
"@pnpm/catalogs.types": "1100.0.0",
|
|
32
|
+
"@pnpm/config.version-policy": "1100.1.6",
|
|
49
33
|
"@pnpm/constants": "1100.0.0",
|
|
50
|
-
"@pnpm/deps.graph-hasher": "1100.2.8",
|
|
51
34
|
"@pnpm/core-loggers": "1100.2.1",
|
|
52
|
-
"@pnpm/deps.
|
|
35
|
+
"@pnpm/deps.graph-hasher": "1100.2.9",
|
|
53
36
|
"@pnpm/deps.path": "1100.0.8",
|
|
37
|
+
"@pnpm/deps.peer-range": "1100.0.2",
|
|
54
38
|
"@pnpm/error": "1100.0.1",
|
|
55
|
-
"@pnpm/fetching.pick-fetcher": "1100.0
|
|
56
|
-
"@pnpm/hooks.types": "1100.1.1",
|
|
57
|
-
"@pnpm/lockfile.preferred-versions": "1100.0.18",
|
|
58
|
-
"@pnpm/lockfile.pruner": "1100.0.13",
|
|
39
|
+
"@pnpm/fetching.pick-fetcher": "1100.1.0",
|
|
59
40
|
"@pnpm/fs.symlink-dependency": "1100.0.10",
|
|
41
|
+
"@pnpm/hooks.types": "1100.2.0",
|
|
42
|
+
"@pnpm/lockfile.preferred-versions": "1100.0.19",
|
|
43
|
+
"@pnpm/lockfile.pruner": "1100.0.13",
|
|
60
44
|
"@pnpm/lockfile.types": "1100.0.13",
|
|
61
|
-
"@pnpm/lockfile.utils": "1100.1.
|
|
45
|
+
"@pnpm/lockfile.utils": "1100.1.2",
|
|
46
|
+
"@pnpm/patching.config": "1100.0.9",
|
|
62
47
|
"@pnpm/patching.types": "1100.0.0",
|
|
63
48
|
"@pnpm/pkg-manifest.reader": "1100.0.9",
|
|
64
49
|
"@pnpm/pkg-manifest.utils": "1100.2.6",
|
|
@@ -66,20 +51,35 @@
|
|
|
66
51
|
"@pnpm/resolving.resolver-base": "1100.5.1",
|
|
67
52
|
"@pnpm/store.controller-types": "1100.1.7",
|
|
68
53
|
"@pnpm/types": "1101.3.2",
|
|
69
|
-
"@pnpm/
|
|
70
|
-
"@pnpm/workspace.spec-parser": "1100.0.0"
|
|
54
|
+
"@pnpm/util.lex-comparator": "^4.0.1",
|
|
55
|
+
"@pnpm/workspace.spec-parser": "1100.0.0",
|
|
56
|
+
"@yarnpkg/core": "4.8.0",
|
|
57
|
+
"graph-cycles": "3.0.0",
|
|
58
|
+
"is-inner-link": "^5.0.0",
|
|
59
|
+
"is-subdir": "^2.0.0",
|
|
60
|
+
"normalize-path": "^3.0.0",
|
|
61
|
+
"p-defer": "^4.0.1",
|
|
62
|
+
"path-exists": "^5.0.0",
|
|
63
|
+
"promise-share": "^2.0.1",
|
|
64
|
+
"ramda": "npm:@pnpm/ramda@0.28.1",
|
|
65
|
+
"rename-overwrite": "^7.0.1",
|
|
66
|
+
"safe-promise-defer": "^2.0.0",
|
|
67
|
+
"semver": "^7.8.4",
|
|
68
|
+
"semver-range-intersect": "^0.3.1",
|
|
69
|
+
"validate-npm-package-name": "7.0.2",
|
|
70
|
+
"version-selector-type": "^3.0.0"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"@pnpm/logger": "^1100.0.0"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@jest/globals": "30.4.1",
|
|
77
|
+
"@pnpm/installing.deps-resolver": "1100.2.8",
|
|
78
|
+
"@pnpm/logger": "1100.0.0",
|
|
77
79
|
"@types/normalize-path": "^3.0.2",
|
|
78
80
|
"@types/ramda": "0.31.1",
|
|
79
81
|
"@types/semver": "7.7.1",
|
|
80
|
-
"@types/validate-npm-package-name": "^4.0.2"
|
|
81
|
-
"@pnpm/installing.deps-resolver": "1100.2.7",
|
|
82
|
-
"@pnpm/logger": "1100.0.0"
|
|
82
|
+
"@types/validate-npm-package-name": "^4.0.2"
|
|
83
83
|
},
|
|
84
84
|
"engines": {
|
|
85
85
|
"node": ">=22.13"
|