@pnpm/workspace.workspace-manifest-writer 1100.2.2 → 1100.2.4
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 +17 -0
- package/lib/auditIgnoreGhsas.d.ts +12 -0
- package/lib/auditIgnoreGhsas.js +53 -0
- package/lib/catalogs.d.ts +5 -0
- package/lib/catalogs.js +149 -0
- package/lib/index.d.ts +3 -2
- package/lib/index.js +66 -397
- package/lib/keyOrder.d.ts +8 -0
- package/lib/keyOrder.js +125 -0
- package/lib/pruneResolvedLists.d.ts +8 -0
- package/lib/pruneResolvedLists.js +89 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @pnpm/workspace.manifest-writer
|
|
2
2
|
|
|
3
|
+
## 1100.2.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Catalogs now work with entries and catalog names such as `constructor` or `toString`. Pruning unused catalog entries crashed on such names, and a new named catalog called `toString` was silently not written.
|
|
8
|
+
|
|
9
|
+
## 1100.2.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies:
|
|
14
|
+
- @pnpm/building.policy@1100.1.4
|
|
15
|
+
- @pnpm/config.parse-overrides@1100.1.7
|
|
16
|
+
- @pnpm/config.version-policy@1100.2.5
|
|
17
|
+
- @pnpm/lockfile.types@1100.1.4
|
|
18
|
+
- @pnpm/workspace.workspace-manifest-reader@1100.2.1
|
|
19
|
+
|
|
3
20
|
## 1100.2.2
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WorkspaceManifest } from '@pnpm/workspace.workspace-manifest-reader';
|
|
2
|
+
/**
|
|
3
|
+
* Set the audit ignore list to `ghsas` (the complete desired list) in
|
|
4
|
+
* whichever spelling the manifest uses — the canonical `audit.ignore` wins
|
|
5
|
+
* over the deprecated `auditConfig.ignoreGhsas`, matching the reader's
|
|
6
|
+
* precedence, so a stale canonical list can't shadow the update on the next
|
|
7
|
+
* read. When both spellings are present, the shadowed deprecated list is
|
|
8
|
+
* removed as part of the write. `auditConfig.ignoreGhsas` is created when
|
|
9
|
+
* neither is present. An empty `ghsas` removes the list, dropping its parent
|
|
10
|
+
* block when nothing else remains in it. Returns whether anything changed.
|
|
11
|
+
*/
|
|
12
|
+
export declare function setAuditIgnoreGhsas(manifest: Partial<WorkspaceManifest>, ghsas: string[]): boolean;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { equals } from 'ramda';
|
|
2
|
+
/**
|
|
3
|
+
* Set the audit ignore list to `ghsas` (the complete desired list) in
|
|
4
|
+
* whichever spelling the manifest uses — the canonical `audit.ignore` wins
|
|
5
|
+
* over the deprecated `auditConfig.ignoreGhsas`, matching the reader's
|
|
6
|
+
* precedence, so a stale canonical list can't shadow the update on the next
|
|
7
|
+
* read. When both spellings are present, the shadowed deprecated list is
|
|
8
|
+
* removed as part of the write. `auditConfig.ignoreGhsas` is created when
|
|
9
|
+
* neither is present. An empty `ghsas` removes the list, dropping its parent
|
|
10
|
+
* block when nothing else remains in it. Returns whether anything changed.
|
|
11
|
+
*/
|
|
12
|
+
export function setAuditIgnoreGhsas(manifest, ghsas) {
|
|
13
|
+
if (manifest.audit?.ignore != null) {
|
|
14
|
+
let changed = setCanonicalAuditIgnore(manifest, manifest.audit, ghsas);
|
|
15
|
+
if (manifest.auditConfig?.ignoreGhsas != null) {
|
|
16
|
+
changed = removeAuditConfigIgnoreGhsas(manifest) || changed;
|
|
17
|
+
}
|
|
18
|
+
return changed;
|
|
19
|
+
}
|
|
20
|
+
if (ghsas.length === 0) {
|
|
21
|
+
return removeAuditConfigIgnoreGhsas(manifest);
|
|
22
|
+
}
|
|
23
|
+
if (equals(manifest.auditConfig?.ignoreGhsas, ghsas)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
manifest.auditConfig = { ...manifest.auditConfig, ignoreGhsas: ghsas };
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
function setCanonicalAuditIgnore(manifest, audit, ghsas) {
|
|
30
|
+
if (ghsas.length === 0) {
|
|
31
|
+
delete audit.ignore;
|
|
32
|
+
if (Object.keys(audit).length === 0) {
|
|
33
|
+
delete manifest.audit;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (equals(audit.ignore, ghsas)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
audit.ignore = ghsas;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
function removeAuditConfigIgnoreGhsas(manifest) {
|
|
44
|
+
if (manifest.auditConfig?.ignoreGhsas == null) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
delete manifest.auditConfig.ignoreGhsas;
|
|
48
|
+
if (Object.keys(manifest.auditConfig).length === 0) {
|
|
49
|
+
delete manifest.auditConfig;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=auditIgnoreGhsas.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Catalogs } from '@pnpm/catalogs.types';
|
|
2
|
+
import type { Project } from '@pnpm/types';
|
|
3
|
+
import type { WorkspaceManifest } from '@pnpm/workspace.workspace-manifest-reader';
|
|
4
|
+
export declare function addCatalogs(manifest: Partial<WorkspaceManifest>, newCatalogs: Catalogs): boolean;
|
|
5
|
+
export declare function removePackagesFromWorkspaceCatalog(manifest: Partial<WorkspaceManifest>, packagesJson: Project[]): boolean;
|
package/lib/catalogs.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { parsePkgAndParentSelector } from '@pnpm/config.parse-overrides';
|
|
2
|
+
export function addCatalogs(manifest, newCatalogs) {
|
|
3
|
+
let shouldBeUpdated = false;
|
|
4
|
+
for (const catalogName in newCatalogs) {
|
|
5
|
+
const existingCatalog = findCatalog(manifest, catalogName);
|
|
6
|
+
const { catalog, changed } = mergeCatalogEntries(existingCatalog, newCatalogs[catalogName] ?? {});
|
|
7
|
+
shouldBeUpdated = changed || shouldBeUpdated;
|
|
8
|
+
if (catalog == null || existingCatalog != null)
|
|
9
|
+
continue;
|
|
10
|
+
attachCatalog(manifest, catalogName, catalog);
|
|
11
|
+
}
|
|
12
|
+
return shouldBeUpdated;
|
|
13
|
+
}
|
|
14
|
+
function findCatalog(manifest, catalogName) {
|
|
15
|
+
if (catalogName === 'default') {
|
|
16
|
+
return manifest.catalog ?? manifest.catalogs?.default;
|
|
17
|
+
}
|
|
18
|
+
return manifest.catalogs != null && Object.hasOwn(manifest.catalogs, catalogName)
|
|
19
|
+
? manifest.catalogs[catalogName]
|
|
20
|
+
: undefined;
|
|
21
|
+
}
|
|
22
|
+
function mergeCatalogEntries(existingCatalog, newEntries) {
|
|
23
|
+
let catalog = existingCatalog;
|
|
24
|
+
let changed = false;
|
|
25
|
+
for (const [dependencyName, specifier] of Object.entries(newEntries)) {
|
|
26
|
+
if (specifier == null) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
catalog ??= {};
|
|
30
|
+
if (catalog[dependencyName] !== specifier) {
|
|
31
|
+
catalog[dependencyName] = specifier;
|
|
32
|
+
changed = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { catalog, changed };
|
|
36
|
+
}
|
|
37
|
+
function attachCatalog(manifest, catalogName, catalog) {
|
|
38
|
+
if (catalogName === 'default') {
|
|
39
|
+
manifest.catalog = catalog;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
manifest.catalogs ??= {};
|
|
43
|
+
manifest.catalogs[catalogName] = catalog;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function removePackagesFromWorkspaceCatalog(manifest, packagesJson) {
|
|
47
|
+
if (packagesJson.length === 0 || (manifest.catalog == null && manifest.catalogs == null)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const packageReferences = collectCatalogReferences(packagesJson, manifest.overrides ?? {});
|
|
51
|
+
const defaultCatalogChanged = pruneDefaultCatalog(manifest, packageReferences);
|
|
52
|
+
const namedCatalogsChanged = pruneNamedCatalogs(manifest, packageReferences);
|
|
53
|
+
return defaultCatalogChanged || namedCatalogsChanged;
|
|
54
|
+
}
|
|
55
|
+
function collectCatalogReferences(packagesJson, overrides) {
|
|
56
|
+
const packageReferences = new Map();
|
|
57
|
+
for (const pkg of packagesJson) {
|
|
58
|
+
const pkgManifest = pkg.manifest;
|
|
59
|
+
const dependencyTypes = [
|
|
60
|
+
pkgManifest.dependencies,
|
|
61
|
+
pkgManifest.devDependencies,
|
|
62
|
+
pkgManifest.optionalDependencies,
|
|
63
|
+
pkgManifest.peerDependencies,
|
|
64
|
+
];
|
|
65
|
+
for (const deps of dependencyTypes) {
|
|
66
|
+
addPackageReferences(packageReferences, deps ?? {});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const [selector, version] of Object.entries(overrides)) {
|
|
70
|
+
if (!version.startsWith('catalog:')) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const pkgName = overrideTargetName(selector);
|
|
74
|
+
if (pkgName == null)
|
|
75
|
+
continue;
|
|
76
|
+
addPackageReference(packageReferences, pkgName, version);
|
|
77
|
+
}
|
|
78
|
+
return packageReferences;
|
|
79
|
+
}
|
|
80
|
+
function addPackageReferences(packageReferences, deps) {
|
|
81
|
+
for (const [pkgName, version] of Object.entries(deps)) {
|
|
82
|
+
addPackageReference(packageReferences, pkgName, version);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function overrideTargetName(selector) {
|
|
86
|
+
try {
|
|
87
|
+
return parsePkgAndParentSelector(selector).targetPkg.name;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function addPackageReference(packageReferences, pkgName, version) {
|
|
94
|
+
let references = packageReferences.get(pkgName);
|
|
95
|
+
if (references == null) {
|
|
96
|
+
references = new Set();
|
|
97
|
+
packageReferences.set(pkgName, references);
|
|
98
|
+
}
|
|
99
|
+
references.add(version);
|
|
100
|
+
}
|
|
101
|
+
function pruneDefaultCatalog(manifest, packageReferences) {
|
|
102
|
+
if (!manifest.catalog)
|
|
103
|
+
return false;
|
|
104
|
+
let shouldBeUpdated = false;
|
|
105
|
+
const packagesToRemove = Object.keys(manifest.catalog).filter(pkg => !packageReferences.get(pkg)?.has('catalog:'));
|
|
106
|
+
for (const pkg of packagesToRemove) {
|
|
107
|
+
delete manifest.catalog[pkg];
|
|
108
|
+
shouldBeUpdated = true;
|
|
109
|
+
}
|
|
110
|
+
if (Object.keys(manifest.catalog).length === 0) {
|
|
111
|
+
delete manifest.catalog;
|
|
112
|
+
shouldBeUpdated = true;
|
|
113
|
+
}
|
|
114
|
+
return shouldBeUpdated;
|
|
115
|
+
}
|
|
116
|
+
function pruneNamedCatalogs(manifest, packageReferences) {
|
|
117
|
+
if (!manifest.catalogs)
|
|
118
|
+
return false;
|
|
119
|
+
let shouldBeUpdated = false;
|
|
120
|
+
const catalogsToRemove = [];
|
|
121
|
+
for (const [catalogName, catalog] of Object.entries(manifest.catalogs)) {
|
|
122
|
+
if (!catalog)
|
|
123
|
+
continue;
|
|
124
|
+
shouldBeUpdated = pruneNamedCatalog(catalogName, catalog, packageReferences) || shouldBeUpdated;
|
|
125
|
+
if (Object.keys(catalog).length === 0) {
|
|
126
|
+
catalogsToRemove.push(catalogName);
|
|
127
|
+
shouldBeUpdated = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const catalogName of catalogsToRemove) {
|
|
131
|
+
delete manifest.catalogs[catalogName];
|
|
132
|
+
}
|
|
133
|
+
if (Object.keys(manifest.catalogs).length === 0) {
|
|
134
|
+
delete manifest.catalogs;
|
|
135
|
+
shouldBeUpdated = true;
|
|
136
|
+
}
|
|
137
|
+
return shouldBeUpdated;
|
|
138
|
+
}
|
|
139
|
+
function pruneNamedCatalog(catalogName, catalog, packageReferences) {
|
|
140
|
+
const packagesToRemove = Object.keys(catalog).filter(pkg => {
|
|
141
|
+
const references = packageReferences.get(pkg);
|
|
142
|
+
return !references?.has(`catalog:${catalogName}`) && !references?.has('catalog:');
|
|
143
|
+
});
|
|
144
|
+
for (const pkg of packagesToRemove) {
|
|
145
|
+
delete catalog[pkg];
|
|
146
|
+
}
|
|
147
|
+
return packagesToRemove.length > 0;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=catalogs.js.map
|
package/lib/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { ResolvedCatalogEntry } from '@pnpm/lockfile.types';
|
|
|
4
4
|
import type { Project } from '@pnpm/types';
|
|
5
5
|
import { type WorkspaceManifest } from '@pnpm/workspace.workspace-manifest-reader';
|
|
6
6
|
export type FileName = typeof GLOBAL_CONFIG_YAML_FILENAME | typeof WORKSPACE_MANIFEST_FILENAME;
|
|
7
|
-
export
|
|
7
|
+
export interface UpdateWorkspaceManifestOptions {
|
|
8
8
|
updatedFields?: Partial<WorkspaceManifest>;
|
|
9
9
|
updatedCatalogs?: Catalogs;
|
|
10
10
|
updatedOverrides?: Record<string, string>;
|
|
@@ -28,7 +28,8 @@ export declare function updateWorkspaceManifest(dir: string, opts: {
|
|
|
28
28
|
resolvedPackageVersions?: ReadonlyMap<string, ReadonlySet<string>>;
|
|
29
29
|
minimumReleaseAgeExcludePrune?: boolean;
|
|
30
30
|
trustPolicyExcludePrune?: boolean;
|
|
31
|
-
}
|
|
31
|
+
}
|
|
32
|
+
export declare function updateWorkspaceManifest(dir: string, opts: UpdateWorkspaceManifestOptions): Promise<void>;
|
|
32
33
|
export interface NewCatalogs {
|
|
33
34
|
[catalogName: string]: {
|
|
34
35
|
[dependencyName: string]: Pick<ResolvedCatalogEntry, 'specifier'>;
|
package/lib/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import { parsePkgAndParentSelector } from '@pnpm/config.parse-overrides';
|
|
5
|
-
import { mergePackageVersionSpecs, parseVersionPolicyRule } from '@pnpm/config.version-policy';
|
|
3
|
+
import { mergePackageVersionSpecs } from '@pnpm/config.version-policy';
|
|
6
4
|
import { WORKSPACE_MANIFEST_FILENAME } from '@pnpm/constants';
|
|
7
|
-
import { lexCompare } from '@pnpm/text.ordinal-comparator';
|
|
8
5
|
import { validateWorkspaceManifest } from '@pnpm/workspace.workspace-manifest-reader';
|
|
9
6
|
import { patchDocument } from '@pnpm/yaml.document-sync';
|
|
10
7
|
import { equals } from 'ramda';
|
|
11
8
|
import writeFileAtomic from 'write-file-atomic';
|
|
12
9
|
import yaml from 'yaml';
|
|
10
|
+
import { setAuditIgnoreGhsas } from './auditIgnoreGhsas.js';
|
|
11
|
+
import { addCatalogs, removePackagesFromWorkspaceCatalog } from './catalogs.js';
|
|
12
|
+
import { captureKeyOrder, propagateBlankLinesToNewPairs, reorderRecursive } from './keyOrder.js';
|
|
13
|
+
import { pruneAllowBuilds, pruneExcludeList } from './pruneResolvedLists.js';
|
|
13
14
|
const DEFAULT_FILENAME = WORKSPACE_MANIFEST_FILENAME;
|
|
14
15
|
async function writeManifestFile(dir, fileName, manifest) {
|
|
15
16
|
const manifestStr = manifest.toString({
|
|
@@ -40,435 +41,103 @@ export async function updateWorkspaceManifest(dir, opts) {
|
|
|
40
41
|
validateWorkspaceManifest(manifest);
|
|
41
42
|
manifest ??= {};
|
|
42
43
|
const originalKeyOrder = captureKeyOrder(manifest);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
shouldBeUpdated = removePackagesFromWorkspaceCatalog(manifest, opts.allProjects ?? []) || shouldBeUpdated;
|
|
46
|
-
}
|
|
47
|
-
const updatedFields = { ...opts.updatedFields };
|
|
48
|
-
for (const [key, value] of Object.entries(updatedFields)) {
|
|
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;
|
|
55
|
-
shouldBeUpdated = true;
|
|
56
|
-
delete manifest[key];
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (equals(manifest[key], value))
|
|
60
|
-
continue;
|
|
61
|
-
shouldBeUpdated = true;
|
|
62
|
-
manifest[key] = value;
|
|
44
|
+
if (!applyManifestUpdates(manifest, opts)) {
|
|
45
|
+
return;
|
|
63
46
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
delete untypedManifest[key];
|
|
68
|
-
shouldBeUpdated = true;
|
|
69
|
-
}
|
|
47
|
+
if (Object.keys(manifest).length === 0) {
|
|
48
|
+
await fs.promises.rm(path.join(dir, fileName));
|
|
49
|
+
return;
|
|
70
50
|
}
|
|
51
|
+
manifest = reorderRecursive(originalKeyOrder, manifest);
|
|
52
|
+
patchDocument(document, manifest, { preserveScalarAliases: true });
|
|
53
|
+
propagateBlankLinesToNewPairs(document, originalKeyOrder?.keys ?? []);
|
|
54
|
+
await writeManifestFile(dir, fileName, document);
|
|
55
|
+
}
|
|
56
|
+
function applyManifestUpdates(manifest, opts) {
|
|
57
|
+
let shouldBeUpdated = applyCatalogUpdates(manifest, opts);
|
|
58
|
+
shouldBeUpdated = applyUpdatedFields(manifest, { ...opts.updatedFields }) || shouldBeUpdated;
|
|
59
|
+
shouldBeUpdated = deleteKeys(manifest, opts.deletedLegacyKeys ?? []) || shouldBeUpdated;
|
|
71
60
|
if (opts.updatedOverrides) {
|
|
72
|
-
manifest.
|
|
73
|
-
for (const [key, value] of Object.entries(opts.updatedOverrides)) {
|
|
74
|
-
if (!equals(manifest.overrides[key], value)) {
|
|
75
|
-
shouldBeUpdated = true;
|
|
76
|
-
manifest.overrides[key] = value;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
61
|
+
shouldBeUpdated = applyUpdatedOverrides(manifest, opts.updatedOverrides) || shouldBeUpdated;
|
|
79
62
|
}
|
|
80
63
|
if (opts.updatedAuditIgnoreGhsas != null) {
|
|
81
64
|
shouldBeUpdated = setAuditIgnoreGhsas(manifest, opts.updatedAuditIgnoreGhsas) || shouldBeUpdated;
|
|
82
65
|
}
|
|
83
66
|
if (opts.resolvedPackageVersions != null) {
|
|
84
|
-
|
|
85
|
-
shouldBeUpdated = pruneExcludeList(manifest, 'minimumReleaseAgeExclude', opts.resolvedPackageVersions) || shouldBeUpdated;
|
|
86
|
-
}
|
|
87
|
-
if (opts.trustPolicyExcludePrune) {
|
|
88
|
-
shouldBeUpdated = pruneExcludeList(manifest, 'trustPolicyExclude', opts.resolvedPackageVersions) || shouldBeUpdated;
|
|
89
|
-
}
|
|
90
|
-
shouldBeUpdated = pruneAllowBuilds(manifest, opts.resolvedPackageVersions) || shouldBeUpdated;
|
|
67
|
+
shouldBeUpdated = pruneResolvedPackageLists(manifest, opts, opts.resolvedPackageVersions) || shouldBeUpdated;
|
|
91
68
|
}
|
|
92
69
|
// Merged after the cleanup pass so entries approved during this install
|
|
93
70
|
// are never pruned by it in the same write.
|
|
94
71
|
if (opts.addedMinimumReleaseAgeExcludes?.length) {
|
|
95
|
-
|
|
96
|
-
const merged = mergePackageVersionSpecs([...existing, ...opts.addedMinimumReleaseAgeExcludes]);
|
|
97
|
-
if (!equals(existing, merged)) {
|
|
98
|
-
shouldBeUpdated = true;
|
|
99
|
-
manifest.minimumReleaseAgeExclude = merged;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (!shouldBeUpdated) {
|
|
103
|
-
return;
|
|
72
|
+
shouldBeUpdated = addMinimumReleaseAgeExcludes(manifest, opts.addedMinimumReleaseAgeExcludes) || shouldBeUpdated;
|
|
104
73
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
74
|
+
return shouldBeUpdated;
|
|
75
|
+
}
|
|
76
|
+
function applyCatalogUpdates(manifest, opts) {
|
|
77
|
+
const catalogsAdded = opts.updatedCatalogs != null && addCatalogs(manifest, opts.updatedCatalogs);
|
|
78
|
+
if (!opts.catalogPrune) {
|
|
79
|
+
return catalogsAdded;
|
|
108
80
|
}
|
|
109
|
-
|
|
110
|
-
patchDocument(document, manifest, { preserveScalarAliases: true });
|
|
111
|
-
propagateBlankLinesToNewPairs(document, originalKeyOrder?.keys ?? []);
|
|
112
|
-
await writeManifestFile(dir, fileName, document);
|
|
81
|
+
return removePackagesFromWorkspaceCatalog(manifest, opts.allProjects ?? []) || catalogsAdded;
|
|
113
82
|
}
|
|
114
|
-
function
|
|
83
|
+
function applyUpdatedFields(manifest, updatedFields) {
|
|
115
84
|
let shouldBeUpdated = false;
|
|
116
|
-
for (const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (specifier == null) {
|
|
85
|
+
for (const [key, value] of Object.entries(updatedFields)) {
|
|
86
|
+
if (value == null) {
|
|
87
|
+
// Clearing a field the manifest never had changes nothing. Counting it as
|
|
88
|
+
// an update would take the empty-manifest branch below and try to remove a
|
|
89
|
+
// file that may not exist.
|
|
90
|
+
if (!Object.hasOwn(manifest, key))
|
|
123
91
|
continue;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (targetCatalog[dependencyName] !== specifier) {
|
|
127
|
-
targetCatalog[dependencyName] = specifier;
|
|
128
|
-
shouldBeUpdated = true;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (targetCatalog == null)
|
|
92
|
+
shouldBeUpdated = true;
|
|
93
|
+
delete manifest[key];
|
|
132
94
|
continue;
|
|
133
|
-
if (targetCatalogWasNil) {
|
|
134
|
-
if (catalogName === 'default') {
|
|
135
|
-
manifest.catalog = targetCatalog;
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
manifest.catalogs ??= {};
|
|
139
|
-
manifest.catalogs[catalogName] = targetCatalog;
|
|
140
|
-
}
|
|
141
95
|
}
|
|
96
|
+
if (equals(manifest[key], value))
|
|
97
|
+
continue;
|
|
98
|
+
shouldBeUpdated = true;
|
|
99
|
+
manifest[key] = value;
|
|
142
100
|
}
|
|
143
101
|
return shouldBeUpdated;
|
|
144
102
|
}
|
|
145
|
-
function
|
|
103
|
+
function deleteKeys(manifest, keys) {
|
|
146
104
|
let shouldBeUpdated = false;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const packageReferences = {};
|
|
151
|
-
for (const pkg of packagesJson) {
|
|
152
|
-
const pkgManifest = pkg.manifest;
|
|
153
|
-
const dependencyTypes = [
|
|
154
|
-
pkgManifest.dependencies,
|
|
155
|
-
pkgManifest.devDependencies,
|
|
156
|
-
pkgManifest.optionalDependencies,
|
|
157
|
-
pkgManifest.peerDependencies,
|
|
158
|
-
];
|
|
159
|
-
for (const deps of dependencyTypes) {
|
|
160
|
-
if (!deps)
|
|
161
|
-
continue;
|
|
162
|
-
for (const [pkgName, version] of Object.entries(deps)) {
|
|
163
|
-
addPackageReference(packageReferences, pkgName, version);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
for (const [selector, version] of Object.entries(manifest.overrides ?? {})) {
|
|
168
|
-
if (!version.startsWith('catalog:')) {
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
let pkgName;
|
|
172
|
-
try {
|
|
173
|
-
pkgName = parsePkgAndParentSelector(selector).targetPkg.name;
|
|
174
|
-
}
|
|
175
|
-
catch {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
addPackageReference(packageReferences, pkgName, version);
|
|
179
|
-
}
|
|
180
|
-
if (manifest.catalog) {
|
|
181
|
-
const packagesToRemove = Object.keys(manifest.catalog).filter(pkg => !packageReferences[pkg]?.has('catalog:'));
|
|
182
|
-
for (const pkg of packagesToRemove) {
|
|
183
|
-
delete manifest.catalog[pkg];
|
|
184
|
-
shouldBeUpdated = true;
|
|
185
|
-
}
|
|
186
|
-
if (Object.keys(manifest.catalog).length === 0) {
|
|
187
|
-
delete manifest.catalog;
|
|
105
|
+
for (const key of keys) {
|
|
106
|
+
if (Object.hasOwn(manifest, key)) {
|
|
107
|
+
delete manifest[key];
|
|
188
108
|
shouldBeUpdated = true;
|
|
189
109
|
}
|
|
190
110
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return !references?.has(`catalog:${catalogName}`) && !references?.has('catalog:');
|
|
199
|
-
});
|
|
200
|
-
for (const pkg of packagesToRemove) {
|
|
201
|
-
delete catalog[pkg];
|
|
202
|
-
shouldBeUpdated = true;
|
|
203
|
-
}
|
|
204
|
-
if (Object.keys(catalog).length === 0) {
|
|
205
|
-
catalogsToRemove.push(catalogName);
|
|
206
|
-
shouldBeUpdated = true;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
for (const catalogName of catalogsToRemove) {
|
|
210
|
-
delete manifest.catalogs[catalogName];
|
|
211
|
-
}
|
|
212
|
-
if (Object.keys(manifest.catalogs).length === 0) {
|
|
213
|
-
delete manifest.catalogs;
|
|
111
|
+
return shouldBeUpdated;
|
|
112
|
+
}
|
|
113
|
+
function applyUpdatedOverrides(manifest, updatedOverrides) {
|
|
114
|
+
let shouldBeUpdated = false;
|
|
115
|
+
manifest.overrides ??= {};
|
|
116
|
+
for (const [key, value] of Object.entries(updatedOverrides)) {
|
|
117
|
+
if (!equals(manifest.overrides[key], value)) {
|
|
214
118
|
shouldBeUpdated = true;
|
|
119
|
+
manifest.overrides[key] = value;
|
|
215
120
|
}
|
|
216
121
|
}
|
|
217
122
|
return shouldBeUpdated;
|
|
218
123
|
}
|
|
219
|
-
function
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
packageReferences[pkgName].add(version);
|
|
224
|
-
}
|
|
225
|
-
// The `minimumReleaseAgeExcludePrune` / `trustPolicyExcludePrune` pass over
|
|
226
|
-
// the exclude list `field` names. An entry is dropped when the freshly
|
|
227
|
-
// resolved lockfile no longer contains what it names: exact versions that
|
|
228
|
-
// were not resolved are dropped (the entry goes away once none remain), and a
|
|
229
|
-
// bare-name entry goes away when the package is absent entirely. Glob patterns
|
|
230
|
-
// always stay — they are forward-looking and can't be proven stale. Entries
|
|
231
|
-
// that fail to parse stay untouched so cleanup never breaks an install.
|
|
232
|
-
function pruneExcludeList(manifest, field, resolvedPackageVersions) {
|
|
233
|
-
const excludes = manifest[field];
|
|
234
|
-
if (excludes == null || excludes.length === 0) {
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
const survivingSpecs = [];
|
|
238
|
-
let changed = false;
|
|
239
|
-
for (const entry of excludes) {
|
|
240
|
-
let packageName;
|
|
241
|
-
let exactVersions;
|
|
242
|
-
try {
|
|
243
|
-
const rule = parseVersionPolicyRule(entry);
|
|
244
|
-
packageName = rule.packageName;
|
|
245
|
-
exactVersions = rule.exactVersions;
|
|
246
|
-
}
|
|
247
|
-
catch {
|
|
248
|
-
survivingSpecs.push(entry);
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
if (exactVersions.length === 0) {
|
|
252
|
-
if (packageName.includes('*') || resolvedPackageVersions.has(packageName)) {
|
|
253
|
-
survivingSpecs.push(entry);
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
changed = true;
|
|
257
|
-
}
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
const resolved = resolvedPackageVersions.get(packageName);
|
|
261
|
-
const survivingVersions = exactVersions.filter((version) => resolved?.has(version));
|
|
262
|
-
if (survivingVersions.length === exactVersions.length) {
|
|
263
|
-
survivingSpecs.push(entry);
|
|
264
|
-
}
|
|
265
|
-
else if (survivingVersions.length > 0) {
|
|
266
|
-
survivingSpecs.push(...mergePackageVersionSpecs([`${packageName}@${survivingVersions.join(' || ')}`]));
|
|
267
|
-
changed = true;
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
changed = true;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
if (!changed) {
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
|
-
if (survivingSpecs.length === 0) {
|
|
277
|
-
delete manifest[field];
|
|
124
|
+
function pruneResolvedPackageLists(manifest, opts, resolvedPackageVersions) {
|
|
125
|
+
let shouldBeUpdated = false;
|
|
126
|
+
if (opts.minimumReleaseAgeExcludePrune) {
|
|
127
|
+
shouldBeUpdated = pruneExcludeList(manifest, 'minimumReleaseAgeExclude', resolvedPackageVersions) || shouldBeUpdated;
|
|
278
128
|
}
|
|
279
|
-
|
|
280
|
-
|
|
129
|
+
if (opts.trustPolicyExcludePrune) {
|
|
130
|
+
shouldBeUpdated = pruneExcludeList(manifest, 'trustPolicyExclude', resolvedPackageVersions) || shouldBeUpdated;
|
|
281
131
|
}
|
|
282
|
-
return
|
|
132
|
+
return pruneAllowBuilds(manifest, resolvedPackageVersions) || shouldBeUpdated;
|
|
283
133
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
* precedence, so a stale canonical list can't shadow the update on the next
|
|
289
|
-
* read. When both spellings are present, the shadowed deprecated list is
|
|
290
|
-
* removed as part of the write. `auditConfig.ignoreGhsas` is created when
|
|
291
|
-
* neither is present. An empty `ghsas` removes the list, dropping its parent
|
|
292
|
-
* block when nothing else remains in it. Returns whether anything changed.
|
|
293
|
-
*/
|
|
294
|
-
function setAuditIgnoreGhsas(manifest, ghsas) {
|
|
295
|
-
let changed = false;
|
|
296
|
-
if (manifest.audit?.ignore != null) {
|
|
297
|
-
if (ghsas.length === 0) {
|
|
298
|
-
delete manifest.audit.ignore;
|
|
299
|
-
if (Object.keys(manifest.audit).length === 0) {
|
|
300
|
-
delete manifest.audit;
|
|
301
|
-
}
|
|
302
|
-
changed = true;
|
|
303
|
-
}
|
|
304
|
-
else if (!equals(manifest.audit.ignore, ghsas)) {
|
|
305
|
-
manifest.audit.ignore = ghsas;
|
|
306
|
-
changed = true;
|
|
307
|
-
}
|
|
308
|
-
if (manifest.auditConfig?.ignoreGhsas != null) {
|
|
309
|
-
changed = removeAuditConfigIgnoreGhsas(manifest) || changed;
|
|
310
|
-
}
|
|
311
|
-
return changed;
|
|
312
|
-
}
|
|
313
|
-
if (ghsas.length === 0) {
|
|
314
|
-
return removeAuditConfigIgnoreGhsas(manifest);
|
|
315
|
-
}
|
|
316
|
-
if (equals(manifest.auditConfig?.ignoreGhsas, ghsas)) {
|
|
134
|
+
function addMinimumReleaseAgeExcludes(manifest, addedExcludes) {
|
|
135
|
+
const existing = manifest.minimumReleaseAgeExclude ?? [];
|
|
136
|
+
const merged = mergePackageVersionSpecs([...existing, ...addedExcludes]);
|
|
137
|
+
if (equals(existing, merged)) {
|
|
317
138
|
return false;
|
|
318
139
|
}
|
|
319
|
-
manifest.
|
|
140
|
+
manifest.minimumReleaseAgeExclude = merged;
|
|
320
141
|
return true;
|
|
321
142
|
}
|
|
322
|
-
function removeAuditConfigIgnoreGhsas(manifest) {
|
|
323
|
-
if (manifest.auditConfig?.ignoreGhsas == null) {
|
|
324
|
-
return false;
|
|
325
|
-
}
|
|
326
|
-
delete manifest.auditConfig.ignoreGhsas;
|
|
327
|
-
if (Object.keys(manifest.auditConfig).length === 0) {
|
|
328
|
-
delete manifest.auditConfig;
|
|
329
|
-
}
|
|
330
|
-
return true;
|
|
331
|
-
}
|
|
332
|
-
// Captures only the key order at each nested level of a plain-object value,
|
|
333
|
-
// without duplicating the values themselves. Used as a lightweight snapshot of
|
|
334
|
-
// the original manifest layout so `reorderRecursive` can decide where to place
|
|
335
|
-
// new keys without holding a structural clone of the entire manifest.
|
|
336
|
-
function captureKeyOrder(value) {
|
|
337
|
-
if (!isPlainObject(value))
|
|
338
|
-
return null;
|
|
339
|
-
const children = {};
|
|
340
|
-
for (const [key, child] of Object.entries(value)) {
|
|
341
|
-
const childOrder = captureKeyOrder(child);
|
|
342
|
-
if (childOrder != null) {
|
|
343
|
-
children[key] = childOrder;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
return { keys: Object.keys(value), children };
|
|
347
|
-
}
|
|
348
|
-
// Reorders the keys of `current` based on how the keys were arranged in the
|
|
349
|
-
// original manifest. Two "sorted" layouts are recognized:
|
|
350
|
-
//
|
|
351
|
-
// 1. fully alphabetical
|
|
352
|
-
// 2. a leading "packages" key followed by alphabetical
|
|
353
|
-
//
|
|
354
|
-
// When the original matches one of those layouts, new keys are inserted in
|
|
355
|
-
// alphabetical position (preserving the leading "packages" if applicable).
|
|
356
|
-
// Otherwise the existing order is preserved and new keys are appended at the
|
|
357
|
-
// end. New manifests (no original keys) default to layout (2) to match the
|
|
358
|
-
// pnpm convention of placing "packages" first.
|
|
359
|
-
function reorderRecursive(originalOrder, current) {
|
|
360
|
-
if (!isPlainObject(current))
|
|
361
|
-
return current;
|
|
362
|
-
const originalKeys = originalOrder?.keys ?? [];
|
|
363
|
-
const originalKeySet = new Set(originalKeys);
|
|
364
|
-
const survivingOriginal = originalKeys.filter((key) => Object.hasOwn(current, key));
|
|
365
|
-
const newKeys = Object.keys(current).filter((key) => !originalKeySet.has(key));
|
|
366
|
-
let orderedKeys;
|
|
367
|
-
if (newKeys.length === 0) {
|
|
368
|
-
orderedKeys = survivingOriginal;
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
const layout = detectKeyLayout(originalKeys);
|
|
372
|
-
orderedKeys = layout === 'unordered'
|
|
373
|
-
? [...survivingOriginal, ...newKeys]
|
|
374
|
-
: sortKeys([...survivingOriginal, ...newKeys], layout);
|
|
375
|
-
}
|
|
376
|
-
const result = {};
|
|
377
|
-
for (const key of orderedKeys) {
|
|
378
|
-
result[key] = reorderRecursive(originalOrder?.children[key] ?? null, current[key]);
|
|
379
|
-
}
|
|
380
|
-
return result;
|
|
381
|
-
}
|
|
382
|
-
function detectKeyLayout(keys) {
|
|
383
|
-
if (keys.length === 0)
|
|
384
|
-
return 'packages-first';
|
|
385
|
-
const packagesFirst = keys[0] === 'packages';
|
|
386
|
-
const start = packagesFirst ? 1 : 0;
|
|
387
|
-
for (let i = start + 1; i < keys.length; i++) {
|
|
388
|
-
if (lexCompare(keys[i - 1], keys[i]) > 0)
|
|
389
|
-
return 'unordered';
|
|
390
|
-
}
|
|
391
|
-
return packagesFirst ? 'packages-first' : 'alphabetical';
|
|
392
|
-
}
|
|
393
|
-
function sortKeys(keys, layout) {
|
|
394
|
-
if (layout === 'packages-first' && keys.includes('packages')) {
|
|
395
|
-
return ['packages', ...keys.filter((key) => key !== 'packages').sort(lexCompare)];
|
|
396
|
-
}
|
|
397
|
-
return [...keys].sort(lexCompare);
|
|
398
|
-
}
|
|
399
|
-
function isPlainObject(value) {
|
|
400
|
-
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
401
|
-
}
|
|
402
|
-
// New top-level pairs are inserted without `spaceBefore`, which glues them to
|
|
403
|
-
// the preceding pair even when the document otherwise uses blank-line
|
|
404
|
-
// separators between fields. Detect that style and propagate it to inserted
|
|
405
|
-
// entries (including reordering-induced changes such as a new key sorting to
|
|
406
|
-
// the front, which demotes the previously-first existing pair to a position
|
|
407
|
-
// that should now have a blank before it).
|
|
408
|
-
//
|
|
409
|
-
// The yaml library reads `spaceBefore` from the pair's key node when rendering
|
|
410
|
-
// block collections, not from the pair itself.
|
|
411
|
-
function propagateBlankLinesToNewPairs(document, originalTopLevelKeys) {
|
|
412
|
-
if (!yaml.isMap(document.contents))
|
|
413
|
-
return;
|
|
414
|
-
const items = document.contents.items;
|
|
415
|
-
const keyOf = (pair) => yaml.isScalar(pair.key) && typeof pair.key.value === 'string'
|
|
416
|
-
? pair.key
|
|
417
|
-
: null;
|
|
418
|
-
const originalKeySet = new Set(originalTopLevelKeys);
|
|
419
|
-
// The originally-first pair never had `spaceBefore` set even in a
|
|
420
|
-
// blank-line-separated document — exclude it when judging the document's
|
|
421
|
-
// style so we still detect the style when that pair has been moved.
|
|
422
|
-
const originalFirstKey = originalTopLevelKeys[0] ?? null;
|
|
423
|
-
let originalNonFirstCount = 0;
|
|
424
|
-
let originalNonFirstWithBlank = 0;
|
|
425
|
-
for (const item of items) {
|
|
426
|
-
const k = keyOf(item);
|
|
427
|
-
if (k == null || !originalKeySet.has(k.value) || k.value === originalFirstKey)
|
|
428
|
-
continue;
|
|
429
|
-
originalNonFirstCount++;
|
|
430
|
-
if (k.spaceBefore)
|
|
431
|
-
originalNonFirstWithBlank++;
|
|
432
|
-
}
|
|
433
|
-
const usesBlankLineStyle = originalNonFirstCount > 0 && originalNonFirstWithBlank === originalNonFirstCount;
|
|
434
|
-
for (let i = 1; i < items.length; i++) {
|
|
435
|
-
const key = keyOf(items[i]);
|
|
436
|
-
if (key == null || key.spaceBefore)
|
|
437
|
-
continue;
|
|
438
|
-
if (usesBlankLineStyle) {
|
|
439
|
-
key.spaceBefore = true;
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
if (originalKeySet.has(key.value))
|
|
443
|
-
continue;
|
|
444
|
-
const nextKey = items[i + 1] ? keyOf(items[i + 1]) : null;
|
|
445
|
-
const prevKey = items[i - 1] ? keyOf(items[i - 1]) : null;
|
|
446
|
-
if (nextKey?.spaceBefore || (nextKey == null && prevKey?.spaceBefore)) {
|
|
447
|
-
key.spaceBefore = true;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
// Drops undecided placeholder entries whose package is provably absent from
|
|
452
|
-
// the resolved lockfile. Explicit decisions, keys with no provable package
|
|
453
|
-
// name, and entries for still-resolved packages always stay.
|
|
454
|
-
function pruneAllowBuilds(manifest, resolvedPackageVersions) {
|
|
455
|
-
const allowBuilds = manifest.allowBuilds;
|
|
456
|
-
if (allowBuilds == null) {
|
|
457
|
-
return false;
|
|
458
|
-
}
|
|
459
|
-
let changed = false;
|
|
460
|
-
for (const [key, value] of Object.entries(allowBuilds)) {
|
|
461
|
-
if (value !== UNDECIDED_ALLOW_BUILD)
|
|
462
|
-
continue;
|
|
463
|
-
const packageName = packageNameFromAllowBuildKey(key);
|
|
464
|
-
if (packageName == null || resolvedPackageVersions.has(packageName))
|
|
465
|
-
continue;
|
|
466
|
-
delete allowBuilds[key];
|
|
467
|
-
changed = true;
|
|
468
|
-
}
|
|
469
|
-
if (changed && Object.keys(allowBuilds).length === 0) {
|
|
470
|
-
delete manifest.allowBuilds;
|
|
471
|
-
}
|
|
472
|
-
return changed;
|
|
473
|
-
}
|
|
474
143
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import yaml from 'yaml';
|
|
2
|
+
export interface KeyOrderNode {
|
|
3
|
+
keys: string[];
|
|
4
|
+
children: Map<string, KeyOrderNode>;
|
|
5
|
+
}
|
|
6
|
+
export declare function captureKeyOrder(value: unknown): KeyOrderNode | null;
|
|
7
|
+
export declare function reorderRecursive(originalOrder: KeyOrderNode | null, current: unknown): unknown;
|
|
8
|
+
export declare function propagateBlankLinesToNewPairs(document: yaml.Document, originalTopLevelKeys: readonly string[]): void;
|
package/lib/keyOrder.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { lexCompare } from '@pnpm/text.ordinal-comparator';
|
|
2
|
+
import yaml from 'yaml';
|
|
3
|
+
// Captures only the key order at each nested level of a plain-object value,
|
|
4
|
+
// without duplicating the values themselves. Used as a lightweight snapshot of
|
|
5
|
+
// the original manifest layout so `reorderRecursive` can decide where to place
|
|
6
|
+
// new keys without holding a structural clone of the entire manifest.
|
|
7
|
+
export function captureKeyOrder(value) {
|
|
8
|
+
if (!isPlainObject(value))
|
|
9
|
+
return null;
|
|
10
|
+
const children = new Map();
|
|
11
|
+
for (const [key, child] of Object.entries(value)) {
|
|
12
|
+
const childOrder = captureKeyOrder(child);
|
|
13
|
+
if (childOrder != null) {
|
|
14
|
+
children.set(key, childOrder);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return { keys: Object.keys(value), children };
|
|
18
|
+
}
|
|
19
|
+
// Reorders the keys of `current` based on how the keys were arranged in the
|
|
20
|
+
// original manifest. Two "sorted" layouts are recognized:
|
|
21
|
+
//
|
|
22
|
+
// 1. fully alphabetical
|
|
23
|
+
// 2. a leading "packages" key followed by alphabetical
|
|
24
|
+
//
|
|
25
|
+
// When the original matches one of those layouts, new keys are inserted in
|
|
26
|
+
// alphabetical position (preserving the leading "packages" if applicable).
|
|
27
|
+
// Otherwise the existing order is preserved and new keys are appended at the
|
|
28
|
+
// end. New manifests (no original keys) default to layout (2) to match the
|
|
29
|
+
// pnpm convention of placing "packages" first.
|
|
30
|
+
export function reorderRecursive(originalOrder, current) {
|
|
31
|
+
if (!isPlainObject(current))
|
|
32
|
+
return current;
|
|
33
|
+
const originalKeys = originalOrder?.keys ?? [];
|
|
34
|
+
const originalKeySet = new Set(originalKeys);
|
|
35
|
+
const survivingOriginal = originalKeys.filter((key) => Object.hasOwn(current, key));
|
|
36
|
+
const newKeys = Object.keys(current).filter((key) => !originalKeySet.has(key));
|
|
37
|
+
let orderedKeys;
|
|
38
|
+
if (newKeys.length === 0) {
|
|
39
|
+
orderedKeys = survivingOriginal;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const layout = detectKeyLayout(originalKeys);
|
|
43
|
+
orderedKeys = layout === 'unordered'
|
|
44
|
+
? [...survivingOriginal, ...newKeys]
|
|
45
|
+
: sortKeys([...survivingOriginal, ...newKeys], layout);
|
|
46
|
+
}
|
|
47
|
+
const result = {};
|
|
48
|
+
for (const key of orderedKeys) {
|
|
49
|
+
result[key] = reorderRecursive(originalOrder?.children.get(key) ?? null, current[key]);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function detectKeyLayout(keys) {
|
|
54
|
+
if (keys.length === 0)
|
|
55
|
+
return 'packages-first';
|
|
56
|
+
const packagesFirst = keys[0] === 'packages';
|
|
57
|
+
const start = packagesFirst ? 1 : 0;
|
|
58
|
+
for (let keyIndex = start + 1; keyIndex < keys.length; keyIndex++) {
|
|
59
|
+
if (lexCompare(keys[keyIndex - 1], keys[keyIndex]) > 0)
|
|
60
|
+
return 'unordered';
|
|
61
|
+
}
|
|
62
|
+
return packagesFirst ? 'packages-first' : 'alphabetical';
|
|
63
|
+
}
|
|
64
|
+
function sortKeys(keys, layout) {
|
|
65
|
+
if (layout === 'packages-first' && keys.includes('packages')) {
|
|
66
|
+
return ['packages', ...keys.filter((key) => key !== 'packages').sort(lexCompare)];
|
|
67
|
+
}
|
|
68
|
+
return [...keys].sort(lexCompare);
|
|
69
|
+
}
|
|
70
|
+
function isPlainObject(value) {
|
|
71
|
+
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
72
|
+
}
|
|
73
|
+
// New top-level pairs are inserted without `spaceBefore`, which glues them to
|
|
74
|
+
// the preceding pair even when the document otherwise uses blank-line
|
|
75
|
+
// separators between fields. Detect that style and propagate it to inserted
|
|
76
|
+
// entries (including reordering-induced changes such as a new key sorting to
|
|
77
|
+
// the front, which demotes the previously-first existing pair to a position
|
|
78
|
+
// that should now have a blank before it).
|
|
79
|
+
//
|
|
80
|
+
// The yaml library reads `spaceBefore` from the pair's key node when rendering
|
|
81
|
+
// block collections, not from the pair itself.
|
|
82
|
+
export function propagateBlankLinesToNewPairs(document, originalTopLevelKeys) {
|
|
83
|
+
if (!yaml.isMap(document.contents))
|
|
84
|
+
return;
|
|
85
|
+
const items = document.contents.items;
|
|
86
|
+
const originalKeySet = new Set(originalTopLevelKeys);
|
|
87
|
+
const usesBlankLineStyle = detectBlankLineStyle(items, originalTopLevelKeys);
|
|
88
|
+
for (let itemIndex = 1; itemIndex < items.length; itemIndex++) {
|
|
89
|
+
const key = stringKeyOf(items[itemIndex]);
|
|
90
|
+
if (key == null || key.spaceBefore)
|
|
91
|
+
continue;
|
|
92
|
+
if (usesBlankLineStyle || (!originalKeySet.has(key.value) && neighborHasBlankLine(items, itemIndex))) {
|
|
93
|
+
key.spaceBefore = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function detectBlankLineStyle(items, originalTopLevelKeys) {
|
|
98
|
+
const originalKeySet = new Set(originalTopLevelKeys);
|
|
99
|
+
// The originally-first pair never had `spaceBefore` set even in a
|
|
100
|
+
// blank-line-separated document — exclude it when judging the document's
|
|
101
|
+
// style so we still detect the style when that pair has been moved.
|
|
102
|
+
const originalFirstKey = originalTopLevelKeys[0] ?? null;
|
|
103
|
+
let originalNonFirstCount = 0;
|
|
104
|
+
let originalNonFirstWithBlank = 0;
|
|
105
|
+
for (const item of items) {
|
|
106
|
+
const itemKey = stringKeyOf(item);
|
|
107
|
+
if (itemKey == null || !originalKeySet.has(itemKey.value) || itemKey.value === originalFirstKey)
|
|
108
|
+
continue;
|
|
109
|
+
originalNonFirstCount++;
|
|
110
|
+
if (itemKey.spaceBefore)
|
|
111
|
+
originalNonFirstWithBlank++;
|
|
112
|
+
}
|
|
113
|
+
return originalNonFirstCount > 0 && originalNonFirstWithBlank === originalNonFirstCount;
|
|
114
|
+
}
|
|
115
|
+
function neighborHasBlankLine(items, itemIndex) {
|
|
116
|
+
const nextKey = items[itemIndex + 1] ? stringKeyOf(items[itemIndex + 1]) : null;
|
|
117
|
+
const prevKey = items[itemIndex - 1] ? stringKeyOf(items[itemIndex - 1]) : null;
|
|
118
|
+
return Boolean(nextKey?.spaceBefore || (nextKey == null && prevKey?.spaceBefore));
|
|
119
|
+
}
|
|
120
|
+
function stringKeyOf(pair) {
|
|
121
|
+
return yaml.isScalar(pair.key) && typeof pair.key.value === 'string'
|
|
122
|
+
? pair.key
|
|
123
|
+
: null;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=keyOrder.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WorkspaceManifest } from '@pnpm/workspace.workspace-manifest-reader';
|
|
2
|
+
export type ExcludeListField = 'minimumReleaseAgeExclude' | 'trustPolicyExclude';
|
|
3
|
+
type ResolvedPackageVersions = ReadonlyMap<string, ReadonlySet<string>>;
|
|
4
|
+
export declare function pruneExcludeList(manifest: Partial<WorkspaceManifest> & {
|
|
5
|
+
[key in ExcludeListField]?: string[];
|
|
6
|
+
}, field: ExcludeListField, resolvedPackageVersions: ResolvedPackageVersions): boolean;
|
|
7
|
+
export declare function pruneAllowBuilds(manifest: Partial<WorkspaceManifest>, resolvedPackageVersions: ResolvedPackageVersions): boolean;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { packageNameFromAllowBuildKey, UNDECIDED_ALLOW_BUILD } from '@pnpm/building.policy';
|
|
2
|
+
import { mergePackageVersionSpecs, parseVersionPolicyRule } from '@pnpm/config.version-policy';
|
|
3
|
+
// The `minimumReleaseAgeExcludePrune` / `trustPolicyExcludePrune` pass over
|
|
4
|
+
// the exclude list `field` names. An entry is dropped when the freshly
|
|
5
|
+
// resolved lockfile no longer contains what it names: exact versions that
|
|
6
|
+
// were not resolved are dropped (the entry goes away once none remain), and a
|
|
7
|
+
// bare-name entry goes away when the package is absent entirely. Glob patterns
|
|
8
|
+
// always stay — they are forward-looking and can't be proven stale. Entries
|
|
9
|
+
// that fail to parse stay untouched so cleanup never breaks an install.
|
|
10
|
+
export function pruneExcludeList(manifest, field, resolvedPackageVersions) {
|
|
11
|
+
const excludes = manifest[field];
|
|
12
|
+
if (excludes == null || excludes.length === 0) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const survivingSpecs = [];
|
|
16
|
+
let changed = false;
|
|
17
|
+
for (const entry of excludes) {
|
|
18
|
+
const prunedEntry = pruneExcludeEntry(entry, resolvedPackageVersions);
|
|
19
|
+
survivingSpecs.push(...prunedEntry.survivingSpecs);
|
|
20
|
+
changed = prunedEntry.changed || changed;
|
|
21
|
+
}
|
|
22
|
+
if (!changed) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (survivingSpecs.length === 0) {
|
|
26
|
+
delete manifest[field];
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
manifest[field] = survivingSpecs;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
function pruneExcludeEntry(entry, resolvedPackageVersions) {
|
|
34
|
+
const rule = tryParseVersionPolicyRule(entry);
|
|
35
|
+
if (rule == null) {
|
|
36
|
+
return { survivingSpecs: [entry], changed: false };
|
|
37
|
+
}
|
|
38
|
+
const { packageName, exactVersions } = rule;
|
|
39
|
+
if (exactVersions.length === 0) {
|
|
40
|
+
const isStillRelevant = packageName.includes('*') || resolvedPackageVersions.has(packageName);
|
|
41
|
+
return isStillRelevant
|
|
42
|
+
? { survivingSpecs: [entry], changed: false }
|
|
43
|
+
: { survivingSpecs: [], changed: true };
|
|
44
|
+
}
|
|
45
|
+
const resolved = resolvedPackageVersions.get(packageName);
|
|
46
|
+
const survivingVersions = exactVersions.filter((version) => resolved?.has(version));
|
|
47
|
+
if (survivingVersions.length === exactVersions.length) {
|
|
48
|
+
return { survivingSpecs: [entry], changed: false };
|
|
49
|
+
}
|
|
50
|
+
if (survivingVersions.length === 0) {
|
|
51
|
+
return { survivingSpecs: [], changed: true };
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
survivingSpecs: mergePackageVersionSpecs([`${packageName}@${survivingVersions.join(' || ')}`]),
|
|
55
|
+
changed: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function tryParseVersionPolicyRule(entry) {
|
|
59
|
+
try {
|
|
60
|
+
return parseVersionPolicyRule(entry);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Drops undecided placeholder entries whose package is provably absent from
|
|
67
|
+
// the resolved lockfile. Explicit decisions, keys with no provable package
|
|
68
|
+
// name, and entries for still-resolved packages always stay.
|
|
69
|
+
export function pruneAllowBuilds(manifest, resolvedPackageVersions) {
|
|
70
|
+
const allowBuilds = manifest.allowBuilds;
|
|
71
|
+
if (allowBuilds == null) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
let changed = false;
|
|
75
|
+
for (const [key, value] of Object.entries(allowBuilds)) {
|
|
76
|
+
if (value !== UNDECIDED_ALLOW_BUILD)
|
|
77
|
+
continue;
|
|
78
|
+
const packageName = packageNameFromAllowBuildKey(key);
|
|
79
|
+
if (packageName == null || resolvedPackageVersions.has(packageName))
|
|
80
|
+
continue;
|
|
81
|
+
delete allowBuilds[key];
|
|
82
|
+
changed = true;
|
|
83
|
+
}
|
|
84
|
+
if (changed && Object.keys(allowBuilds).length === 0) {
|
|
85
|
+
delete manifest.allowBuilds;
|
|
86
|
+
}
|
|
87
|
+
return changed;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=pruneResolvedLists.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/workspace.workspace-manifest-writer",
|
|
3
|
-
"version": "1100.2.
|
|
3
|
+
"version": "1100.2.4",
|
|
4
4
|
"description": "Updates the workspace manifest file",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
"!*.map"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@pnpm/building.policy": "1100.1.
|
|
30
|
+
"@pnpm/building.policy": "1100.1.4",
|
|
31
31
|
"@pnpm/catalogs.types": "1100.0.1",
|
|
32
|
-
"@pnpm/config.parse-overrides": "1100.1.
|
|
33
|
-
"@pnpm/config.version-policy": "1100.2.
|
|
32
|
+
"@pnpm/config.parse-overrides": "1100.1.7",
|
|
33
|
+
"@pnpm/config.version-policy": "1100.2.5",
|
|
34
34
|
"@pnpm/constants": "1102.0.0",
|
|
35
|
-
"@pnpm/lockfile.types": "1100.1.
|
|
35
|
+
"@pnpm/lockfile.types": "1100.1.4",
|
|
36
36
|
"@pnpm/text.ordinal-comparator": "1100.0.0",
|
|
37
37
|
"@pnpm/types": "1102.1.1",
|
|
38
|
-
"@pnpm/workspace.workspace-manifest-reader": "1100.2.
|
|
38
|
+
"@pnpm/workspace.workspace-manifest-reader": "1100.2.1",
|
|
39
39
|
"@pnpm/yaml.document-sync": "1100.0.2",
|
|
40
40
|
"ramda": "npm:@pnpm/ramda@0.28.1",
|
|
41
41
|
"write-file-atomic": "^7.0.1",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@jest/globals": "30.4.1",
|
|
46
|
-
"@pnpm/prepare": "1100.0.
|
|
46
|
+
"@pnpm/prepare": "1100.0.34",
|
|
47
47
|
"@pnpm/prepare-temp-dir": "1100.0.1",
|
|
48
|
-
"@pnpm/workspace.projects-reader": "1101.1.
|
|
49
|
-
"@pnpm/workspace.workspace-manifest-writer": "1100.2.
|
|
48
|
+
"@pnpm/workspace.projects-reader": "1101.1.1",
|
|
49
|
+
"@pnpm/workspace.workspace-manifest-writer": "1100.2.4",
|
|
50
50
|
"@types/ramda": "0.32.0",
|
|
51
51
|
"@types/write-file-atomic": "^4.0.3",
|
|
52
52
|
"read-yaml-file": "^3.0.0",
|