* docs: lint prose with Vale Add Vale with an in-repo style, .vale/styles/pnpm, that turns the writing rules in AGENTS.md and the Google developer documentation style guide into lint rules. The stock Vale packages (Google, Microsoft, RedHat, write-good) were too noisy to block on, for example 1,300 passive-voice hits in the v12 docs alone, so the rules are written for this repo, following the GitLab model. Each one links the guideline it enforces. Levels: - error, fails CI: dashes, plain-word substitutions, condescending words, "please", "note that", hedges, puffery, optional plurals, exclamation marks, ellipses, curly quotes, generic link text, product spellings, sentence-case headings, repeated words, double spaces, implementation history in comments; in changesets, bold or italic emphasis, internal names and release-line names. - warning: future tense, sentences over 30 words, "instead of" and "so that" tails in changesets. - suggestion: passive voice. .vale.ini scopes the rules. Docs get every rule. Contributor Markdown and code comments keep dashes and headings as warnings until their roughly 8,100 em dashes are rewritten. Released changesets stay in .changeset/ and are not rewritten, so lint-prose.mjs lints only the changesets added since PROSE_BASE (the PR base in CI). The prose workflow installs a pinned, checksum-verified Vale release and runs `pnpm lint:prose`. The rest of this change rewrites the existing docs, READMEs, contributor Markdown, and code comments to pass the error-level rules. Only prose and comment text changed. Renamed headings keep their anchors. Spelling stays with cspell, which checks identifiers that Vale does not see. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: keep prose lint from flattening meaning --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@pnpm/yaml.document-sync
Update a YAML document to match the contents of an in-memory object.
Installation
pnpm add @pnpm/yaml.document-sync
Usage
Given a "source" document such as:
foo:
bar:
# 25 is better than 24
baz: 25
qux:
# He was number 1
- 1
And a "target" object in-memory:
import fs from 'node:fs'
import { patchDocument } from '@pnpm/yaml.document-sync'
import yaml from 'yaml'
const source = await fs.promises.readFile('source.yaml', 'utf8')
const document = yaml.parseDocument(source)
const target = {
foo: { bar: { baz: 25 } },
qux: [1, 2, 3]
}
patchDocument(document, target)
The patchDocument function will mutate document to match the target's contents, retaining comments along the way. In the example above, the final rendered document will be:
foo:
bar:
# 25 is better than 24
baz: 25
qux:
# He was number 1
- 1
- 2
- 3
For package manifests, retain null values and empty maps and keep existing keys in their original order:
patchDocument(document, target, {
preserveKeyOrder: true,
preserveScalarAliases: true,
pruneEmptyValues: false,
})
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.
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.
Purpose
This package is useful when your codebase:
- Uses the yaml library.
- Calls
.toJSON()on the parse result and performs changes to it. - Needs to "sync" those changes back to the source document.
Instead of this package, consider performing mutations directly on the yaml.Document returned from yaml.parseDocument() instead. Directly modifying will be faster and more accurate. This package exists as a workaround for codebases that make changes to a JSON object instead and need to reconcile changes back into the source yaml.Document.
Caveats
There are several cases where comment preservation is inherently ambiguous. If the caveats outlined below are problematic, consider modifying the source yaml.Document before running the patch function in this package.
Key Renames
For example, renames of a key are ambiguous. Given:
- foo:
# Test
bar: 1
And a target object to match:
{
"baz": {
"bar": 1
}
}
The comment on bar won't be retained.
List Reconciliation
For simple lists (e.g. lists with only primitives), items will be uniquely matched using their contents. However, updates to complex lists are inherently ambiguous.
For example, given a source list with objects as elements:
- foo: 1
# Comment
- bar: 2
And a target:
[
{ "foo": 1 },
{ "baz": 3 },
{ "bar": 2 }
]
The result will erase the comment:
- foo: 1
- baz: 3
- bar: 2
It's not trivial to detect that the object with bar as a field moved down. Detecting this case would require a diffing algorithm, which would be best effort anyway.
Virtual DOM libraries such as React have the same problem. In React, list elements need to specify a key prop to uniquely identify each item. This library may take a similar approach in the future if needed. This is not a problem for primitive lists since their values can be compared using simple equality checks.
Aliases
Given:
foo: &config
- 1
- 2
bar: *config
And a target object:
{
"foo": [1, 2],
"bar": [1, 2, 3]
}
For correctness, the YAML alias needs to be removed.
foo: &config
- 1
- 2
bar:
- 1
- 2
- 3
Set preserveScalarAliases: true to retain scalar aliases whose final values
agree. If an anchor's defining entry is removed, the first surviving entry
defines it. An entry whose value differs from the defining entry is written
as a scalar. This option does not coordinate dependency updates; it preserves
YAML representation while applying the supplied target values.
patchDocument(document, target, { preserveScalarAliases: true })
License
MIT