@pnpm/yaml.document-sync 1100.0.1 → 1100.0.2
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 +8 -0
- package/README.md +24 -0
- package/lib/patchDocument.d.ts +13 -0
- package/lib/patchDocument.js +30 -20
- package/lib/preserveScalarAliases.d.ts +2 -0
- package/lib/preserveScalarAliases.js +83 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @pnpm/yaml.document-sync
|
|
2
2
|
|
|
3
|
+
## 1100.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- pnpm now preserves scalar YAML anchors and aliases when editing `pnpm-workspace.yaml`. Removing the entry that defines an anchor keeps surviving aliases valid. Entries updated to different values are written separately [#8245](https://github.com/pnpm/pnpm/issues/8245).
|
|
8
|
+
|
|
9
|
+
- pnpm now preserves comments and existing key order when updating `package.yaml`. New keys are appended to their mapping [pnpm/pnpm#2008](https://github.com/pnpm/pnpm/issues/2008).
|
|
10
|
+
|
|
3
11
|
## 1100.0.1
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -60,6 +60,20 @@ qux:
|
|
|
60
60
|
- 3
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
For package manifests, retain null values and empty maps and keep existing keys in their original order:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
patchDocument(document, target, {
|
|
67
|
+
preserveKeyOrder: true,
|
|
68
|
+
preserveScalarAliases: true,
|
|
69
|
+
pruneEmptyValues: false,
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
New keys are appended to their mapping. By default, keys follow the target object's order, and null values and empty maps are pruned from existing nodes.
|
|
74
|
+
|
|
75
|
+
Consumers that use a different conversion for scalar mapping keys can pass `stringifyKey`. For example, `stringifyKey: String` matches `js-yaml`, which exposes a null mapping key as the property name `"null"`. The default follows `yaml` and uses an empty string.
|
|
76
|
+
|
|
63
77
|
## Purpose
|
|
64
78
|
|
|
65
79
|
This package is useful when your codebase:
|
|
@@ -164,6 +178,16 @@ bar:
|
|
|
164
178
|
- 3
|
|
165
179
|
```
|
|
166
180
|
|
|
181
|
+
Set `preserveScalarAliases: true` to retain scalar aliases whose final values
|
|
182
|
+
agree. If an anchor's defining entry is removed, the first surviving entry
|
|
183
|
+
defines it. An entry whose value differs from the defining entry is written
|
|
184
|
+
as a scalar. This option does not coordinate dependency updates; it preserves
|
|
185
|
+
YAML representation while applying the supplied target values.
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
patchDocument(document, target, { preserveScalarAliases: true })
|
|
189
|
+
```
|
|
190
|
+
|
|
167
191
|
## License
|
|
168
192
|
|
|
169
193
|
MIT
|
package/lib/patchDocument.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import yaml from 'yaml';
|
|
2
2
|
export interface PatchDocumentOptions {
|
|
3
|
+
/** Convert scalar keys to target property names. Defaults to YAML's null-to-empty-string conversion. */
|
|
4
|
+
readonly stringifyKey?: (key: unknown) => string;
|
|
5
|
+
/** Keep existing map keys in their original order and append new keys. */
|
|
6
|
+
readonly preserveKeyOrder?: boolean;
|
|
7
|
+
/** Remove null values and empty maps. Defaults to true for configuration files. */
|
|
8
|
+
readonly pruneEmptyValues?: boolean;
|
|
3
9
|
/**
|
|
4
10
|
* Updating aliases is inherently ambiguous since they're not a concept in
|
|
5
11
|
* JSON. The default is to unwrap and remove aliases since that's the most
|
|
@@ -12,6 +18,13 @@ export interface PatchDocumentOptions {
|
|
|
12
18
|
* @default 'unwrap'
|
|
13
19
|
*/
|
|
14
20
|
readonly aliases?: 'unwrap' | 'follow';
|
|
21
|
+
/**
|
|
22
|
+
* Keep scalar aliases whose final values agree, moving removed anchors to a
|
|
23
|
+
* surviving entry. Divergent values become independent scalars. When enabled,
|
|
24
|
+
* this takes precedence over `aliases` for scalar nodes only.
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
readonly preserveScalarAliases?: boolean;
|
|
15
28
|
}
|
|
16
29
|
/**
|
|
17
30
|
* Recursively update a YAML document (in-place) to match the contents of a
|
package/lib/patchDocument.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import yaml from 'yaml';
|
|
2
|
+
import { preserveScalarAliases } from './preserveScalarAliases.js';
|
|
2
3
|
/**
|
|
3
4
|
* Recursively update a YAML document (in-place) to match the contents of a
|
|
4
5
|
* target value.
|
|
@@ -11,17 +12,22 @@ export function patchDocument(document, target, options) {
|
|
|
11
12
|
if (document.errors.length > 0) {
|
|
12
13
|
throw new Error('Document with errors cannot be patched');
|
|
13
14
|
}
|
|
15
|
+
const restoreAliases = options?.preserveScalarAliases ? preserveScalarAliases(document) : undefined;
|
|
14
16
|
document.contents = patchNode(document.contents, target, {
|
|
17
|
+
...options,
|
|
15
18
|
document,
|
|
16
19
|
aliases: options?.aliases ?? 'unwrap',
|
|
17
20
|
});
|
|
21
|
+
restoreAliases?.();
|
|
18
22
|
}
|
|
19
23
|
function patchNode(node, target, ctx) {
|
|
20
24
|
if (node == null) {
|
|
21
25
|
return ctx.document.createNode(target);
|
|
22
26
|
}
|
|
23
27
|
if (target == null) {
|
|
24
|
-
|
|
28
|
+
if (ctx.pruneEmptyValues !== false)
|
|
29
|
+
return null;
|
|
30
|
+
return yaml.isScalar(node) && node.value === target ? node : ctx.document.createNode(target);
|
|
25
31
|
}
|
|
26
32
|
if (yaml.isAlias(node)) {
|
|
27
33
|
return patchAlias(node, target, ctx);
|
|
@@ -56,8 +62,7 @@ function patchAlias(alias, target, ctx) {
|
|
|
56
62
|
case 'unwrap': {
|
|
57
63
|
const copy = resolved.clone();
|
|
58
64
|
copy.anchor = undefined;
|
|
59
|
-
patchNode(copy, target, ctx);
|
|
60
|
-
return copy;
|
|
65
|
+
return patchNode(copy, target, ctx);
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
}
|
|
@@ -65,37 +70,42 @@ function patchScalar(scalar, target, ctx) {
|
|
|
65
70
|
if (scalar.value === target) {
|
|
66
71
|
return scalar;
|
|
67
72
|
}
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
const replacement = ctx.document.createNode(target);
|
|
74
|
+
if (yaml.isScalar(replacement)) {
|
|
75
|
+
scalar.value = replacement.value;
|
|
76
|
+
scalar.tag = replacement.tag;
|
|
70
77
|
return scalar;
|
|
71
78
|
}
|
|
72
|
-
return
|
|
79
|
+
return replacement;
|
|
73
80
|
}
|
|
74
81
|
function patchMap(map, target, ctx) {
|
|
75
82
|
if (!isRecord(target)) {
|
|
76
83
|
return ctx.document.createNode(target);
|
|
77
84
|
}
|
|
78
|
-
|
|
79
|
-
// empty maps in the final document.
|
|
80
|
-
if (target == null || Object.keys(target).length === 0) {
|
|
85
|
+
if (ctx.pruneEmptyValues !== false && Object.keys(target).length === 0) {
|
|
81
86
|
return null;
|
|
82
87
|
}
|
|
83
88
|
const mapKeyToExistingPair = new Map();
|
|
84
89
|
for (const pair of map.items) {
|
|
85
90
|
// We can't update non-node types. Pairs should only contain values that are
|
|
86
91
|
// non-nodes if the yaml document was modified manually after parsing.
|
|
87
|
-
if (!yaml.isScalar(pair.key)
|
|
92
|
+
if (!yaml.isScalar(pair.key)) {
|
|
88
93
|
throw new Error('Encountered unexpected non-node value: ' + String(pair.key));
|
|
89
94
|
}
|
|
90
|
-
mapKeyToExistingPair.set(pair.key.value, pair);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.
|
|
95
|
+
mapKeyToExistingPair.set(ctx.stringifyKey?.(pair.key.value) ?? String(pair.key.value ?? ''), pair);
|
|
96
|
+
}
|
|
97
|
+
const keys = ctx.preserveKeyOrder
|
|
98
|
+
? [...mapKeyToExistingPair.keys()].filter(key => Object.hasOwn(target, key))
|
|
99
|
+
.concat(Object.keys(target).filter(key => !mapKeyToExistingPair.has(key)))
|
|
100
|
+
: Object.keys(target);
|
|
101
|
+
map.items = keys
|
|
102
|
+
.map(key => {
|
|
103
|
+
const value = target[key];
|
|
94
104
|
const existingPair = mapKeyToExistingPair.get(key);
|
|
95
105
|
if (existingPair == null) {
|
|
96
106
|
return ctx.document.createPair(key, value);
|
|
97
107
|
}
|
|
98
|
-
if (!yaml.isNode(existingPair.value)) {
|
|
108
|
+
if (existingPair.value != null && !yaml.isNode(existingPair.value)) {
|
|
99
109
|
throw new Error('Encountered unexpected non-node value: ' + String(existingPair.value));
|
|
100
110
|
}
|
|
101
111
|
existingPair.value = patchNode(existingPair.value, value, ctx);
|
|
@@ -118,10 +128,10 @@ function patchSeq(seq, target, ctx) {
|
|
|
118
128
|
// problem becomes important in the future, it may be worth making callers to
|
|
119
129
|
// pass in a getKeyForNode() function.
|
|
120
130
|
return isPrimitiveList(target)
|
|
121
|
-
? patchSeqPrimitive(seq, target)
|
|
131
|
+
? patchSeqPrimitive(seq, target, ctx)
|
|
122
132
|
: patchSeqComplex(seq, target, ctx);
|
|
123
133
|
}
|
|
124
|
-
function patchSeqPrimitive(seq, target) {
|
|
134
|
+
function patchSeqPrimitive(seq, target, ctx) {
|
|
125
135
|
// Keep track of existing nodes to reuse when building up the final list from
|
|
126
136
|
// the target list. These nodes will have comments attached to them, so it's
|
|
127
137
|
// important to reuse them when possible.
|
|
@@ -133,14 +143,14 @@ function patchSeqPrimitive(seq, target) {
|
|
|
133
143
|
// We know all items in the target list are scalars. If there's a non-scalar
|
|
134
144
|
// in the source list, it needs to be removed. Skip over this item so it's
|
|
135
145
|
// not added to the final list.
|
|
136
|
-
if (!yaml.isScalar(item) || !isPrimitive(item.value) || item.value == null) {
|
|
146
|
+
if (!yaml.isScalar(item) || !isPrimitive(item.value) || (item.value == null && ctx.pruneEmptyValues !== false)) {
|
|
137
147
|
continue;
|
|
138
148
|
}
|
|
139
149
|
const nodeList = valueToNodesMap.get(item.value) ?? [];
|
|
140
150
|
nodeList.push(item);
|
|
141
151
|
valueToNodesMap.set(item.value, nodeList);
|
|
142
152
|
}
|
|
143
|
-
seq.items = target.filter(item => item != null).map((item) => {
|
|
153
|
+
seq.items = target.filter(item => item != null || ctx.pruneEmptyValues === false).map((item) => {
|
|
144
154
|
const existingNodesList = valueToNodesMap.get(item);
|
|
145
155
|
const firstExistingItem = existingNodesList?.shift();
|
|
146
156
|
// If the list is now empty as a result of removing the first item, clean up
|
|
@@ -154,7 +164,7 @@ function patchSeqPrimitive(seq, target) {
|
|
|
154
164
|
}
|
|
155
165
|
function patchSeqComplex(seq, target, ctx) {
|
|
156
166
|
const nextItems = [];
|
|
157
|
-
for (let i = 0; i <
|
|
167
|
+
for (let i = 0; i < target.length; i++) {
|
|
158
168
|
const existingItem = seq.items[i];
|
|
159
169
|
const targetItem = target[i];
|
|
160
170
|
if (existingItem != null && !yaml.isNode(existingItem)) {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import yaml from 'yaml';
|
|
2
|
+
export function preserveScalarAliases(document) {
|
|
3
|
+
const groups = new Map();
|
|
4
|
+
const replacements = new Map();
|
|
5
|
+
yaml.visit(document, {
|
|
6
|
+
Scalar(_, node) {
|
|
7
|
+
if (node.anchor)
|
|
8
|
+
groups.set(node, node);
|
|
9
|
+
},
|
|
10
|
+
Alias(_, node) {
|
|
11
|
+
const source = node.resolve(document);
|
|
12
|
+
if (!yaml.isScalar(source))
|
|
13
|
+
return;
|
|
14
|
+
const copy = source.clone();
|
|
15
|
+
copy.anchor = undefined;
|
|
16
|
+
copy.comment = node.comment;
|
|
17
|
+
copy.commentBefore = node.commentBefore;
|
|
18
|
+
copy.spaceBefore = node.spaceBefore;
|
|
19
|
+
groups.set(copy, source);
|
|
20
|
+
replacements.set(node, copy);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
yaml.visit(document, {
|
|
24
|
+
Alias(_, node) {
|
|
25
|
+
return replacements.get(node);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
return () => {
|
|
29
|
+
assignUniqueNames(document, new Set(groups.values()));
|
|
30
|
+
const anchors = new Map();
|
|
31
|
+
yaml.visit(document, {
|
|
32
|
+
Scalar(_, node) {
|
|
33
|
+
const source = groups.get(node);
|
|
34
|
+
if (!source)
|
|
35
|
+
return;
|
|
36
|
+
const anchor = anchors.get(source);
|
|
37
|
+
if (!anchor) {
|
|
38
|
+
node.anchor = source.anchor;
|
|
39
|
+
anchors.set(source, node);
|
|
40
|
+
}
|
|
41
|
+
else if (Object.is(anchor.value, node.value)) {
|
|
42
|
+
const alias = new yaml.Alias(anchor.anchor);
|
|
43
|
+
alias.comment = node.comment;
|
|
44
|
+
alias.commentBefore = node.commentBefore;
|
|
45
|
+
alias.spaceBefore = node.spaceBefore;
|
|
46
|
+
return alias;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
node.anchor = undefined;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function assignUniqueNames(document, sources) {
|
|
57
|
+
const names = new Map();
|
|
58
|
+
yaml.visit(document, {
|
|
59
|
+
Node(_, node) {
|
|
60
|
+
if (yaml.isAlias(node) || !node.anchor)
|
|
61
|
+
return;
|
|
62
|
+
const nodes = names.get(node.anchor) ?? new Set();
|
|
63
|
+
nodes.add(node);
|
|
64
|
+
names.set(node.anchor, nodes);
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
const nextSuffix = new Map();
|
|
68
|
+
for (const source of sources) {
|
|
69
|
+
const name = source.anchor;
|
|
70
|
+
const nodes = names.get(name);
|
|
71
|
+
if (!nodes || (nodes.size === 1 && nodes.has(source))) {
|
|
72
|
+
names.set(name, new Set([source]));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
let suffix = nextSuffix.get(name) ?? 1;
|
|
76
|
+
while (names.has(`${name}_${suffix}`))
|
|
77
|
+
suffix++;
|
|
78
|
+
nextSuffix.set(name, suffix + 1);
|
|
79
|
+
source.anchor = `${name}_${suffix}`;
|
|
80
|
+
names.set(source.anchor, new Set([source]));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=preserveScalarAliases.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/yaml.document-sync",
|
|
3
|
-
"version": "1100.0.
|
|
3
|
+
"version": "1100.0.2",
|
|
4
4
|
"description": "Update a YAML document to match the contents of an in-memory object.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"!*.map"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"yaml": "^2.9.
|
|
32
|
+
"yaml": "^2.9.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@jest/globals": "30.4.1",
|
|
36
|
-
"@pnpm/yaml.document-sync": "1100.0.
|
|
36
|
+
"@pnpm/yaml.document-sync": "1100.0.2"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=22.13"
|