@savvy-web/silk-effects 4.2.4 → 4.2.5
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VERSION_RE } from "../../schemas/dependency-table.js";
|
|
2
2
|
import { RULE_DOCS } from "../../constants.js";
|
|
3
|
-
import { getHeadingLevel, getHeadingText } from "./utils.js";
|
|
3
|
+
import { getHeadingLevel, getHeadingText, unescapeMarkdown } from "./utils.js";
|
|
4
4
|
|
|
5
5
|
//#region src/changesets/markdownlint/rules/dependency-table-format.ts
|
|
6
6
|
const EM_DASH = "—";
|
|
@@ -31,13 +31,13 @@ const EXPECTED_HEADERS = [
|
|
|
31
31
|
* The `tableContent` token has a `.text` property with the cell value.
|
|
32
32
|
*
|
|
33
33
|
* @param cell - A GFM table cell token (header or data)
|
|
34
|
-
* @returns The trimmed text content of the cell, or an empty string
|
|
34
|
+
* @returns The trimmed, escape-resolved text content of the cell, or an empty string
|
|
35
35
|
*
|
|
36
36
|
* @internal
|
|
37
37
|
*/
|
|
38
38
|
function getCellText(cell) {
|
|
39
39
|
const content = cell.children.find((c) => c.type === "tableContent");
|
|
40
|
-
return content ? content.text.trim() : "";
|
|
40
|
+
return content ? unescapeMarkdown(content.text.trim()) : "";
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* Extract all cell texts from a `tableRow` token.
|
|
@@ -14,8 +14,38 @@ function getHeadingLevel(heading) {
|
|
|
14
14
|
return sequence ? sequence.text.length : 0;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
+
* CommonMark backslash escapes: a backslash is literal unless it precedes an
|
|
18
|
+
* ASCII punctuation character, in which case the pair denotes that character.
|
|
19
|
+
*/
|
|
20
|
+
const BACKSLASH_ESCAPE_RE = /\\([!-/:-@[-`{-~])/g;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve CommonMark backslash escapes in raw micromark source text.
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* Every rule in this directory has a sibling remark implementation that reads
|
|
26
|
+
* the parsed mdast tree, where the parser has already resolved escapes, via
|
|
27
|
+
* `mdast-util-to-string`. Micromark tokens instead carry RAW source. Comparing
|
|
28
|
+
* raw text against a value the parsed form would produce makes the two
|
|
29
|
+
* implementations of one documented rule disagree about the same file
|
|
30
|
+
* (issue #367).
|
|
31
|
+
*
|
|
32
|
+
* Applied at the shared extractors below so both implementations judge the
|
|
33
|
+
* value a reader actually sees, rather than per-rule where the next extractor
|
|
34
|
+
* added would silently reintroduce the split.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
function unescapeMarkdown(raw) {
|
|
39
|
+
return raw.replace(BACKSLASH_ESCAPE_RE, "$1");
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
17
42
|
* Get the plain text content of an `atxHeading` token.
|
|
18
43
|
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* Escape-resolved, so a heading compares equal to the same heading as the
|
|
46
|
+
* remark rules see it. `getHeadingLevel` needs no equivalent — it counts
|
|
47
|
+
* sequence characters rather than reading text.
|
|
48
|
+
*
|
|
19
49
|
* @param heading - The `atxHeading` micromark token
|
|
20
50
|
* @returns The heading text, or empty string if no text token found
|
|
21
51
|
*
|
|
@@ -23,8 +53,8 @@ function getHeadingLevel(heading) {
|
|
|
23
53
|
*/
|
|
24
54
|
function getHeadingText(heading) {
|
|
25
55
|
const textToken = heading.children.find((c) => c.type === "atxHeadingText");
|
|
26
|
-
return textToken ? textToken.text : "";
|
|
56
|
+
return textToken ? unescapeMarkdown(textToken.text) : "";
|
|
27
57
|
}
|
|
28
58
|
|
|
29
59
|
//#endregion
|
|
30
|
-
export { RULE_DOCS, getHeadingLevel, getHeadingText };
|
|
60
|
+
export { RULE_DOCS, getHeadingLevel, getHeadingText, unescapeMarkdown };
|
|
@@ -11,10 +11,23 @@ const DEP_TYPE_MAP = [
|
|
|
11
11
|
["optionalDependencies", "optionalDependency"]
|
|
12
12
|
];
|
|
13
13
|
/**
|
|
14
|
-
* Resolve a specifier against the snapshot it belongs to,
|
|
15
|
-
*
|
|
14
|
+
* Resolve a specifier against the snapshot it belongs to, scoped to the
|
|
15
|
+
* importer that declared it, falling back to the raw specifier string when
|
|
16
|
+
* the snapshot cannot resolve it.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* Uses `resolveIn` rather than the workspace-wide `resolve`. Both consult the
|
|
20
|
+
* ref's own lockfile importer entries when a `catalog:` specifier is not in the
|
|
21
|
+
* catalog set — which is what makes hook-injected catalogs (`catalog:effect:peers`,
|
|
22
|
+
* injected by a config-dependency pnpmfile and recorded in neither
|
|
23
|
+
* `pnpm-workspace.yaml` nor the lockfile's `catalogs:` block) resolvable at all.
|
|
24
|
+
* The workspace-wide form abstains whenever two importers disagree on a
|
|
25
|
+
* dependency's version, which in a multi-package repo reproduces the original
|
|
26
|
+
* defect: both sides fall back to the identical raw specifier, compare equal,
|
|
27
|
+
* and emit no row. Scoping to the declaring importer answers precisely where
|
|
28
|
+
* the workspace-wide form cannot.
|
|
16
29
|
*/
|
|
17
|
-
const resolveOrRaw = (snapshot, dep, spec) => Option.getOrElse(snapshot.
|
|
30
|
+
const resolveOrRaw = (snapshot, importerPath, dep, spec) => Option.getOrElse(snapshot.resolveIn(importerPath, dep, spec), () => spec);
|
|
18
31
|
/**
|
|
19
32
|
* Drop no-net-change field moves: the same dependency removed from one field
|
|
20
33
|
* and added to another with an equal resolved version (e.g. a dep promoted
|
|
@@ -59,13 +72,15 @@ function computeWorkspaceDependencyDiffs(before, after) {
|
|
|
59
72
|
for (const afterPkg of after.packages) {
|
|
60
73
|
const beforePkg = Option.getOrNull(before.package(afterPkg.name));
|
|
61
74
|
const rows = [];
|
|
75
|
+
const afterImporter = afterPkg.relativePath;
|
|
76
|
+
const beforeImporter = beforePkg?.relativePath ?? afterImporter;
|
|
62
77
|
for (const [field, type] of DEP_TYPE_MAP) {
|
|
63
78
|
const beforeRecord = beforePkg?.[field] ?? {};
|
|
64
79
|
const afterRecord = afterPkg[field];
|
|
65
80
|
const seen = /* @__PURE__ */ new Set();
|
|
66
81
|
for (const [name, beforeSpec] of Object.entries(beforeRecord)) {
|
|
67
82
|
seen.add(name);
|
|
68
|
-
const from = resolveOrRaw(before, name, beforeSpec);
|
|
83
|
+
const from = resolveOrRaw(before, beforeImporter, name, beforeSpec);
|
|
69
84
|
const afterSpec = afterRecord[name];
|
|
70
85
|
if (afterSpec === void 0) {
|
|
71
86
|
rows.push({
|
|
@@ -77,7 +92,7 @@ function computeWorkspaceDependencyDiffs(before, after) {
|
|
|
77
92
|
});
|
|
78
93
|
continue;
|
|
79
94
|
}
|
|
80
|
-
const to = resolveOrRaw(after, name, afterSpec);
|
|
95
|
+
const to = resolveOrRaw(after, afterImporter, name, afterSpec);
|
|
81
96
|
if (from !== to) rows.push({
|
|
82
97
|
dependency: name,
|
|
83
98
|
type,
|
|
@@ -93,7 +108,7 @@ function computeWorkspaceDependencyDiffs(before, after) {
|
|
|
93
108
|
type,
|
|
94
109
|
action: "added",
|
|
95
110
|
from: EM_DASH,
|
|
96
|
-
to: resolveOrRaw(after, name, afterSpec)
|
|
111
|
+
to: resolveOrRaw(after, afterImporter, name, afterSpec)
|
|
97
112
|
});
|
|
98
113
|
}
|
|
99
114
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
+
import { literalText, stringifyMarkdown } from "./remark-pipeline.js";
|
|
1
2
|
import { DependencyTableRowSchema } from "../schemas/dependency-table.js";
|
|
2
3
|
import { Schema } from "effect";
|
|
3
|
-
import remarkGfm from "remark-gfm";
|
|
4
|
-
import remarkStringify from "remark-stringify";
|
|
5
|
-
import { unified } from "unified";
|
|
6
4
|
import { toString } from "mdast-util-to-string";
|
|
7
5
|
|
|
8
6
|
//#region src/changesets/utils/dependency-table.ts
|
|
@@ -102,6 +100,12 @@ function parseDependencyTable(table) {
|
|
|
102
100
|
/**
|
|
103
101
|
* Create a table cell with a text node.
|
|
104
102
|
*
|
|
103
|
+
* @remarks
|
|
104
|
+
* The cell text is marked literal, so stringification writes it verbatim
|
|
105
|
+
* instead of escaping characters that could open a markdown construct. See
|
|
106
|
+
* {@link literalText} — without it `~0.2.1` is written as `\~0.2.1` and
|
|
107
|
+
* `some_pkg` as `some\_pkg`.
|
|
108
|
+
*
|
|
105
109
|
* @param text - The cell text content
|
|
106
110
|
* @returns An MDAST `TableCell` node
|
|
107
111
|
*
|
|
@@ -110,10 +114,7 @@ function parseDependencyTable(table) {
|
|
|
110
114
|
function makeCell(text) {
|
|
111
115
|
return {
|
|
112
116
|
type: "tableCell",
|
|
113
|
-
children: [
|
|
114
|
-
type: "text",
|
|
115
|
-
value: text
|
|
116
|
-
}]
|
|
117
|
+
children: [literalText(text)]
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
120
|
/**
|
|
@@ -188,11 +189,10 @@ function serializeDependencyTable(rows) {
|
|
|
188
189
|
* @internal
|
|
189
190
|
*/
|
|
190
191
|
function serializeDependencyTableToMarkdown(rows) {
|
|
191
|
-
|
|
192
|
+
return stringifyMarkdown({
|
|
192
193
|
type: "root",
|
|
193
194
|
children: [serializeDependencyTable(rows)]
|
|
194
|
-
};
|
|
195
|
-
return unified().use(remarkGfm).use(remarkStringify).stringify(tree).trim();
|
|
195
|
+
}).trim();
|
|
196
196
|
}
|
|
197
197
|
/**
|
|
198
198
|
* Sort priority for dependency actions: removed first, then updated, then added.
|
|
@@ -5,6 +5,80 @@ import { unified } from "unified";
|
|
|
5
5
|
|
|
6
6
|
//#region src/changesets/utils/remark-pipeline.ts
|
|
7
7
|
/**
|
|
8
|
+
* Marker set on the `data` of a text node whose value must be written to
|
|
9
|
+
* markdown verbatim rather than escaped.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* `remark-stringify` escapes any character that could open a markdown
|
|
13
|
+
* construct. Inside a dependency table that is always wrong: with
|
|
14
|
+
* `remark-gfm` enabled `~` is the strikethrough delimiter, so the specifier
|
|
15
|
+
* `~0.2.1` is written as `\~0.2.1`, and `_` in a package name becomes `\_`.
|
|
16
|
+
* The corruption compounds on every re-serialization, and dependency-table
|
|
17
|
+
* cells round-trip through consolidation and PR-body reconstruction.
|
|
18
|
+
*
|
|
19
|
+
* The marker is deliberately narrow. It is set only by the dependency-table
|
|
20
|
+
* cell builder, whose cell vocabulary is closed — package name, dependency
|
|
21
|
+
* type, action, version specifier, and the em-dash sentinel — and none of
|
|
22
|
+
* those can legitimately carry markdown. Prose elsewhere in a changeset keeps
|
|
23
|
+
* the default escaping.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
const LITERAL_TEXT_MARKER = "silkLiteralText";
|
|
28
|
+
/**
|
|
29
|
+
* Mark a text node so {@link createRemarkProcessor} writes it verbatim.
|
|
30
|
+
*
|
|
31
|
+
* @param value - The literal cell text
|
|
32
|
+
* @returns An MDAST `Text` node carrying the literal marker
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
function literalText(value) {
|
|
37
|
+
return {
|
|
38
|
+
type: "text",
|
|
39
|
+
value,
|
|
40
|
+
data: { [LITERAL_TEXT_MARKER]: true }
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether a text node was marked by {@link literalText}.
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
function isLiteralText(node) {
|
|
49
|
+
return node.data?.[LITERAL_TEXT_MARKER] === true;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Escape the two characters that would otherwise corrupt the table grid.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* A `|` would close the cell early and a `\` would be read as an escape
|
|
56
|
+
* introducer, so both are escaped to keep the written cell parseable back to
|
|
57
|
+
* its exact input. Every other character is emitted as-is. Because parsing
|
|
58
|
+
* consumes the backslash, a value that already carries a legacy `\~` from the
|
|
59
|
+
* old escaping path re-parses to its clean form rather than accumulating
|
|
60
|
+
* another layer.
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
function escapeCellLiteral(value) {
|
|
65
|
+
return value.replace(/[\\|]/g, "\\$&");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `mdast-util-to-markdown` handler overrides used by the shared processor.
|
|
69
|
+
*
|
|
70
|
+
* @remarks
|
|
71
|
+
* Replaces the default `text` handler — which is `state.safe(node.value, info)`
|
|
72
|
+
* — with one that bypasses escaping for nodes marked by {@link literalText}
|
|
73
|
+
* and delegates to the default behavior for everything else.
|
|
74
|
+
*
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
const literalTextHandlers = { text: (node, _parent, state, info) => {
|
|
78
|
+
const text = node;
|
|
79
|
+
return isLiteralText(text) ? escapeCellLiteral(text.value) : state.safe(text.value, info);
|
|
80
|
+
} };
|
|
81
|
+
/**
|
|
8
82
|
* Create a unified processor configured with remark-parse, remark-gfm,
|
|
9
83
|
* and remark-stringify.
|
|
10
84
|
*
|
|
@@ -12,7 +86,8 @@ import { unified } from "unified";
|
|
|
12
86
|
* Each call creates a fresh processor instance. The plugin chain is:
|
|
13
87
|
* 1. `remark-parse` — markdown to MDAST
|
|
14
88
|
* 2. `remark-gfm` — GitHub Flavored Markdown extensions (tables, etc.)
|
|
15
|
-
* 3. `remark-stringify` — MDAST back to markdown
|
|
89
|
+
* 3. `remark-stringify` — MDAST back to markdown, with the
|
|
90
|
+
* {@link literalText} handler override
|
|
16
91
|
*
|
|
17
92
|
* @privateRemarks
|
|
18
93
|
* Return type is intentionally inferred because the unified `Processor`
|
|
@@ -23,7 +98,7 @@ import { unified } from "unified";
|
|
|
23
98
|
* @internal
|
|
24
99
|
*/
|
|
25
100
|
function createRemarkProcessor() {
|
|
26
|
-
return unified().use(remarkParse).use(remarkGfm).use(remarkStringify);
|
|
101
|
+
return unified().use(remarkParse).use(remarkGfm).use(remarkStringify, { handlers: literalTextHandlers });
|
|
27
102
|
}
|
|
28
103
|
/**
|
|
29
104
|
* Parse a markdown string into an MDAST AST synchronously.
|
|
@@ -76,4 +151,4 @@ function stringifyMarkdown(tree) {
|
|
|
76
151
|
}
|
|
77
152
|
|
|
78
153
|
//#endregion
|
|
79
|
-
export { createRemarkProcessor, parseMarkdown, stringifyMarkdown };
|
|
154
|
+
export { LITERAL_TEXT_MARKER, createRemarkProcessor, literalText, parseMarkdown, stringifyMarkdown };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/silk-effects",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared Effect library for Silk Suite conventions",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/systems/tree/main/packages/silk-effects",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@effected/jsonc": "^0.5.1",
|
|
39
39
|
"@effected/package-json": "^0.5.1",
|
|
40
40
|
"@effected/walker": "^0.3.2",
|
|
41
|
-
"@effected/workspaces": "^0.
|
|
41
|
+
"@effected/workspaces": "^0.7.0",
|
|
42
42
|
"@effected/yaml": "^0.5.1",
|
|
43
43
|
"@manypkg/get-packages": "^3.1.0",
|
|
44
44
|
"mdast-util-heading-range": "^4.0.0",
|