@pnpm/workspace.workspace-manifest-writer 1100.0.21 → 1100.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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @pnpm/workspace.manifest-writer
2
2
 
3
+ ## 1100.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Added a new setting `minimumReleaseAgeExcludePrune`. When enabled, `pnpm add`, `pnpm update`, and `pnpm remove` prune the entries of `minimumReleaseAgeExclude` in `pnpm-workspace.yaml` that the freshly written lockfile no longer resolves: versions that are gone are dropped (an entry is removed once none of its versions remain), and entries for packages that are no longer in the lockfile are removed too. Name patterns (`@scope/*`) are always kept. The cleanup is skipped when the install's lockfile does not cover the whole workspace (`sharedWorkspaceLockfile: false`), since entries another project still needs would look stale.
8
+
9
+ Renamed `cleanupUnusedCatalogs` to `catalogPrune`, so that catalog pruning and release-age exclude pruning use one vocabulary. `cleanupUnusedCatalogs` continues to work; when both are set, `catalogPrune` wins.
10
+
11
+ ### Patch Changes
12
+
13
+ - `pnpm config delete <key>` no longer fails with `ENOENT` when the config file it would edit does not exist. Clearing a setting that was never set is a no-op [#13651](https://github.com/pnpm/pnpm/issues/13651).
14
+
15
+ - Updated dependencies:
16
+ - @pnpm/config.parse-overrides@1100.1.3
17
+ - @pnpm/config.version-policy@1100.2.0
18
+ - @pnpm/workspace.workspace-manifest-reader@1100.1.6
19
+
3
20
  ## 1100.0.21
4
21
 
5
22
  ### Patch Changes
package/lib/index.d.ts CHANGED
@@ -10,8 +10,15 @@ export declare function updateWorkspaceManifest(dir: string, opts: {
10
10
  updatedOverrides?: Record<string, string>;
11
11
  addedMinimumReleaseAgeExcludes?: string[];
12
12
  fileName?: FileName;
13
- cleanupUnusedCatalogs?: boolean;
13
+ catalogPrune?: boolean;
14
14
  allProjects?: Project[];
15
+ /**
16
+ * Package name → the versions the freshly resolved lockfile records.
17
+ * Present only under `minimumReleaseAgeExcludePrune`, and only when the
18
+ * lockfile covers every project `minimumReleaseAgeExclude` governs;
19
+ * absent, the cleanup pass does not run.
20
+ */
21
+ resolvedPackageVersions?: ReadonlyMap<string, ReadonlySet<string>>;
15
22
  }): Promise<void>;
16
23
  export interface NewCatalogs {
17
24
  [catalogName: string]: {
package/lib/index.js CHANGED
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import util from 'node:util';
4
4
  import { parsePkgAndParentSelector } from '@pnpm/config.parse-overrides';
5
- import { mergePackageVersionSpecs } from '@pnpm/config.version-policy';
5
+ import { mergePackageVersionSpecs, parseVersionPolicyRule } from '@pnpm/config.version-policy';
6
6
  import { WORKSPACE_MANIFEST_FILENAME } from '@pnpm/constants';
7
7
  import { lexCompare } from '@pnpm/text.ordinal-comparator';
8
8
  import { validateWorkspaceManifest } from '@pnpm/workspace.workspace-manifest-reader';
@@ -41,20 +41,25 @@ export async function updateWorkspaceManifest(dir, opts) {
41
41
  manifest ??= {};
42
42
  const originalKeyOrder = captureKeyOrder(manifest);
43
43
  let shouldBeUpdated = opts.updatedCatalogs != null && addCatalogs(manifest, opts.updatedCatalogs);
44
- if (opts.cleanupUnusedCatalogs) {
44
+ if (opts.catalogPrune) {
45
45
  shouldBeUpdated = removePackagesFromWorkspaceCatalog(manifest, opts.allProjects ?? []) || shouldBeUpdated;
46
46
  }
47
47
  const updatedFields = { ...opts.updatedFields };
48
48
  for (const [key, value] of Object.entries(updatedFields)) {
49
- if (!equals(manifest[key], value)) {
49
+ if (value == null) {
50
+ // Clearing a field the manifest never had changes nothing. Counting it as
51
+ // an update would take the empty-manifest branch below and try to remove a
52
+ // file that may not exist.
53
+ if (!Object.hasOwn(manifest, key))
54
+ continue;
50
55
  shouldBeUpdated = true;
51
- if (value == null) {
52
- delete manifest[key];
53
- }
54
- else {
55
- manifest[key] = value;
56
- }
56
+ delete manifest[key];
57
+ continue;
57
58
  }
59
+ if (equals(manifest[key], value))
60
+ continue;
61
+ shouldBeUpdated = true;
62
+ manifest[key] = value;
58
63
  }
59
64
  if (opts.updatedOverrides) {
60
65
  manifest.overrides ??= {};
@@ -65,6 +70,11 @@ export async function updateWorkspaceManifest(dir, opts) {
65
70
  }
66
71
  }
67
72
  }
73
+ if (opts.resolvedPackageVersions != null) {
74
+ shouldBeUpdated = pruneMinimumReleaseAgeExcludes(manifest, opts.resolvedPackageVersions) || shouldBeUpdated;
75
+ }
76
+ // Merged after the cleanup pass so entries approved during this install
77
+ // are never pruned by it in the same write.
68
78
  if (opts.addedMinimumReleaseAgeExcludes?.length) {
69
79
  const existing = manifest.minimumReleaseAgeExclude ?? [];
70
80
  const merged = mergePackageVersionSpecs([...existing, ...opts.addedMinimumReleaseAgeExcludes]);
@@ -196,6 +206,65 @@ function addPackageReference(packageReferences, pkgName, version) {
196
206
  }
197
207
  packageReferences[pkgName].add(version);
198
208
  }
209
+ // The `minimumReleaseAgeExcludePrune` pass. An entry is dropped when the
210
+ // freshly resolved lockfile no longer contains what it names: exact versions
211
+ // that were not resolved are dropped (the entry goes away once none remain),
212
+ // and a bare-name entry goes away when the package is absent entirely. Glob
213
+ // patterns always stay — they are forward-looking and can't be proven stale.
214
+ // Entries that fail to parse stay untouched so cleanup never breaks an
215
+ // install.
216
+ function pruneMinimumReleaseAgeExcludes(manifest, resolvedPackageVersions) {
217
+ const excludes = manifest.minimumReleaseAgeExclude;
218
+ if (excludes == null || excludes.length === 0) {
219
+ return false;
220
+ }
221
+ const survivingSpecs = [];
222
+ let changed = false;
223
+ for (const entry of excludes) {
224
+ let packageName;
225
+ let exactVersions;
226
+ try {
227
+ const rule = parseVersionPolicyRule(entry);
228
+ packageName = rule.packageName;
229
+ exactVersions = rule.exactVersions;
230
+ }
231
+ catch {
232
+ survivingSpecs.push(entry);
233
+ continue;
234
+ }
235
+ if (exactVersions.length === 0) {
236
+ if (packageName.includes('*') || resolvedPackageVersions.has(packageName)) {
237
+ survivingSpecs.push(entry);
238
+ }
239
+ else {
240
+ changed = true;
241
+ }
242
+ continue;
243
+ }
244
+ const resolved = resolvedPackageVersions.get(packageName);
245
+ const survivingVersions = exactVersions.filter((version) => resolved?.has(version));
246
+ if (survivingVersions.length === exactVersions.length) {
247
+ survivingSpecs.push(entry);
248
+ }
249
+ else if (survivingVersions.length > 0) {
250
+ survivingSpecs.push(...mergePackageVersionSpecs([`${packageName}@${survivingVersions.join(' || ')}`]));
251
+ changed = true;
252
+ }
253
+ else {
254
+ changed = true;
255
+ }
256
+ }
257
+ if (!changed) {
258
+ return false;
259
+ }
260
+ if (survivingSpecs.length === 0) {
261
+ delete manifest.minimumReleaseAgeExclude;
262
+ }
263
+ else {
264
+ manifest.minimumReleaseAgeExclude = survivingSpecs;
265
+ }
266
+ return true;
267
+ }
199
268
  // Captures only the key order at each nested level of a plain-object value,
200
269
  // without duplicating the values themselves. Used as a lightweight snapshot of
201
270
  // the original manifest layout so `reorderRecursive` can decide where to place
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/workspace.workspace-manifest-writer",
3
- "version": "1100.0.21",
3
+ "version": "1100.1.0",
4
4
  "description": "Updates the workspace manifest file",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -28,13 +28,13 @@
28
28
  ],
29
29
  "dependencies": {
30
30
  "@pnpm/catalogs.types": "1100.0.1",
31
- "@pnpm/config.parse-overrides": "1100.1.2",
32
- "@pnpm/config.version-policy": "1100.1.12",
31
+ "@pnpm/config.parse-overrides": "1100.1.3",
32
+ "@pnpm/config.version-policy": "1100.2.0",
33
33
  "@pnpm/constants": "1101.0.0",
34
34
  "@pnpm/lockfile.types": "1100.0.19",
35
35
  "@pnpm/text.ordinal-comparator": "1100.0.0",
36
36
  "@pnpm/types": "1101.9.0",
37
- "@pnpm/workspace.workspace-manifest-reader": "1100.1.5",
37
+ "@pnpm/workspace.workspace-manifest-reader": "1100.1.6",
38
38
  "@pnpm/yaml.document-sync": "1100.0.1",
39
39
  "ramda": "npm:@pnpm/ramda@0.28.1",
40
40
  "write-file-atomic": "^7.0.1",
@@ -42,10 +42,10 @@
42
42
  },
43
43
  "devDependencies": {
44
44
  "@jest/globals": "30.4.1",
45
- "@pnpm/prepare": "1100.0.25",
45
+ "@pnpm/prepare": "1100.0.26",
46
46
  "@pnpm/prepare-temp-dir": "1100.0.1",
47
- "@pnpm/workspace.projects-reader": "1101.0.22",
48
- "@pnpm/workspace.workspace-manifest-writer": "1100.0.21",
47
+ "@pnpm/workspace.projects-reader": "1101.0.23",
48
+ "@pnpm/workspace.workspace-manifest-writer": "1100.1.0",
49
49
  "@types/ramda": "0.32.0",
50
50
  "@types/write-file-atomic": "^4.0.3",
51
51
  "read-yaml-file": "^3.0.0",