@pnpm/workspace.workspace-manifest-writer 1100.0.21 → 1100.1.1

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,39 @@
1
1
  # @pnpm/workspace.manifest-writer
2
2
 
3
+ ## 1100.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - `pnpm approve-builds` now removes `onlyBuiltDependencies`, `onlyBuiltDependenciesFile`, `neverBuiltDependencies`, and `ignoredBuiltDependencies` from `pnpm-workspace.yaml` when it writes `allowBuilds`. Those settings were replaced by `allowBuilds` in pnpm 11 and silently ignored since, so a workspace migrated from pnpm 10 kept them around looking active.
8
+
9
+ - `pnpm remove` now prunes undecided entries (`"set this to true or false"`) from `allowBuilds` in `pnpm-workspace.yaml` when `sharedWorkspaceLockfile: true` and the corresponding packages are removed [pnpm/pnpm#13892](https://github.com/pnpm/pnpm/issues/13892).
10
+
11
+ - Updated dependencies:
12
+ - @pnpm/building.policy@1100.0.20
13
+ - @pnpm/config.parse-overrides@1100.1.4
14
+ - @pnpm/config.version-policy@1100.2.1
15
+ - @pnpm/constants@1102.0.0
16
+ - @pnpm/lockfile.types@1100.0.20
17
+ - @pnpm/types@1102.0.0
18
+ - @pnpm/workspace.workspace-manifest-reader@1100.1.7
19
+
20
+ ## 1100.1.0
21
+
22
+ ### Minor Changes
23
+
24
+ - 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.
25
+
26
+ 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.
27
+
28
+ ### Patch Changes
29
+
30
+ - `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).
31
+
32
+ - Updated dependencies:
33
+ - @pnpm/config.parse-overrides@1100.1.3
34
+ - @pnpm/config.version-policy@1100.2.0
35
+ - @pnpm/workspace.workspace-manifest-reader@1100.1.6
36
+
3
37
  ## 1100.0.21
4
38
 
5
39
  ### Patch Changes
package/lib/index.d.ts CHANGED
@@ -9,9 +9,18 @@ export declare function updateWorkspaceManifest(dir: string, opts: {
9
9
  updatedCatalogs?: Catalogs;
10
10
  updatedOverrides?: Record<string, string>;
11
11
  addedMinimumReleaseAgeExcludes?: string[];
12
+ deletedLegacyKeys?: string[];
12
13
  fileName?: FileName;
13
- cleanupUnusedCatalogs?: boolean;
14
+ catalogPrune?: boolean;
14
15
  allProjects?: Project[];
16
+ /**
17
+ * Package name → the versions the freshly resolved lockfile records.
18
+ * Supplied when a freshly resolved shared lockfile is available.
19
+ * `minimumReleaseAgeExcludePrune` gates only minimum-release-age cleanup;
20
+ * `allowBuilds` cleanup runs whenever this map is present.
21
+ */
22
+ resolvedPackageVersions?: ReadonlyMap<string, ReadonlySet<string>>;
23
+ minimumReleaseAgeExcludePrune?: boolean;
15
24
  }): Promise<void>;
16
25
  export interface NewCatalogs {
17
26
  [catalogName: string]: {
package/lib/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import util from 'node:util';
4
+ import { packageNameFromAllowBuildKey, UNDECIDED_ALLOW_BUILD } from '@pnpm/building.policy';
4
5
  import { parsePkgAndParentSelector } from '@pnpm/config.parse-overrides';
5
- import { mergePackageVersionSpecs } from '@pnpm/config.version-policy';
6
+ import { mergePackageVersionSpecs, parseVersionPolicyRule } from '@pnpm/config.version-policy';
6
7
  import { WORKSPACE_MANIFEST_FILENAME } from '@pnpm/constants';
7
8
  import { lexCompare } from '@pnpm/text.ordinal-comparator';
8
9
  import { validateWorkspaceManifest } from '@pnpm/workspace.workspace-manifest-reader';
@@ -41,19 +42,31 @@ export async function updateWorkspaceManifest(dir, opts) {
41
42
  manifest ??= {};
42
43
  const originalKeyOrder = captureKeyOrder(manifest);
43
44
  let shouldBeUpdated = opts.updatedCatalogs != null && addCatalogs(manifest, opts.updatedCatalogs);
44
- if (opts.cleanupUnusedCatalogs) {
45
+ if (opts.catalogPrune) {
45
46
  shouldBeUpdated = removePackagesFromWorkspaceCatalog(manifest, opts.allProjects ?? []) || shouldBeUpdated;
46
47
  }
47
48
  const updatedFields = { ...opts.updatedFields };
48
49
  for (const [key, value] of Object.entries(updatedFields)) {
49
- if (!equals(manifest[key], value)) {
50
+ if (value == null) {
51
+ // Clearing a field the manifest never had changes nothing. Counting it as
52
+ // an update would take the empty-manifest branch below and try to remove a
53
+ // file that may not exist.
54
+ if (!Object.hasOwn(manifest, key))
55
+ continue;
56
+ shouldBeUpdated = true;
57
+ delete manifest[key];
58
+ continue;
59
+ }
60
+ if (equals(manifest[key], value))
61
+ continue;
62
+ shouldBeUpdated = true;
63
+ manifest[key] = value;
64
+ }
65
+ const untypedManifest = manifest;
66
+ for (const key of opts.deletedLegacyKeys ?? []) {
67
+ if (Object.hasOwn(untypedManifest, key)) {
68
+ delete untypedManifest[key];
50
69
  shouldBeUpdated = true;
51
- if (value == null) {
52
- delete manifest[key];
53
- }
54
- else {
55
- manifest[key] = value;
56
- }
57
70
  }
58
71
  }
59
72
  if (opts.updatedOverrides) {
@@ -65,6 +78,14 @@ export async function updateWorkspaceManifest(dir, opts) {
65
78
  }
66
79
  }
67
80
  }
81
+ if (opts.resolvedPackageVersions != null) {
82
+ if (opts.minimumReleaseAgeExcludePrune) {
83
+ shouldBeUpdated = pruneMinimumReleaseAgeExcludes(manifest, opts.resolvedPackageVersions) || shouldBeUpdated;
84
+ }
85
+ shouldBeUpdated = pruneAllowBuilds(manifest, opts.resolvedPackageVersions) || shouldBeUpdated;
86
+ }
87
+ // Merged after the cleanup pass so entries approved during this install
88
+ // are never pruned by it in the same write.
68
89
  if (opts.addedMinimumReleaseAgeExcludes?.length) {
69
90
  const existing = manifest.minimumReleaseAgeExclude ?? [];
70
91
  const merged = mergePackageVersionSpecs([...existing, ...opts.addedMinimumReleaseAgeExcludes]);
@@ -196,6 +217,65 @@ function addPackageReference(packageReferences, pkgName, version) {
196
217
  }
197
218
  packageReferences[pkgName].add(version);
198
219
  }
220
+ // The `minimumReleaseAgeExcludePrune` pass. An entry is dropped when the
221
+ // freshly resolved lockfile no longer contains what it names: exact versions
222
+ // that were not resolved are dropped (the entry goes away once none remain),
223
+ // and a bare-name entry goes away when the package is absent entirely. Glob
224
+ // patterns always stay — they are forward-looking and can't be proven stale.
225
+ // Entries that fail to parse stay untouched so cleanup never breaks an
226
+ // install.
227
+ function pruneMinimumReleaseAgeExcludes(manifest, resolvedPackageVersions) {
228
+ const excludes = manifest.minimumReleaseAgeExclude;
229
+ if (excludes == null || excludes.length === 0) {
230
+ return false;
231
+ }
232
+ const survivingSpecs = [];
233
+ let changed = false;
234
+ for (const entry of excludes) {
235
+ let packageName;
236
+ let exactVersions;
237
+ try {
238
+ const rule = parseVersionPolicyRule(entry);
239
+ packageName = rule.packageName;
240
+ exactVersions = rule.exactVersions;
241
+ }
242
+ catch {
243
+ survivingSpecs.push(entry);
244
+ continue;
245
+ }
246
+ if (exactVersions.length === 0) {
247
+ if (packageName.includes('*') || resolvedPackageVersions.has(packageName)) {
248
+ survivingSpecs.push(entry);
249
+ }
250
+ else {
251
+ changed = true;
252
+ }
253
+ continue;
254
+ }
255
+ const resolved = resolvedPackageVersions.get(packageName);
256
+ const survivingVersions = exactVersions.filter((version) => resolved?.has(version));
257
+ if (survivingVersions.length === exactVersions.length) {
258
+ survivingSpecs.push(entry);
259
+ }
260
+ else if (survivingVersions.length > 0) {
261
+ survivingSpecs.push(...mergePackageVersionSpecs([`${packageName}@${survivingVersions.join(' || ')}`]));
262
+ changed = true;
263
+ }
264
+ else {
265
+ changed = true;
266
+ }
267
+ }
268
+ if (!changed) {
269
+ return false;
270
+ }
271
+ if (survivingSpecs.length === 0) {
272
+ delete manifest.minimumReleaseAgeExclude;
273
+ }
274
+ else {
275
+ manifest.minimumReleaseAgeExclude = survivingSpecs;
276
+ }
277
+ return true;
278
+ }
199
279
  // Captures only the key order at each nested level of a plain-object value,
200
280
  // without duplicating the values themselves. Used as a lightweight snapshot of
201
281
  // the original manifest layout so `reorderRecursive` can decide where to place
@@ -315,4 +395,27 @@ function propagateBlankLinesToNewPairs(document, originalTopLevelKeys) {
315
395
  }
316
396
  }
317
397
  }
398
+ // Drops undecided placeholder entries whose package is provably absent from
399
+ // the resolved lockfile. Explicit decisions, keys with no provable package
400
+ // name, and entries for still-resolved packages always stay.
401
+ function pruneAllowBuilds(manifest, resolvedPackageVersions) {
402
+ const allowBuilds = manifest.allowBuilds;
403
+ if (allowBuilds == null) {
404
+ return false;
405
+ }
406
+ let changed = false;
407
+ for (const [key, value] of Object.entries(allowBuilds)) {
408
+ if (value !== UNDECIDED_ALLOW_BUILD)
409
+ continue;
410
+ const packageName = packageNameFromAllowBuildKey(key);
411
+ if (packageName == null || resolvedPackageVersions.has(packageName))
412
+ continue;
413
+ delete allowBuilds[key];
414
+ changed = true;
415
+ }
416
+ if (changed && Object.keys(allowBuilds).length === 0) {
417
+ delete manifest.allowBuilds;
418
+ }
419
+ return changed;
420
+ }
318
421
  //# sourceMappingURL=index.js.map
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.1",
4
4
  "description": "Updates the workspace manifest file",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -27,14 +27,15 @@
27
27
  "!*.map"
28
28
  ],
29
29
  "dependencies": {
30
+ "@pnpm/building.policy": "1100.0.20",
30
31
  "@pnpm/catalogs.types": "1100.0.1",
31
- "@pnpm/config.parse-overrides": "1100.1.2",
32
- "@pnpm/config.version-policy": "1100.1.12",
33
- "@pnpm/constants": "1101.0.0",
34
- "@pnpm/lockfile.types": "1100.0.19",
32
+ "@pnpm/config.parse-overrides": "1100.1.4",
33
+ "@pnpm/config.version-policy": "1100.2.1",
34
+ "@pnpm/constants": "1102.0.0",
35
+ "@pnpm/lockfile.types": "1100.0.20",
35
36
  "@pnpm/text.ordinal-comparator": "1100.0.0",
36
- "@pnpm/types": "1101.9.0",
37
- "@pnpm/workspace.workspace-manifest-reader": "1100.1.5",
37
+ "@pnpm/types": "1102.0.0",
38
+ "@pnpm/workspace.workspace-manifest-reader": "1100.1.7",
38
39
  "@pnpm/yaml.document-sync": "1100.0.1",
39
40
  "ramda": "npm:@pnpm/ramda@0.28.1",
40
41
  "write-file-atomic": "^7.0.1",
@@ -42,10 +43,10 @@
42
43
  },
43
44
  "devDependencies": {
44
45
  "@jest/globals": "30.4.1",
45
- "@pnpm/prepare": "1100.0.25",
46
+ "@pnpm/prepare": "1100.0.27",
46
47
  "@pnpm/prepare-temp-dir": "1100.0.1",
47
- "@pnpm/workspace.projects-reader": "1101.0.22",
48
- "@pnpm/workspace.workspace-manifest-writer": "1100.0.21",
48
+ "@pnpm/workspace.projects-reader": "1101.0.24",
49
+ "@pnpm/workspace.workspace-manifest-writer": "1100.1.1",
49
50
  "@types/ramda": "0.32.0",
50
51
  "@types/write-file-atomic": "^4.0.3",
51
52
  "read-yaml-file": "^3.0.0",