@pnpm/installing.commands 1100.2.2 → 1100.3.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/lib/fetch.js +1 -0
- package/lib/import/index.js +1 -0
- package/lib/install.js +5 -0
- package/lib/installDeps.d.ts +1 -1
- package/lib/installDeps.js +41 -11
- package/lib/link.js +3 -3
- package/lib/policyHandlers.d.ts +68 -0
- package/lib/policyHandlers.js +162 -0
- package/lib/recursive.d.ts +3 -1
- package/lib/recursive.js +41 -7
- package/lib/remove.js +1 -0
- package/package.json +38 -37
package/lib/fetch.js
CHANGED
|
@@ -58,6 +58,7 @@ export async function handler(opts) {
|
|
|
58
58
|
pruneStore: true,
|
|
59
59
|
storeController: store.ctrl,
|
|
60
60
|
storeDir: store.dir,
|
|
61
|
+
resolutionVerifiers: store.resolutionVerifiers,
|
|
61
62
|
// Hoisting is skipped anyway,
|
|
62
63
|
// so we store these empty patterns in node_modules/.modules.yaml
|
|
63
64
|
// to let the subsequent install know that hoisting should be performed.
|
package/lib/import/index.js
CHANGED
package/lib/install.js
CHANGED
|
@@ -79,6 +79,11 @@ export const cliOptionsTypes = () => ({
|
|
|
79
79
|
'fix-lockfile': Boolean,
|
|
80
80
|
'resolution-only': Boolean,
|
|
81
81
|
recursive: Boolean,
|
|
82
|
+
// `--no-save` lets `pnpm install` skip writing to package.json /
|
|
83
|
+
// pnpm-workspace.yaml. Without registering it here, nopt drops the
|
|
84
|
+
// flag, `opts.save` stays undefined, and the auto-add path treats
|
|
85
|
+
// it as "save enabled".
|
|
86
|
+
save: Boolean,
|
|
82
87
|
});
|
|
83
88
|
export const shorthands = {
|
|
84
89
|
D: '--dev',
|
package/lib/installDeps.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type UpdateMatchingFunction } from '@pnpm/installing.deps-installer';
|
|
|
4
4
|
import type { LockfileObject } from '@pnpm/lockfile.types';
|
|
5
5
|
import { type CreateStoreControllerOptions } from '@pnpm/store.connection-manager';
|
|
6
6
|
import type { IncludedDependencies, PackageVulnerabilityAudit } from '@pnpm/types';
|
|
7
|
-
export type InstallDepsOptions = Pick<Config, 'autoInstallPeers' | 'bail' | 'bin' | 'catalogs' | 'catalogMode' | 'cleanupUnusedCatalogs' | 'dedupePeerDependents' | 'dedupePeers' | 'depth' | 'dev' | 'enableGlobalVirtualStore' | 'virtualStoreOnly' | 'engineStrict' | 'excludeLinksFromLockfile' | 'global' | 'globalPnpmfile' | 'ignoreCurrentSpecifiers' | 'ignorePnpmfile' | 'ignoreScripts' | 'optimisticRepeatInstall' | 'linkWorkspacePackages' | 'lockfileDir' | 'lockfileOnly' | 'agent' | 'production' | 'preferWorkspacePackages' | 'registries' | 'runtime' | 'runtimeOnFail' | 'save' | 'saveDev' | 'saveExact' | 'saveOptional' | 'savePeer' | 'savePrefix' | 'saveProd' | 'saveWorkspaceProtocol' | 'lockfileIncludeTarballUrl' | 'scriptsPrependNodePath' | 'scriptShell' | 'sideEffectsCache' | 'sideEffectsCacheReadonly' | 'sort' | 'sharedWorkspaceLockfile' | 'shellEmulator' | 'tag' | 'allowBuilds' | 'optional' | 'workspaceConcurrency' | 'workspaceDir' | 'workspacePackagePatterns' | 'extraEnv' | 'ignoreWorkspaceCycles' | 'disallowWorkspaceCycles' | 'configDependencies' | 'packageExtensions' | 'updateConfig'> & Pick<ConfigContext, 'allProjects' | 'allProjectsGraph' | 'cliOptions' | 'hooks' | 'rootProjectManifestDir' | 'rootProjectManifest' | 'selectedProjectsGraph'> & CreateStoreControllerOptions & {
|
|
7
|
+
export type InstallDepsOptions = Pick<Config, 'autoInstallPeers' | 'bail' | 'bin' | 'catalogs' | 'catalogMode' | 'cleanupUnusedCatalogs' | 'dedupePeerDependents' | 'dedupePeers' | 'depth' | 'dev' | 'enableGlobalVirtualStore' | 'virtualStoreOnly' | 'engineStrict' | 'excludeLinksFromLockfile' | 'global' | 'globalPnpmfile' | 'ignoreCurrentSpecifiers' | 'ignorePnpmfile' | 'ignoreScripts' | 'optimisticRepeatInstall' | 'linkWorkspacePackages' | 'lockfileDir' | 'lockfileOnly' | 'agent' | 'production' | 'preferWorkspacePackages' | 'registries' | 'runtime' | 'runtimeOnFail' | 'save' | 'saveDev' | 'saveExact' | 'saveOptional' | 'savePeer' | 'savePrefix' | 'saveProd' | 'saveWorkspaceProtocol' | 'lockfileIncludeTarballUrl' | 'scriptsPrependNodePath' | 'scriptShell' | 'sideEffectsCache' | 'sideEffectsCacheReadonly' | 'sort' | 'sharedWorkspaceLockfile' | 'shellEmulator' | 'tag' | 'allowBuilds' | 'optional' | 'workspaceConcurrency' | 'workspaceDir' | 'workspacePackagePatterns' | 'extraEnv' | 'ignoreWorkspaceCycles' | 'disallowWorkspaceCycles' | 'configDependencies' | 'packageExtensions' | 'updateConfig'> & Pick<ConfigContext, 'allProjects' | 'allProjectsGraph' | 'cliOptions' | 'hooks' | 'rootProjectManifestDir' | 'rootProjectManifest' | 'selectedProjectsGraph'> & Partial<Pick<Config, 'ci'>> & CreateStoreControllerOptions & {
|
|
8
8
|
argv: {
|
|
9
9
|
original: string[];
|
|
10
10
|
};
|
package/lib/installDeps.js
CHANGED
|
@@ -17,6 +17,7 @@ import { updateWorkspaceManifest } from '@pnpm/workspace.workspace-manifest-writ
|
|
|
17
17
|
import { getPinnedVersion } from './getPinnedVersion.js';
|
|
18
18
|
import { getSaveType } from './getSaveType.js';
|
|
19
19
|
import { handleIgnoredBuilds } from './handleIgnoredBuilds.js';
|
|
20
|
+
import { setupPolicyHandlers } from './policyHandlers.js';
|
|
20
21
|
import { createMatcher, makeIgnorePatterns, matchDependencies, recursive, } from './recursive.js';
|
|
21
22
|
import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies.js';
|
|
22
23
|
const OVERWRITE_UPDATE_OPTIONS = {
|
|
@@ -115,6 +116,12 @@ export async function installDeps(opts, params) {
|
|
|
115
116
|
else if (opts.runtimeOnFail) {
|
|
116
117
|
applyRuntimeOnFailOverride(manifest, opts.runtimeOnFail);
|
|
117
118
|
}
|
|
119
|
+
// `setupPolicyHandlers` composes the per-policy handlers the install
|
|
120
|
+
// needs for the current opts (today: minimumReleaseAge; future:
|
|
121
|
+
// trustPolicy UX, license policy, etc.). Returns `undefined` when no
|
|
122
|
+
// handler is active so the install skips the empty no-op call at
|
|
123
|
+
// every checkpoint when no policies are configured.
|
|
124
|
+
const policyHandlers = setupPolicyHandlers(opts);
|
|
118
125
|
const installOpts = {
|
|
119
126
|
...opts,
|
|
120
127
|
// In case installation is done in a multi-package repository
|
|
@@ -127,8 +134,10 @@ export async function installDeps(opts, params) {
|
|
|
127
134
|
skipRuntimes: opts.runtime === false,
|
|
128
135
|
storeController: store.ctrl,
|
|
129
136
|
storeDir: store.dir,
|
|
137
|
+
resolutionVerifiers: store.resolutionVerifiers,
|
|
130
138
|
workspacePackages,
|
|
131
139
|
preferredVersions: opts.packageVulnerabilityAudit ? preferNonvulnerablePackageVersions(opts.packageVulnerabilityAudit) : undefined,
|
|
140
|
+
handleResolutionPolicyViolations: policyHandlers?.handleResolutionPolicyViolations,
|
|
132
141
|
};
|
|
133
142
|
let updateMatch;
|
|
134
143
|
let updatePackageManifest = opts.updatePackageManifest;
|
|
@@ -187,14 +196,20 @@ export async function installDeps(opts, params) {
|
|
|
187
196
|
rootDir: opts.dir,
|
|
188
197
|
targetDependenciesField: getSaveType(opts),
|
|
189
198
|
};
|
|
190
|
-
const { updatedCatalogs, updatedProject, ignoredBuilds } = await mutateModulesInSingleProject(mutatedProject, installOpts);
|
|
199
|
+
const { updatedCatalogs, updatedProject, ignoredBuilds, resolutionPolicyViolations } = await mutateModulesInSingleProject(mutatedProject, installOpts);
|
|
191
200
|
if (opts.save !== false) {
|
|
201
|
+
// Only pick entries when we'll actually persist. Otherwise the
|
|
202
|
+
// info log would claim we added entries the workspace manifest
|
|
203
|
+
// never saw, and the next install would re-prompt or fail
|
|
204
|
+
// verification.
|
|
205
|
+
const policyUpdates = policyHandlers?.pickManifestUpdates(resolutionPolicyViolations);
|
|
192
206
|
await Promise.all([
|
|
193
207
|
writeProjectManifest(updatedProject.manifest),
|
|
194
208
|
updateWorkspaceManifest(opts.workspaceDir ?? opts.dir, {
|
|
195
209
|
updatedCatalogs,
|
|
196
210
|
cleanupUnusedCatalogs: opts.cleanupUnusedCatalogs,
|
|
197
211
|
allProjects: opts.allProjects,
|
|
212
|
+
...policyUpdates,
|
|
198
213
|
}),
|
|
199
214
|
]);
|
|
200
215
|
}
|
|
@@ -211,20 +226,35 @@ export async function installDeps(opts, params) {
|
|
|
211
226
|
await handleIgnoredBuilds(opts, ignoredBuilds);
|
|
212
227
|
return;
|
|
213
228
|
}
|
|
214
|
-
const { updatedCatalogs, updatedManifest, ignoredBuilds } = await install(manifest, {
|
|
229
|
+
const { updatedCatalogs, updatedManifest, ignoredBuilds, resolutionPolicyViolations } = await install(manifest, {
|
|
215
230
|
...installOpts,
|
|
216
231
|
updatePackageManifest,
|
|
217
232
|
updateMatching,
|
|
218
233
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
234
|
+
// `opts.save === false` (e.g. `--no-save`) means "don't persist anything
|
|
235
|
+
// from this install" — both package.json and the workspace manifest.
|
|
236
|
+
// Skip the pick so the info log doesn't claim entries were added that
|
|
237
|
+
// were never written; the next install will resurface them.
|
|
238
|
+
if (opts.save !== false) {
|
|
239
|
+
const policyUpdates = policyHandlers?.pickManifestUpdates(resolutionPolicyViolations);
|
|
240
|
+
if (opts.update === true) {
|
|
241
|
+
await Promise.all([
|
|
242
|
+
writeProjectManifest(updatedManifest),
|
|
243
|
+
updateWorkspaceManifest(opts.workspaceDir ?? opts.dir, {
|
|
244
|
+
updatedCatalogs,
|
|
245
|
+
cleanupUnusedCatalogs: opts.cleanupUnusedCatalogs,
|
|
246
|
+
allProjects,
|
|
247
|
+
...policyUpdates,
|
|
248
|
+
}),
|
|
249
|
+
]);
|
|
250
|
+
}
|
|
251
|
+
else if (policyUpdates != null) {
|
|
252
|
+
// Plain `pnpm install` (no --update, no params) wouldn't otherwise touch
|
|
253
|
+
// the workspace manifest. Persist the auto-policy patches anyway so any
|
|
254
|
+
// loose bypass (today: minimumReleaseAgeExclude) remains explicit on
|
|
255
|
+
// subsequent installs.
|
|
256
|
+
await updateWorkspaceManifest(opts.workspaceDir ?? opts.dir, policyUpdates);
|
|
257
|
+
}
|
|
228
258
|
}
|
|
229
259
|
await handleIgnoredBuilds(opts, ignoredBuilds);
|
|
230
260
|
if (opts.linkWorkspacePackages && opts.workspaceDir) {
|
package/lib/link.js
CHANGED
|
@@ -69,6 +69,9 @@ This might cause issues in your project. To resolve this, you may use the "file:
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
export async function handler(opts, params) {
|
|
72
|
+
if ((params == null) || (params.length === 0)) {
|
|
73
|
+
throw new PnpmError('LINK_BAD_PARAMS', 'You must provide a parameter. Usage: pnpm link <dir>');
|
|
74
|
+
}
|
|
72
75
|
let workspacePackagesArr;
|
|
73
76
|
let workspacePackages;
|
|
74
77
|
if (opts.workspaceDir) {
|
|
@@ -87,9 +90,6 @@ export async function handler(opts, params) {
|
|
|
87
90
|
binsDir: opts.bin,
|
|
88
91
|
});
|
|
89
92
|
const writeProjectManifest = await createProjectManifestWriter(opts.rootProjectManifestDir);
|
|
90
|
-
if ((params == null) || (params.length === 0)) {
|
|
91
|
-
throw new PnpmError('LINK_BAD_PARAMS', 'You must provide a parameter. Usage: pnpm link <dir>');
|
|
92
|
-
}
|
|
93
93
|
const [pkgPaths, pkgNames] = partition((inp) => isFilespec.test(inp), params);
|
|
94
94
|
if (pkgNames.length > 0) {
|
|
95
95
|
throw new PnpmError('LINK_BAD_PARAMS', `Cannot link by package name. Use a relative or absolute path instead, e.g. "pnpm link ./${pkgNames[0]}"`);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape returned by `installing/deps-installer`'s
|
|
3
|
+
* `collectResolutionPolicyViolations` and the inline accumulator on
|
|
4
|
+
* the resolveDependencies result. Re-declared locally so the commands
|
|
5
|
+
* layer can react without depending on the deps-installer's private
|
|
6
|
+
* install types.
|
|
7
|
+
*
|
|
8
|
+
* Verifier codes (today: `MINIMUM_RELEASE_AGE_VIOLATION` and
|
|
9
|
+
* `TRUST_DOWNGRADE`) are the contract surface for downstream UX.
|
|
10
|
+
* Each `PolicyHandler` below filters violations by code to decide
|
|
11
|
+
* what to do with them (prompt, persist to an exclude list, log,
|
|
12
|
+
* abort).
|
|
13
|
+
*/
|
|
14
|
+
export interface PolicyViolation {
|
|
15
|
+
name: string;
|
|
16
|
+
version: string;
|
|
17
|
+
code: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Workspace-manifest patch a per-policy handler can request. Each
|
|
22
|
+
* field maps to a `pnpm-workspace.yaml` exclude-list array; the
|
|
23
|
+
* install command forwards these to `updateWorkspaceManifest` so the
|
|
24
|
+
* workspace writer dedupes and appends them in one pass.
|
|
25
|
+
*
|
|
26
|
+
* New policies that want auto-persistence add their field here AND
|
|
27
|
+
* teach `updateWorkspaceManifest` how to honor it.
|
|
28
|
+
*/
|
|
29
|
+
export interface WorkspaceManifestPolicyUpdates {
|
|
30
|
+
addedMinimumReleaseAgeExcludes?: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Aggregated plan the install command consumes. The `handleResolutionPolicyViolations`
|
|
34
|
+
* call fans out across every registered handler in registration order;
|
|
35
|
+
* any handler can throw to abort. `pickManifestUpdates` merges the
|
|
36
|
+
* per-handler patches into one bag so the workspace writer runs once.
|
|
37
|
+
*/
|
|
38
|
+
export interface PolicyHandlersPlan {
|
|
39
|
+
handleResolutionPolicyViolations: (violations: readonly PolicyViolation[]) => Promise<void>;
|
|
40
|
+
pickManifestUpdates: (violations: readonly PolicyViolation[]) => WorkspaceManifestPolicyUpdates | undefined;
|
|
41
|
+
}
|
|
42
|
+
export interface PolicyHandlersOptions {
|
|
43
|
+
minimumReleaseAge?: number;
|
|
44
|
+
minimumReleaseAgeStrict?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Pass `false` for `--no-save` installs. Handlers that would
|
|
47
|
+
* persist to the workspace manifest refuse to enter modes where
|
|
48
|
+
* approval is durably required (today: strict minimumReleaseAge)
|
|
49
|
+
* so the prompt never offers an action it can't honor.
|
|
50
|
+
*/
|
|
51
|
+
save?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Override for CI detection. Defaults to `ci-info`'s `isCI` flag.
|
|
54
|
+
*/
|
|
55
|
+
ci?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Composes the per-policy handlers the install command needs for the
|
|
59
|
+
* current opts. Returns `undefined` only when no handler reports
|
|
60
|
+
* activity — saves the install command an empty no-op call at every
|
|
61
|
+
* checkpoint when no policies are configured.
|
|
62
|
+
*
|
|
63
|
+
* Today only the minimumReleaseAge handler is registered. Future
|
|
64
|
+
* policies (trustPolicy UX, license policy, etc.) plug in by
|
|
65
|
+
* exporting a sibling `create<Name>PolicyHandler(opts)` and getting
|
|
66
|
+
* pushed into the `handlers` list below.
|
|
67
|
+
*/
|
|
68
|
+
export declare function setupPolicyHandlers(opts: PolicyHandlersOptions): PolicyHandlersPlan | undefined;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
2
|
+
import { globalInfo } from '@pnpm/logger';
|
|
3
|
+
import { MINIMUM_RELEASE_AGE_VIOLATION_CODE } from '@pnpm/resolving.npm-resolver';
|
|
4
|
+
import { isCI } from 'ci-info';
|
|
5
|
+
import enquirer from 'enquirer';
|
|
6
|
+
/**
|
|
7
|
+
* Composes the per-policy handlers the install command needs for the
|
|
8
|
+
* current opts. Returns `undefined` only when no handler reports
|
|
9
|
+
* activity — saves the install command an empty no-op call at every
|
|
10
|
+
* checkpoint when no policies are configured.
|
|
11
|
+
*
|
|
12
|
+
* Today only the minimumReleaseAge handler is registered. Future
|
|
13
|
+
* policies (trustPolicy UX, license policy, etc.) plug in by
|
|
14
|
+
* exporting a sibling `create<Name>PolicyHandler(opts)` and getting
|
|
15
|
+
* pushed into the `handlers` list below.
|
|
16
|
+
*/
|
|
17
|
+
export function setupPolicyHandlers(opts) {
|
|
18
|
+
const handlers = [];
|
|
19
|
+
const minimumReleaseAge = createMinimumReleaseAgeHandler(opts);
|
|
20
|
+
if (minimumReleaseAge)
|
|
21
|
+
handlers.push(minimumReleaseAge);
|
|
22
|
+
if (handlers.length === 0)
|
|
23
|
+
return undefined;
|
|
24
|
+
return {
|
|
25
|
+
handleResolutionPolicyViolations: async (violations) => {
|
|
26
|
+
// Sequential, not parallel: a TTY prompt from handler N would
|
|
27
|
+
// race with a different prompt from N+1, and we want a clean
|
|
28
|
+
// throw to short-circuit before later handlers ask for input.
|
|
29
|
+
for (const handler of handlers) {
|
|
30
|
+
if (handler.handleResolutionPolicyViolations) {
|
|
31
|
+
// eslint-disable-next-line no-await-in-loop
|
|
32
|
+
await handler.handleResolutionPolicyViolations(violations);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
pickManifestUpdates: (violations) => {
|
|
37
|
+
const merged = {};
|
|
38
|
+
let any = false;
|
|
39
|
+
for (const handler of handlers) {
|
|
40
|
+
if (!handler.pickManifestUpdates)
|
|
41
|
+
continue;
|
|
42
|
+
const patch = handler.pickManifestUpdates(violations);
|
|
43
|
+
if (patch == null)
|
|
44
|
+
continue;
|
|
45
|
+
// Shallow merge — handlers own disjoint fields by convention,
|
|
46
|
+
// so there's no collision policy to encode here yet.
|
|
47
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
48
|
+
if (value == null)
|
|
49
|
+
continue;
|
|
50
|
+
merged[key] = value;
|
|
51
|
+
any = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return any ? merged : undefined;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* minimumReleaseAge policy handler.
|
|
60
|
+
*
|
|
61
|
+
* Loose mode (`minimumReleaseAgeStrict: false`) lets the resolver
|
|
62
|
+
* install versions newer than the cutoff and auto-persists them to
|
|
63
|
+
* `minimumReleaseAgeExclude`. Strict mode + an interactive TTY
|
|
64
|
+
* surfaces the full set of immature picks (direct AND transitive) at
|
|
65
|
+
* once via a confirm prompt — the install proceeds if the user
|
|
66
|
+
* approves, otherwise it aborts before touching the lockfile or
|
|
67
|
+
* package.json (#10488). Strict mode in CI or any other non-TTY
|
|
68
|
+
* context aborts hard with the same violation list so the failure
|
|
69
|
+
* pinpoints every offending entry, not just the first one the
|
|
70
|
+
* resolver picked.
|
|
71
|
+
*
|
|
72
|
+
* Strict mode combined with `--no-save` is rejected up-front — the
|
|
73
|
+
* approval prompt promises persistence the install command's
|
|
74
|
+
* `opts.save !== false` gate would block, leaving the lockfile
|
|
75
|
+
* holding approved-but-unlisted immature picks that the next install
|
|
76
|
+
* would reject.
|
|
77
|
+
*
|
|
78
|
+
* Returns `undefined` when minimumReleaseAge is not active.
|
|
79
|
+
*/
|
|
80
|
+
function createMinimumReleaseAgeHandler(opts) {
|
|
81
|
+
if (!opts.minimumReleaseAge)
|
|
82
|
+
return undefined;
|
|
83
|
+
const strictMode = opts.minimumReleaseAgeStrict === true;
|
|
84
|
+
const persistenceEnabled = opts.save !== false;
|
|
85
|
+
const inCi = opts.ci ?? isCI;
|
|
86
|
+
const canPrompt = !inCi && Boolean(process.stdin.isTTY);
|
|
87
|
+
return {
|
|
88
|
+
handleResolutionPolicyViolations: async (violations) => {
|
|
89
|
+
if (!strictMode)
|
|
90
|
+
return;
|
|
91
|
+
const immature = filterImmatureViolations(violations);
|
|
92
|
+
if (immature.length === 0)
|
|
93
|
+
return;
|
|
94
|
+
if (!persistenceEnabled) {
|
|
95
|
+
throw new PnpmError('STRICT_MIN_RELEASE_AGE_REQUIRES_SAVE', 'minimumReleaseAgeStrict cannot be combined with --no-save: ' +
|
|
96
|
+
'approval would require writing to minimumReleaseAgeExclude in pnpm-workspace.yaml, ' +
|
|
97
|
+
'which --no-save prevents.', {
|
|
98
|
+
hint: 'Drop --no-save so the exclude list can be persisted, or set ' +
|
|
99
|
+
'minimumReleaseAgeStrict: false to let the install proceed without prompting ' +
|
|
100
|
+
'(the lockfile would still trigger the auto-collect on the next normal install).',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (canPrompt) {
|
|
104
|
+
await promptForApproval(immature);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
throw failOnImmature(immature);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
pickManifestUpdates: (violations) => {
|
|
111
|
+
const entries = pickImmatureEntries(violations, strictMode);
|
|
112
|
+
return entries ? { addedMinimumReleaseAgeExcludes: entries } : undefined;
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function filterImmatureViolations(violations) {
|
|
117
|
+
return violations.filter((v) => v.code === MINIMUM_RELEASE_AGE_VIOLATION_CODE);
|
|
118
|
+
}
|
|
119
|
+
function pickImmatureEntries(violations, promptRequired) {
|
|
120
|
+
const immature = filterImmatureViolations(violations);
|
|
121
|
+
if (immature.length === 0)
|
|
122
|
+
return undefined;
|
|
123
|
+
const sorted = [...new Set(immature.map((v) => `${v.name}@${v.version}`))].sort();
|
|
124
|
+
// Strict-mode picks already passed through the approval prompt, so
|
|
125
|
+
// the log here only confirms what was persisted. Loose-mode picks
|
|
126
|
+
// haven't been announced anywhere else, so the same log doubles as
|
|
127
|
+
// the discovery notice.
|
|
128
|
+
const reason = promptRequired
|
|
129
|
+
? '(approved at the prompt)'
|
|
130
|
+
: '(loose mode allowed these immature versions)';
|
|
131
|
+
globalInfo(`Added ${sorted.length} ${sorted.length === 1 ? 'entry' : 'entries'} to minimumReleaseAgeExclude in pnpm-workspace.yaml ` +
|
|
132
|
+
`${reason}:\n ${sorted.join('\n ')}`);
|
|
133
|
+
return sorted;
|
|
134
|
+
}
|
|
135
|
+
function failOnImmature(immature) {
|
|
136
|
+
const sorted = [...immature].sort((a, b) => `${a.name}@${a.version}`.localeCompare(`${b.name}@${b.version}`));
|
|
137
|
+
const list = sorted.map((v) => ` ${v.name}@${v.version} ${v.reason}`).join('\n');
|
|
138
|
+
return new PnpmError('NO_MATURE_MATCHING_VERSION', `${sorted.length} ${sorted.length === 1 ? 'version does' : 'versions do'} not meet the minimumReleaseAge constraint:\n${list}`, {
|
|
139
|
+
hint: 'Run the install interactively to approve these picks, or add them to ' +
|
|
140
|
+
'minimumReleaseAgeExclude in pnpm-workspace.yaml, or wait for the packages ' +
|
|
141
|
+
'to mature past the configured cutoff.',
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async function promptForApproval(immature) {
|
|
145
|
+
const sorted = [...immature].sort((a, b) => `${a.name}@${a.version}`.localeCompare(`${b.name}@${b.version}`));
|
|
146
|
+
const message = `${sorted.length} ${sorted.length === 1 ? 'version does' : 'versions do'} not meet the minimumReleaseAge constraint:\n` +
|
|
147
|
+
sorted.map((v) => ` ${v.name}@${v.version}`).join('\n') + '\n' +
|
|
148
|
+
'Add to minimumReleaseAgeExclude in pnpm-workspace.yaml and proceed with the install?';
|
|
149
|
+
const answer = await enquirer.prompt({
|
|
150
|
+
type: 'confirm',
|
|
151
|
+
name: 'confirmed',
|
|
152
|
+
message,
|
|
153
|
+
initial: false,
|
|
154
|
+
});
|
|
155
|
+
if (!answer.confirmed) {
|
|
156
|
+
throw new PnpmError('MINIMUM_RELEASE_AGE_DENIED', 'Aborted: the immature versions were not approved.', {
|
|
157
|
+
hint: 'Re-run the install without `minimumReleaseAgeStrict: true` to allow these versions, ' +
|
|
158
|
+
'or wait for the packages to mature past the configured cutoff.',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=policyHandlers.js.map
|
package/lib/recursive.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { CommandHandler } from '@pnpm/cli.command';
|
|
|
2
2
|
import { type Config, type ConfigContext } from '@pnpm/config.reader';
|
|
3
3
|
import { type UpdateMatchingFunction } from '@pnpm/installing.deps-installer';
|
|
4
4
|
import type { PreferredVersions } from '@pnpm/resolving.resolver-base';
|
|
5
|
+
import type { ResolutionVerifier } from '@pnpm/resolving.resolver-base';
|
|
5
6
|
import { type CreateStoreControllerOptions } from '@pnpm/store.connection-manager';
|
|
6
7
|
import type { StoreController } from '@pnpm/store.controller';
|
|
7
8
|
import type { IncludedDependencies, Project, ProjectManifest, ProjectsGraph } from '@pnpm/types';
|
|
@@ -26,9 +27,10 @@ export type RecursiveOptions = CreateStoreControllerOptions & Pick<Config, 'bail
|
|
|
26
27
|
storeControllerAndDir?: {
|
|
27
28
|
ctrl: StoreController;
|
|
28
29
|
dir: string;
|
|
30
|
+
resolutionVerifiers: ResolutionVerifier[];
|
|
29
31
|
};
|
|
30
32
|
pnpmfile: string[];
|
|
31
|
-
} & Partial<Pick<Config, 'sort' | 'strictDepBuilds' | 'workspaceConcurrency'>> & Required<Pick<Config, 'workspaceDir'>>;
|
|
33
|
+
} & Partial<Pick<Config, 'ci' | 'sort' | 'strictDepBuilds' | 'workspaceConcurrency'>> & Required<Pick<Config, 'workspaceDir'>>;
|
|
32
34
|
export type CommandFullName = 'install' | 'add' | 'remove' | 'update' | 'import';
|
|
33
35
|
export declare function recursive(allProjects: Project[], params: string[], opts: RecursiveOptions, cmdFullName: CommandFullName): Promise<boolean | string>;
|
|
34
36
|
export declare function matchDependencies(match: (input: string) => string | null, manifest: ProjectManifest, include: IncludedDependencies): string[];
|
package/lib/recursive.js
CHANGED
|
@@ -18,6 +18,7 @@ import pLimit from 'p-limit';
|
|
|
18
18
|
import { getPinnedVersion } from './getPinnedVersion.js';
|
|
19
19
|
import { getSaveType } from './getSaveType.js';
|
|
20
20
|
import { handleIgnoredBuilds } from './handleIgnoredBuilds.js';
|
|
21
|
+
import { setupPolicyHandlers } from './policyHandlers.js';
|
|
21
22
|
import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies.js';
|
|
22
23
|
export async function recursive(allProjects, params, opts, cmdFullName) {
|
|
23
24
|
if (allProjects.length === 0) {
|
|
@@ -33,6 +34,12 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
|
|
|
33
34
|
const store = opts.storeControllerAndDir ?? await createStoreController(opts);
|
|
34
35
|
const workspacePackages = arrayOfWorkspacePackagesToMap(allProjects);
|
|
35
36
|
const targetDependenciesField = getSaveType(opts);
|
|
37
|
+
// See `installDeps.ts` for context; mirrored here so workspace-recursive
|
|
38
|
+
// installs also surface immature picks (loose-mode auto-persist or
|
|
39
|
+
// strict-mode prompt). The workspace manifest writer dedupes against the
|
|
40
|
+
// existing list, so a single drain at the end captures additions across
|
|
41
|
+
// every project.
|
|
42
|
+
const policyHandlers = setupPolicyHandlers(opts);
|
|
36
43
|
const installOpts = Object.assign(opts, {
|
|
37
44
|
allProjects: getAllProjects(manifestsByPath, opts.allProjectsGraph, opts.sort),
|
|
38
45
|
linkWorkspacePackagesDepth: opts.linkWorkspacePackages === 'deep' ? Infinity : opts.linkWorkspacePackages ? 0 : -1,
|
|
@@ -46,7 +53,9 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
|
|
|
46
53
|
storeController: store.ctrl,
|
|
47
54
|
storeDir: store.dir,
|
|
48
55
|
targetDependenciesField,
|
|
56
|
+
resolutionVerifiers: store.resolutionVerifiers,
|
|
49
57
|
workspacePackages,
|
|
58
|
+
handleResolutionPolicyViolations: policyHandlers?.handleResolutionPolicyViolations,
|
|
50
59
|
});
|
|
51
60
|
const result = {};
|
|
52
61
|
const projectConfigRecord = createProjectConfigRecord(opts);
|
|
@@ -167,11 +176,17 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
|
|
|
167
176
|
if ((mutatedImporters.length === 0) && cmdFullName === 'update' && opts.depth === 0) {
|
|
168
177
|
throw new PnpmError('NO_PACKAGE_IN_DEPENDENCIES', 'None of the specified packages were found in the dependencies of any of the projects.');
|
|
169
178
|
}
|
|
170
|
-
const { updatedCatalogs, updatedProjects: mutatedPkgs, ignoredBuilds, } = await mutateModules(mutatedImporters, {
|
|
179
|
+
const { updatedCatalogs, updatedProjects: mutatedPkgs, ignoredBuilds, resolutionPolicyViolations, } = await mutateModules(mutatedImporters, {
|
|
171
180
|
...installOpts,
|
|
172
181
|
storeController: store.ctrl,
|
|
182
|
+
resolutionVerifiers: store.resolutionVerifiers,
|
|
173
183
|
});
|
|
174
184
|
if (opts.save !== false) {
|
|
185
|
+
// Only pick entries when we'll actually persist. Otherwise the
|
|
186
|
+
// info log would claim entries were added that the workspace
|
|
187
|
+
// manifest never saw, and the next install would re-prompt or
|
|
188
|
+
// fail verification.
|
|
189
|
+
const policyUpdates = policyHandlers?.pickManifestUpdates(resolutionPolicyViolations);
|
|
175
190
|
const promises = mutatedPkgs.map(async ({ originalManifest, manifest, rootDir }) => {
|
|
176
191
|
return manifestsByPath[rootDir].writeProjectManifest(originalManifest ?? manifest);
|
|
177
192
|
});
|
|
@@ -179,6 +194,7 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
|
|
|
179
194
|
updatedCatalogs,
|
|
180
195
|
cleanupUnusedCatalogs: opts.cleanupUnusedCatalogs,
|
|
181
196
|
allProjects,
|
|
197
|
+
...policyUpdates,
|
|
182
198
|
}));
|
|
183
199
|
await Promise.all(promises);
|
|
184
200
|
}
|
|
@@ -188,6 +204,10 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
|
|
|
188
204
|
const pkgPaths = Object.keys(opts.selectedProjectsGraph).sort();
|
|
189
205
|
let updatedCatalogs;
|
|
190
206
|
const allIgnoredBuilds = new Set();
|
|
207
|
+
// Each per-project install returns its own slice of lockfile-resolution
|
|
208
|
+
// violations; accumulate them here so the post-loop persist step can
|
|
209
|
+
// dedup and write a single batch to the workspace manifest.
|
|
210
|
+
const allResolutionPolicyViolations = [];
|
|
191
211
|
const limitInstallation = pLimit(getWorkspaceConcurrency(opts.workspaceConcurrency));
|
|
192
212
|
await Promise.all(pkgPaths.map(async (rootDir) => limitInstallation(async () => {
|
|
193
213
|
const hooks = opts.ignorePnpmfile
|
|
@@ -239,6 +259,7 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
|
|
|
239
259
|
updatedCatalogs: undefined, // there's no reason to add new or update catalogs on `pnpm remove`
|
|
240
260
|
updatedManifest: mutationResult.updatedProjects[0].manifest,
|
|
241
261
|
ignoredBuilds: mutationResult.ignoredBuilds,
|
|
262
|
+
resolutionPolicyViolations: mutationResult.resolutionPolicyViolations,
|
|
242
263
|
};
|
|
243
264
|
};
|
|
244
265
|
break;
|
|
@@ -249,7 +270,7 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
|
|
|
249
270
|
break;
|
|
250
271
|
}
|
|
251
272
|
const localConfig = getProjectConfig(manifest) ?? {};
|
|
252
|
-
const { updatedCatalogs: newCatalogsAddition, updatedManifest: newManifest, ignoredBuilds, } = await action(manifest, {
|
|
273
|
+
const { updatedCatalogs: newCatalogsAddition, updatedManifest: newManifest, ignoredBuilds, resolutionPolicyViolations, } = await action(manifest, {
|
|
253
274
|
...installOpts,
|
|
254
275
|
...localConfig,
|
|
255
276
|
...opts.allProjectsGraph[rootDir]?.package,
|
|
@@ -263,6 +284,7 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
|
|
|
263
284
|
}),
|
|
264
285
|
configByUri: installOpts.configByUri,
|
|
265
286
|
storeController: store.ctrl,
|
|
287
|
+
resolutionVerifiers: store.resolutionVerifiers,
|
|
266
288
|
});
|
|
267
289
|
if (opts.save !== false) {
|
|
268
290
|
await writeProjectManifest(newManifest);
|
|
@@ -276,6 +298,11 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
|
|
|
276
298
|
allIgnoredBuilds.add(depPath);
|
|
277
299
|
}
|
|
278
300
|
}
|
|
301
|
+
if (resolutionPolicyViolations?.length) {
|
|
302
|
+
for (const violation of resolutionPolicyViolations) {
|
|
303
|
+
allResolutionPolicyViolations.push(violation);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
279
306
|
result[rootDir].status = 'passed';
|
|
280
307
|
}
|
|
281
308
|
catch (err) { // eslint-disable-line
|
|
@@ -294,11 +321,18 @@ export async function recursive(allProjects, params, opts, cmdFullName) {
|
|
|
294
321
|
}
|
|
295
322
|
})));
|
|
296
323
|
await handleIgnoredBuilds(opts, allIgnoredBuilds.size ? allIgnoredBuilds : undefined);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
324
|
+
if (opts.save !== false) {
|
|
325
|
+
// Only pick entries when we'll actually persist. Otherwise the
|
|
326
|
+
// info log would claim entries were added that the workspace
|
|
327
|
+
// manifest never saw, mirroring the gate the shared-lockfile
|
|
328
|
+
// branch + installDeps already apply.
|
|
329
|
+
await updateWorkspaceManifest(opts.workspaceDir, {
|
|
330
|
+
updatedCatalogs,
|
|
331
|
+
cleanupUnusedCatalogs: opts.cleanupUnusedCatalogs,
|
|
332
|
+
allProjects,
|
|
333
|
+
...policyHandlers?.pickManifestUpdates(allResolutionPolicyViolations),
|
|
334
|
+
});
|
|
335
|
+
}
|
|
302
336
|
if (!opts.lockfileOnly && !opts.ignoreScripts && (cmdFullName === 'add' ||
|
|
303
337
|
cmdFullName === 'install' ||
|
|
304
338
|
cmdFullName === 'update')) {
|
package/lib/remove.js
CHANGED
|
@@ -135,6 +135,7 @@ export async function handler(opts, params) {
|
|
|
135
135
|
linkWorkspacePackagesDepth: opts.linkWorkspacePackages === 'deep' ? Infinity : opts.linkWorkspacePackages ? 0 : -1,
|
|
136
136
|
storeController: store.ctrl,
|
|
137
137
|
storeDir: store.dir,
|
|
138
|
+
resolutionVerifiers: store.resolutionVerifiers,
|
|
138
139
|
include,
|
|
139
140
|
});
|
|
140
141
|
const allProjects = opts.allProjects ?? (opts.workspaceDir
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/installing.commands",
|
|
3
|
-
"version": "1100.
|
|
3
|
+
"version": "1100.3.0",
|
|
4
4
|
"description": "Commands for installation",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@zkochan/rimraf": "^4.0.0",
|
|
34
34
|
"@zkochan/table": "^2.0.1",
|
|
35
35
|
"chalk": "^5.6.0",
|
|
36
|
+
"ci-info": "^4.3.0",
|
|
36
37
|
"enquirer": "^2.4.1",
|
|
37
38
|
"get-npm-tarball-url": "^2.1.0",
|
|
38
39
|
"is-subdir": "^2.0.0",
|
|
@@ -43,45 +44,46 @@
|
|
|
43
44
|
"ramda": "npm:@pnpm/ramda@0.28.1",
|
|
44
45
|
"render-help": "^2.0.0",
|
|
45
46
|
"version-selector-type": "^3.0.0",
|
|
46
|
-
"@pnpm/building.after-install": "1101.0.
|
|
47
|
+
"@pnpm/building.after-install": "1101.0.13",
|
|
47
48
|
"@pnpm/catalogs.types": "1100.0.0",
|
|
48
49
|
"@pnpm/cli.command": "1100.0.1",
|
|
49
50
|
"@pnpm/cli.common-cli-options-help": "1100.0.1",
|
|
50
|
-
"@pnpm/cli.utils": "1101.0.4",
|
|
51
|
-
"@pnpm/config.matcher": "1100.0.1",
|
|
52
51
|
"@pnpm/config.pick-registry-for-package": "1100.0.3",
|
|
53
|
-
"@pnpm/config.
|
|
52
|
+
"@pnpm/config.matcher": "1100.0.1",
|
|
53
|
+
"@pnpm/cli.utils": "1101.0.5",
|
|
54
|
+
"@pnpm/config.writer": "1100.0.8",
|
|
55
|
+
"@pnpm/deps.status": "1100.0.16",
|
|
54
56
|
"@pnpm/constants": "1100.0.0",
|
|
55
|
-
"@pnpm/deps.inspection.outdated": "1100.0.15",
|
|
56
57
|
"@pnpm/deps.path": "1100.0.3",
|
|
57
|
-
"@pnpm/deps.status": "1100.0.15",
|
|
58
58
|
"@pnpm/fs.graceful-fs": "1100.1.0",
|
|
59
|
-
"@pnpm/error": "1100.0.0",
|
|
60
59
|
"@pnpm/fs.read-modules-dir": "1100.0.1",
|
|
61
|
-
"@pnpm/
|
|
62
|
-
"@pnpm/
|
|
63
|
-
"@pnpm/
|
|
64
|
-
"@pnpm/
|
|
65
|
-
"@pnpm/
|
|
66
|
-
"@pnpm/installing.
|
|
67
|
-
"@pnpm/
|
|
60
|
+
"@pnpm/config.reader": "1101.3.2",
|
|
61
|
+
"@pnpm/error": "1100.0.0",
|
|
62
|
+
"@pnpm/installing.context": "1100.0.11",
|
|
63
|
+
"@pnpm/global.commands": "1100.0.18",
|
|
64
|
+
"@pnpm/hooks.pnpmfile": "1100.0.9",
|
|
65
|
+
"@pnpm/installing.dedupe.check": "1100.0.6",
|
|
66
|
+
"@pnpm/installing.deps-installer": "1101.2.0",
|
|
67
|
+
"@pnpm/installing.env-installer": "1101.0.10",
|
|
68
|
+
"@pnpm/lockfile.types": "1100.0.6",
|
|
68
69
|
"@pnpm/pkg-manifest.reader": "1100.0.3",
|
|
69
|
-
"@pnpm/pkg-manifest.utils": "1100.1.3",
|
|
70
70
|
"@pnpm/resolving.parse-wanted-dependency": "1100.0.1",
|
|
71
|
-
"@pnpm/
|
|
72
|
-
"@pnpm/
|
|
71
|
+
"@pnpm/pkg-manifest.utils": "1100.1.4",
|
|
72
|
+
"@pnpm/resolving.resolver-base": "1100.2.0",
|
|
73
73
|
"@pnpm/types": "1101.1.0",
|
|
74
|
-
"@pnpm/
|
|
75
|
-
"@pnpm/
|
|
74
|
+
"@pnpm/resolving.npm-resolver": "1101.2.0",
|
|
75
|
+
"@pnpm/deps.inspection.outdated": "1100.0.16",
|
|
76
|
+
"@pnpm/store.connection-manager": "1100.2.0",
|
|
77
|
+
"@pnpm/workspace.project-manifest-reader": "1100.0.6",
|
|
78
|
+
"@pnpm/workspace.projects-filter": "1100.0.12",
|
|
76
79
|
"@pnpm/workspace.project-manifest-writer": "1100.0.3",
|
|
77
|
-
"@pnpm/store.
|
|
78
|
-
"@pnpm/workspace.projects-
|
|
79
|
-
"@pnpm/workspace.state": "1100.0.12",
|
|
80
|
-
"@pnpm/workspace.workspace-manifest-writer": "1100.0.7",
|
|
80
|
+
"@pnpm/store.controller": "1101.0.7",
|
|
81
|
+
"@pnpm/workspace.projects-graph": "1100.0.9",
|
|
81
82
|
"@pnpm/workspace.projects-sorter": "1100.0.2",
|
|
82
|
-
"@pnpm/
|
|
83
|
-
"@pnpm/workspace.
|
|
84
|
-
"@pnpm/workspace.root-finder": "1100.0.1"
|
|
83
|
+
"@pnpm/workspace.projects-reader": "1101.0.5",
|
|
84
|
+
"@pnpm/workspace.workspace-manifest-writer": "1100.0.8",
|
|
85
|
+
"@pnpm/workspace.root-finder": "1100.0.1",
|
|
86
|
+
"@pnpm/workspace.state": "1100.0.13"
|
|
85
87
|
},
|
|
86
88
|
"peerDependencies": {
|
|
87
89
|
"@pnpm/logger": ">=1001.0.0 <1002.0.0"
|
|
@@ -94,7 +96,6 @@
|
|
|
94
96
|
"@types/ramda": "0.31.1",
|
|
95
97
|
"@types/yarnpkg__lockfile": "^1.1.9",
|
|
96
98
|
"@types/zkochan__table": "npm:@types/table@6.0.0",
|
|
97
|
-
"ci-info": "^4.3.0",
|
|
98
99
|
"delay": "^7.0.0",
|
|
99
100
|
"jest-diff": "^30.3.0",
|
|
100
101
|
"path-name": "^1.0.0",
|
|
@@ -105,18 +106,18 @@
|
|
|
105
106
|
"write-json-file": "^7.0.0",
|
|
106
107
|
"write-package": "7.2.0",
|
|
107
108
|
"write-yaml-file": "^6.0.0",
|
|
108
|
-
"@pnpm/
|
|
109
|
-
"@pnpm/
|
|
110
|
-
"@pnpm/
|
|
111
|
-
"@pnpm/store.index": "1100.1.0",
|
|
112
|
-
"@pnpm/logger": "1100.0.0",
|
|
109
|
+
"@pnpm/assert-project": "1100.0.9",
|
|
110
|
+
"@pnpm/prepare": "1100.0.9",
|
|
111
|
+
"@pnpm/installing.commands": "1100.3.0",
|
|
113
112
|
"@pnpm/test-fixtures": "1100.0.0",
|
|
113
|
+
"@pnpm/logger": "1100.0.0",
|
|
114
114
|
"@pnpm/test-ipc-server": "1100.0.0",
|
|
115
|
-
"@pnpm/installing.modules-yaml": "1100.0.4",
|
|
116
115
|
"@pnpm/testing.command-defaults": "1100.0.1",
|
|
117
|
-
"@pnpm/testing.mock-agent": "1100.0.
|
|
118
|
-
"@pnpm/
|
|
119
|
-
"@pnpm/
|
|
116
|
+
"@pnpm/testing.mock-agent": "1100.0.5",
|
|
117
|
+
"@pnpm/store.index": "1100.1.0",
|
|
118
|
+
"@pnpm/worker": "1100.1.6",
|
|
119
|
+
"@pnpm/workspace.projects-filter": "1100.0.12",
|
|
120
|
+
"@pnpm/installing.modules-yaml": "1100.0.4"
|
|
120
121
|
},
|
|
121
122
|
"engines": {
|
|
122
123
|
"node": ">=22.13"
|