@savvy-web/changesets 0.4.2 → 0.5.0

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.
@@ -28,6 +28,7 @@ __webpack_require__.d(__webpack_exports__, {
28
28
  HeadingHierarchyRule: ()=>HeadingHierarchyRule,
29
29
  UncategorizedContentRule: ()=>UncategorizedContentRule,
30
30
  default: ()=>markdownlint,
31
+ DependencyTableFormatRule: ()=>DependencyTableFormatRule,
31
32
  RequiredSectionsRule: ()=>RequiredSectionsRule
32
33
  });
33
34
  const DOCS_BASE = "https://github.com/savvy-web/changesets/blob/main/docs/rules";
@@ -35,7 +36,8 @@ const RULE_DOCS = {
35
36
  CSH001: `${DOCS_BASE}/CSH001.md`,
36
37
  CSH002: `${DOCS_BASE}/CSH002.md`,
37
38
  CSH003: `${DOCS_BASE}/CSH003.md`,
38
- CSH004: `${DOCS_BASE}/CSH004.md`
39
+ CSH004: `${DOCS_BASE}/CSH004.md`,
40
+ CSH005: `${DOCS_BASE}/CSH005.md`
39
41
  };
40
42
  function getHeadingLevel(heading) {
41
43
  const sequence = heading.children.find((c)=>"atxHeadingSequence" === c.type);
@@ -104,6 +106,155 @@ const ContentStructureRule = {
104
106
  }
105
107
  }
106
108
  };
109
+ const EM_DASH = "\u2014";
110
+ const VALID_TYPES = new Set([
111
+ "dependency",
112
+ "devDependency",
113
+ "peerDependency",
114
+ "optionalDependency",
115
+ "workspace",
116
+ "config"
117
+ ]);
118
+ const VALID_ACTIONS = new Set([
119
+ "added",
120
+ "updated",
121
+ "removed"
122
+ ]);
123
+ const EXPECTED_HEADERS = [
124
+ "dependency",
125
+ "type",
126
+ "action",
127
+ "from",
128
+ "to"
129
+ ];
130
+ const VERSION_RE = /^(\u2014|[~^]?\d+\.\d+\.\d+(?:[-+.][\w.+-]*)?)$/;
131
+ function getCellText(cell) {
132
+ const content = cell.children.find((c)=>"tableContent" === c.type);
133
+ return content ? content.text.trim() : "";
134
+ }
135
+ function getRowCells(row) {
136
+ return row.children.filter((c)=>"tableHeader" === c.type || "tableData" === c.type).map(getCellText);
137
+ }
138
+ const DependencyTableFormatRule = {
139
+ names: [
140
+ "changeset-dependency-table-format",
141
+ "CSH005"
142
+ ],
143
+ description: "Dependencies section must contain a valid dependency table",
144
+ tags: [
145
+ "changeset"
146
+ ],
147
+ parser: "micromark",
148
+ function: function(params, onError) {
149
+ const tokens = params.parsers.micromark.tokens;
150
+ for(let i = 0; i < tokens.length; i++){
151
+ const token = tokens[i];
152
+ if ("atxHeading" !== token.type) continue;
153
+ if (2 !== getHeadingLevel(token)) continue;
154
+ if ("dependencies" !== getHeadingText(token).toLowerCase()) continue;
155
+ const headingLine = token.startLine;
156
+ let tableToken = null;
157
+ for(let j = i + 1; j < tokens.length; j++){
158
+ const next = tokens[j];
159
+ if ("lineEnding" !== next.type && "lineEndingBlank" !== next.type) {
160
+ if ("atxHeading" === next.type) break;
161
+ if ("table" === next.type) tableToken = next;
162
+ break;
163
+ }
164
+ }
165
+ if (null === tableToken) {
166
+ onError({
167
+ lineNumber: headingLine,
168
+ detail: `Dependencies section must contain a table, not a list or paragraph. See: ${RULE_DOCS.CSH005}`
169
+ });
170
+ continue;
171
+ }
172
+ const tableHead = tableToken.children.find((c)=>"tableHead" === c.type);
173
+ if (!tableHead) {
174
+ onError({
175
+ lineNumber: tableToken.startLine,
176
+ detail: `Dependencies table is missing a header row. See: ${RULE_DOCS.CSH005}`
177
+ });
178
+ continue;
179
+ }
180
+ const headerRow = tableHead.children.find((c)=>"tableRow" === c.type);
181
+ if (!headerRow) {
182
+ onError({
183
+ lineNumber: tableToken.startLine,
184
+ detail: `Dependencies table is missing a header row. See: ${RULE_DOCS.CSH005}`
185
+ });
186
+ continue;
187
+ }
188
+ const headers = getRowCells(headerRow).map((h)=>h.toLowerCase());
189
+ if (headers.length !== EXPECTED_HEADERS.length || !headers.every((h, idx)=>h === EXPECTED_HEADERS[idx])) {
190
+ onError({
191
+ lineNumber: headerRow.startLine,
192
+ detail: `Dependencies table must have columns: Dependency, Type, Action, From, To. Got: ${headers.join(", ")}. See: ${RULE_DOCS.CSH005}`
193
+ });
194
+ continue;
195
+ }
196
+ const tableBody = tableToken.children.find((c)=>"tableBody" === c.type);
197
+ if (!tableBody) {
198
+ onError({
199
+ lineNumber: tableToken.startLine,
200
+ detail: `Dependencies table must have at least one data row. See: ${RULE_DOCS.CSH005}`
201
+ });
202
+ continue;
203
+ }
204
+ const dataRows = tableBody.children.filter((c)=>"tableRow" === c.type);
205
+ if (0 === dataRows.length) {
206
+ onError({
207
+ lineNumber: tableToken.startLine,
208
+ detail: `Dependencies table must have at least one data row. See: ${RULE_DOCS.CSH005}`
209
+ });
210
+ continue;
211
+ }
212
+ for (const row of dataRows){
213
+ const cells = getRowCells(row);
214
+ if (cells.length < 5) {
215
+ onError({
216
+ lineNumber: row.startLine,
217
+ detail: `Dependencies table row has too few columns (expected 5, got ${cells.length}). See: ${RULE_DOCS.CSH005}`
218
+ });
219
+ continue;
220
+ }
221
+ const [dependency, type, action, from, to] = cells;
222
+ if (!dependency) onError({
223
+ lineNumber: row.startLine,
224
+ detail: `Dependencies table row has an empty 'Dependency' cell. See: ${RULE_DOCS.CSH005}`
225
+ });
226
+ if (!VALID_TYPES.has(type)) onError({
227
+ lineNumber: row.startLine,
228
+ detail: `Invalid dependency type '${type}'. Valid types are: ${[
229
+ ...VALID_TYPES
230
+ ].join(", ")}. See: ${RULE_DOCS.CSH005}`
231
+ });
232
+ if (!VALID_ACTIONS.has(action)) onError({
233
+ lineNumber: row.startLine,
234
+ detail: `Invalid dependency action '${action}'. Valid actions are: ${[
235
+ ...VALID_ACTIONS
236
+ ].join(", ")}. See: ${RULE_DOCS.CSH005}`
237
+ });
238
+ if (from && !VERSION_RE.test(from)) onError({
239
+ lineNumber: row.startLine,
240
+ detail: `Invalid 'from' value '${from}'. Must be a semver string or em dash (\u2014). See: ${RULE_DOCS.CSH005}`
241
+ });
242
+ if (to && !VERSION_RE.test(to)) onError({
243
+ lineNumber: row.startLine,
244
+ detail: `Invalid 'to' value '${to}'. Must be a semver string or em dash (\u2014). See: ${RULE_DOCS.CSH005}`
245
+ });
246
+ if ("added" === action && from !== EM_DASH) onError({
247
+ lineNumber: row.startLine,
248
+ detail: `'from' must be '\u2014' when action is 'added' (got '${from}'). See: ${RULE_DOCS.CSH005}`
249
+ });
250
+ if ("removed" === action && to !== EM_DASH) onError({
251
+ lineNumber: row.startLine,
252
+ detail: `'to' must be '\u2014' when action is 'removed' (got '${to}'). See: ${RULE_DOCS.CSH005}`
253
+ });
254
+ }
255
+ }
256
+ }
257
+ };
107
258
  const HeadingHierarchyRule = {
108
259
  names: [
109
260
  "changeset-heading-hierarchy",
@@ -311,16 +462,19 @@ const SilkChangesetsRules = [
311
462
  HeadingHierarchyRule,
312
463
  RequiredSectionsRule,
313
464
  ContentStructureRule,
314
- UncategorizedContentRule
465
+ UncategorizedContentRule,
466
+ DependencyTableFormatRule
315
467
  ];
316
468
  const markdownlint = SilkChangesetsRules;
317
469
  exports.ContentStructureRule = __webpack_exports__.ContentStructureRule;
470
+ exports.DependencyTableFormatRule = __webpack_exports__.DependencyTableFormatRule;
318
471
  exports.HeadingHierarchyRule = __webpack_exports__.HeadingHierarchyRule;
319
472
  exports.RequiredSectionsRule = __webpack_exports__.RequiredSectionsRule;
320
473
  exports.UncategorizedContentRule = __webpack_exports__.UncategorizedContentRule;
321
474
  exports["default"] = __webpack_exports__["default"];
322
475
  for(var __rspack_i in __webpack_exports__)if (-1 === [
323
476
  "ContentStructureRule",
477
+ "DependencyTableFormatRule",
324
478
  "HeadingHierarchyRule",
325
479
  "RequiredSectionsRule",
326
480
  "UncategorizedContentRule",
@@ -1,14 +1,24 @@
1
1
  /**
2
+ * \@savvy-web/changesets/markdownlint
3
+ *
2
4
  * markdownlint custom rules for validating changeset file structure.
3
5
  *
4
6
  * Provides the same validation as the remark-lint rules but using
5
7
  * markdownlint's micromark token API for integration with
6
8
  * markdownlint-cli2 and the VS Code markdownlint extension.
7
9
  *
8
- * - `changeset-heading-hierarchy` (CSH001): Enforce h2 start, no h1, no depth skips
9
- * - `changeset-required-sections` (CSH002): Validate section headings match known categories
10
- * - `changeset-content-structure` (CSH003): Content quality validation
11
- * - `changeset-uncategorized-content` (CSH004): Reject content before first h2 heading
10
+ * Rules included:
11
+ * - {@link HeadingHierarchyRule | changeset-heading-hierarchy} (CSH001): Enforce h2 start, no h1, no depth skips
12
+ * - {@link RequiredSectionsRule | changeset-required-sections} (CSH002): Validate section headings match known categories
13
+ * - {@link ContentStructureRule | changeset-content-structure} (CSH003): Content quality validation
14
+ * - {@link UncategorizedContentRule | changeset-uncategorized-content} (CSH004): Reject content before first h2 heading
15
+ * - {@link DependencyTableFormatRule | changeset-dependency-table-format} (CSH005): Validate dependency table structure
16
+ *
17
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH001.md | CSH001 docs}
18
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH002.md | CSH002 docs}
19
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH003.md | CSH003 docs}
20
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH004.md | CSH004 docs}
21
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH005.md | CSH005 docs}
12
22
  *
13
23
  * @packageDocumentation
14
24
  */
@@ -16,31 +26,103 @@
16
26
  import type { Rule } from 'markdownlint';
17
27
 
18
28
  /**
19
- * markdownlint rule: changeset-content-structure (CSH003)
29
+ * markdownlint rule: `changeset-content-structure` (CSH003).
30
+ *
31
+ * Validates content quality inside changeset markdown files by inspecting
32
+ * micromark tokens for three categories of structural problems:
33
+ *
34
+ * 1. **Empty sections** -- an `atxHeading` (h2) followed immediately by another
35
+ * h2 or the end of the token stream with no intervening content tokens.
36
+ * 2. **Code blocks without a language identifier** -- a `codeFenced` token whose
37
+ * opening fence child lacks a `codeFencedFenceInfo` token.
38
+ * 3. **Empty list items** -- a `listItemPrefix` token with no subsequent
39
+ * `content` token before the next prefix or end of list.
20
40
  *
21
- * Validates content quality in changeset files:
22
- * - Sections must not be empty (h2 followed immediately by another h2 or EOF)
23
- * - Code blocks must have a language identifier
24
- * - List items should have meaningful content (not empty)
41
+ * @remarks
42
+ * This rule mirrors the remark-lint rule `remarkLintContentStructure` but uses
43
+ * markdownlint's micromark token API so it can run inside markdownlint-cli2 and
44
+ * the VS Code markdownlint extension.
45
+ *
46
+ * @example
47
+ * ```json
48
+ * {
49
+ * "changeset-content-structure": true
50
+ * }
51
+ * ```
52
+ *
53
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH003.md | CSH003 rule documentation}
54
+ * @see `src/remark/rules/content-structure.ts` for the corresponding remark-lint rule
55
+ *
56
+ * @public
25
57
  */
26
58
  export declare const ContentStructureRule: Rule;
27
59
 
28
60
  /**
29
- * markdownlint rule: changeset-heading-hierarchy (CSH001)
61
+ * The markdownlint `Rule` object for CSH005 (`changeset-dependency-table-format`).
62
+ *
63
+ * @public
64
+ */
65
+ export declare const DependencyTableFormatRule: Rule;
66
+
67
+ /**
68
+ * markdownlint rule: `changeset-heading-hierarchy` (CSH001).
69
+ *
70
+ * Validates heading structure in changeset markdown files by inspecting
71
+ * `atxHeading` micromark tokens for three constraints:
72
+ *
73
+ * 1. **No h1 headings** -- h1 is reserved for the version title generated by
74
+ * the changelog formatter.
75
+ * 2. **Start at h2** -- the first heading in a changeset must be h2.
76
+ * 3. **No depth skips** -- heading levels must increase sequentially
77
+ * (h2 then h3, not h2 then h4).
78
+ *
79
+ * @remarks
80
+ * This rule mirrors the remark-lint rule `remarkLintHeadingHierarchy` but uses
81
+ * markdownlint's micromark token API so it can run inside markdownlint-cli2 and
82
+ * the VS Code markdownlint extension.
83
+ *
84
+ * @example
85
+ * ```json
86
+ * {
87
+ * "changeset-heading-hierarchy": true
88
+ * }
89
+ * ```
90
+ *
91
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH001.md | CSH001 rule documentation}
92
+ * @see `src/remark/rules/heading-hierarchy.ts` for the corresponding remark-lint rule
30
93
  *
31
- * Validates heading structure in changeset files:
32
- * - No h1 headings allowed
33
- * - Headings must start at h2
34
- * - No depth skips (e.g., h2 ... h4 is invalid)
94
+ * @public
35
95
  */
36
96
  export declare const HeadingHierarchyRule: Rule;
37
97
 
38
98
  /**
39
- * markdownlint rule: changeset-required-sections (CSH002)
99
+ * markdownlint rule: `changeset-required-sections` (CSH002).
100
+ *
101
+ * Validates that every h2 (`atxHeading` with depth 2) in a changeset markdown
102
+ * file matches a known category heading from the category system. When an
103
+ * unrecognized heading is found, the error detail lists all valid headings.
104
+ *
105
+ * @remarks
106
+ * Heading comparison is case-insensitive. The set of valid headings is provided
107
+ * by `allHeadings()` and `isValidHeading()` from the category system
108
+ * (`src/categories/index.ts`). This rule inspects `atxHeading` micromark
109
+ * tokens and extracts their text via the {@link getHeadingText} utility.
110
+ *
111
+ * This rule mirrors the remark-lint rule `remarkLintRequiredSections` but uses
112
+ * markdownlint's micromark token API so it can run inside markdownlint-cli2 and
113
+ * the VS Code markdownlint extension.
114
+ *
115
+ * @example
116
+ * ```json
117
+ * {
118
+ * "changeset-required-sections": true
119
+ * }
120
+ * ```
121
+ *
122
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH002.md | CSH002 rule documentation}
123
+ * @see `src/remark/rules/required-sections.ts` for the corresponding remark-lint rule
40
124
  *
41
- * Validates that all h2 headings in changeset files match a known
42
- * category heading from the category system. Reports unrecognized
43
- * headings with the list of valid options.
125
+ * @public
44
126
  */
45
127
  export declare const RequiredSectionsRule: Rule;
46
128
 
@@ -60,10 +142,34 @@ declare const SilkChangesetsRules: Rule[];
60
142
  export default SilkChangesetsRules;
61
143
 
62
144
  /**
63
- * markdownlint rule: changeset-uncategorized-content (CSH004)
145
+ * markdownlint rule: `changeset-uncategorized-content` (CSH004).
146
+ *
147
+ * Detects content that appears before the first h2 heading in a changeset
148
+ * markdown file. All substantive content must be placed under a categorized
149
+ * section (`## heading`).
64
150
  *
65
- * Detects content that appears before the first h2 heading in a changeset file.
66
- * All content must be placed under a categorized section (## heading).
151
+ * @remarks
152
+ * The rule iterates over the top-level micromark token stream and stops at the
153
+ * first `atxHeading` with depth 2. Any token encountered before that heading
154
+ * that is not a `lineEnding`, `lineEndingBlank`, or `htmlFlow` (HTML comments)
155
+ * triggers an error. This ensures that changeset content is always grouped
156
+ * under a recognized category heading.
157
+ *
158
+ * This rule mirrors the remark-lint rule `remarkLintUncategorizedContent` but
159
+ * uses markdownlint's micromark token API so it can run inside
160
+ * markdownlint-cli2 and the VS Code markdownlint extension.
161
+ *
162
+ * @example
163
+ * ```json
164
+ * {
165
+ * "changeset-uncategorized-content": true
166
+ * }
167
+ * ```
168
+ *
169
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH004.md | CSH004 rule documentation}
170
+ * @see `src/remark/rules/uncategorized-content.ts` for the corresponding remark-lint rule
171
+ *
172
+ * @public
67
173
  */
68
174
  export declare const UncategorizedContentRule: Rule;
69
175
 
package/cjs/remark.cjs CHANGED
@@ -24,21 +24,161 @@ var __webpack_require__ = {};
24
24
  var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
+ RequiredSectionsRule: ()=>RequiredSectionsRule,
28
+ SilkChangesetPreset: ()=>SilkChangesetPreset,
27
29
  UncategorizedContentRule: ()=>UncategorizedContentRule,
28
30
  SilkChangesetTransformPreset: ()=>SilkChangesetTransformPreset,
29
- NormalizeFormatPlugin: ()=>NormalizeFormatPlugin,
31
+ DependencyTableFormatRule: ()=>DependencyTableFormatRule,
30
32
  IssueLinkRefsPlugin: ()=>IssueLinkRefsPlugin,
31
- MergeSectionsPlugin: ()=>MergeSectionsPlugin,
33
+ DeduplicateItemsPlugin: ()=>DeduplicateItemsPlugin,
32
34
  ContentStructureRule: ()=>ContentStructureRule,
33
35
  HeadingHierarchyRule: ()=>HeadingHierarchyRule,
34
- DeduplicateItemsPlugin: ()=>DeduplicateItemsPlugin,
35
- ReorderSectionsPlugin: ()=>ReorderSectionsPlugin,
36
+ AggregateDependencyTablesPlugin: ()=>AggregateDependencyTablesPlugin,
37
+ MergeSectionsPlugin: ()=>MergeSectionsPlugin,
36
38
  ContributorFootnotesPlugin: ()=>ContributorFootnotesPlugin,
37
- SilkChangesetPreset: ()=>SilkChangesetPreset,
38
- RequiredSectionsRule: ()=>RequiredSectionsRule
39
+ NormalizeFormatPlugin: ()=>NormalizeFormatPlugin,
40
+ ReorderSectionsPlugin: ()=>ReorderSectionsPlugin
39
41
  });
40
- const external_unist_util_visit_namespaceObject = require("unist-util-visit");
42
+ const external_effect_namespaceObject = require("effect");
41
43
  const external_mdast_util_to_string_namespaceObject = require("mdast-util-to-string");
44
+ require("remark-gfm");
45
+ require("remark-stringify");
46
+ require("unified");
47
+ const NonEmptyString = external_effect_namespaceObject.Schema.String.pipe(external_effect_namespaceObject.Schema.minLength(1));
48
+ external_effect_namespaceObject.Schema.Number.pipe(external_effect_namespaceObject.Schema.int(), external_effect_namespaceObject.Schema.positive());
49
+ const DependencyActionSchema = external_effect_namespaceObject.Schema.Literal("added", "updated", "removed");
50
+ const DependencyTableTypeSchema = external_effect_namespaceObject.Schema.Literal("dependency", "devDependency", "peerDependency", "optionalDependency", "workspace", "config");
51
+ const VersionOrEmptySchema = external_effect_namespaceObject.Schema.String.pipe(external_effect_namespaceObject.Schema.pattern(/^(\u2014|[~^]?\d+\.\d+\.\d+(?:[-+.][\w.+-]*)?)$/));
52
+ const DependencyTableRowSchema = external_effect_namespaceObject.Schema.Struct({
53
+ dependency: NonEmptyString,
54
+ type: DependencyTableTypeSchema,
55
+ action: DependencyActionSchema,
56
+ from: VersionOrEmptySchema,
57
+ to: VersionOrEmptySchema
58
+ });
59
+ external_effect_namespaceObject.Schema.Array(DependencyTableRowSchema).pipe(external_effect_namespaceObject.Schema.minItems(1));
60
+ const COLUMN_HEADERS = [
61
+ "Dependency",
62
+ "Type",
63
+ "Action",
64
+ "From",
65
+ "To"
66
+ ];
67
+ const COLUMN_KEYS = [
68
+ "dependency",
69
+ "type",
70
+ "action",
71
+ "from",
72
+ "to"
73
+ ];
74
+ const decode = external_effect_namespaceObject.Schema.decodeUnknownSync(DependencyTableRowSchema);
75
+ function parseDependencyTable(table) {
76
+ const rows = table.children;
77
+ if (rows.length < 2) throw new Error("Dependency table must have at least one data row");
78
+ const headerRow = rows[0];
79
+ const headers = headerRow.children.map((cell)=>(0, external_mdast_util_to_string_namespaceObject.toString)(cell).trim().toLowerCase());
80
+ const expected = COLUMN_HEADERS.map((h)=>h.toLowerCase());
81
+ if (headers.length !== expected.length || !headers.every((h, i)=>h === expected[i])) throw new Error(`Table must have columns: ${COLUMN_HEADERS.join(", ")}. Got: ${headers.join(", ")}`);
82
+ const result = [];
83
+ for(let i = 1; i < rows.length; i++){
84
+ const cells = rows[i].children;
85
+ const raw = {};
86
+ for(let c = 0; c < COLUMN_KEYS.length; c++)raw[COLUMN_KEYS[c]] = (0, external_mdast_util_to_string_namespaceObject.toString)(cells[c]).trim();
87
+ result.push(decode(raw));
88
+ }
89
+ return result;
90
+ }
91
+ function makeCell(text) {
92
+ return {
93
+ type: "tableCell",
94
+ children: [
95
+ {
96
+ type: "text",
97
+ value: text
98
+ }
99
+ ]
100
+ };
101
+ }
102
+ function makeRow(texts) {
103
+ return {
104
+ type: "tableRow",
105
+ children: texts.map(makeCell)
106
+ };
107
+ }
108
+ function serializeDependencyTable(rows) {
109
+ const headerRow = makeRow([
110
+ ...COLUMN_HEADERS
111
+ ]);
112
+ const dataRows = rows.map((row)=>makeRow(COLUMN_KEYS.map((key)=>row[key])));
113
+ return {
114
+ type: "table",
115
+ children: [
116
+ headerRow,
117
+ ...dataRows
118
+ ]
119
+ };
120
+ }
121
+ const ACTION_ORDER = {
122
+ removed: 0,
123
+ updated: 1,
124
+ added: 2
125
+ };
126
+ function collapseDependencyRows(rows) {
127
+ const groups = new Map();
128
+ for (const row of rows){
129
+ const key = `${row.dependency}\0${row.type}`;
130
+ const existing = groups.get(key);
131
+ if (!existing) {
132
+ groups.set(key, {
133
+ ...row
134
+ });
135
+ continue;
136
+ }
137
+ const merged = collapseTwo(existing, row);
138
+ if (null === merged) groups.delete(key);
139
+ else groups.set(key, merged);
140
+ }
141
+ return [
142
+ ...groups.values()
143
+ ];
144
+ }
145
+ function collapseTwo(first, second) {
146
+ const a = first.action;
147
+ const b = second.action;
148
+ if ("updated" === a && "updated" === b) return {
149
+ ...first,
150
+ to: second.to
151
+ };
152
+ if ("added" === a && "updated" === b) return {
153
+ ...first,
154
+ to: second.to
155
+ };
156
+ if ("added" === a && "removed" === b) return null;
157
+ if ("updated" === a && "removed" === b) return {
158
+ ...first,
159
+ action: "removed",
160
+ to: "\u2014"
161
+ };
162
+ if ("removed" === a && "added" === b) return {
163
+ ...first,
164
+ action: "updated",
165
+ to: second.to
166
+ };
167
+ return {
168
+ ...second
169
+ };
170
+ }
171
+ function sortDependencyRows(rows) {
172
+ return [
173
+ ...rows
174
+ ].sort((a, b)=>{
175
+ const actionDiff = (ACTION_ORDER[a.action] ?? 99) - (ACTION_ORDER[b.action] ?? 99);
176
+ if (0 !== actionDiff) return actionDiff;
177
+ const typeDiff = a.type.localeCompare(b.type);
178
+ if (0 !== typeDiff) return typeDiff;
179
+ return a.dependency.localeCompare(b.dependency);
180
+ });
181
+ }
42
182
  function getVersionBlocks(tree) {
43
183
  const blocks = [];
44
184
  for(let i = 0; i < tree.children.length; i++){
@@ -68,6 +208,49 @@ function getBlockSections(tree, block) {
68
208
  function getHeadingText(heading) {
69
209
  return (0, external_mdast_util_to_string_namespaceObject.toString)(heading);
70
210
  }
211
+ const AggregateDependencyTablesPlugin = ()=>(tree)=>{
212
+ const blocks = getVersionBlocks(tree);
213
+ for(let b = blocks.length - 1; b >= 0; b--){
214
+ const sections = getBlockSections(tree, blocks[b]);
215
+ const depSections = sections.filter((s)=>"dependencies" === getHeadingText(s.heading).toLowerCase());
216
+ if (0 === depSections.length) continue;
217
+ const allRows = [];
218
+ const legacyContent = [];
219
+ for (const section of depSections)for (const node of section.contentNodes)if ("table" === node.type) try {
220
+ const rows = parseDependencyTable(node);
221
+ allRows.push(...rows);
222
+ } catch {
223
+ legacyContent.push(node);
224
+ }
225
+ else legacyContent.push(node);
226
+ const collapsed = sortDependencyRows(collapseDependencyRows(allRows));
227
+ const indicesToRemove = [];
228
+ for (const section of depSections){
229
+ indicesToRemove.push(section.headingIndex);
230
+ for(let c = 0; c < section.contentNodes.length; c++)indicesToRemove.push(section.headingIndex + 1 + c);
231
+ }
232
+ indicesToRemove.sort((a, b)=>b - a);
233
+ for (const idx of indicesToRemove)tree.children.splice(idx, 1);
234
+ if (0 === collapsed.length && 0 === legacyContent.length) continue;
235
+ const insertAt = depSections[0].headingIndex;
236
+ const newNodes = [];
237
+ const heading = {
238
+ type: "heading",
239
+ depth: 3,
240
+ children: [
241
+ {
242
+ type: "text",
243
+ value: "Dependencies"
244
+ }
245
+ ]
246
+ };
247
+ newNodes.push(heading);
248
+ if (collapsed.length > 0) newNodes.push(serializeDependencyTable(collapsed));
249
+ newNodes.push(...legacyContent);
250
+ tree.children.splice(insertAt, 0, ...newNodes);
251
+ }
252
+ };
253
+ const external_unist_util_visit_namespaceObject = require("unist-util-visit");
71
254
  const ATTRIBUTION_PLAIN_RE = /\s*Thanks @(\w[\w-]*)!$/;
72
255
  function extractLinkedAttribution(children) {
73
256
  if (children.length < 3) return;
@@ -479,7 +662,8 @@ const RULE_DOCS = {
479
662
  CSH001: `${DOCS_BASE}/CSH001.md`,
480
663
  CSH002: `${DOCS_BASE}/CSH002.md`,
481
664
  CSH003: `${DOCS_BASE}/CSH003.md`,
482
- CSH004: `${DOCS_BASE}/CSH004.md`
665
+ CSH004: `${DOCS_BASE}/CSH004.md`,
666
+ CSH005: `${DOCS_BASE}/CSH005.md`
483
667
  };
484
668
  const ContentStructureRule = (0, external_unified_lint_rule_namespaceObject.lintRule)("remark-lint:changeset-content-structure", (tree, file)=>{
485
669
  (0, external_unist_util_visit_namespaceObject.visit)(tree, "heading", (node, index, parent)=>{
@@ -495,6 +679,32 @@ const ContentStructureRule = (0, external_unified_lint_rule_namespaceObject.lint
495
679
  if (!text) file.message(`Empty list item. Each list item must contain descriptive text (e.g., "- Fixed login timeout issue"). See: ${RULE_DOCS.CSH003}`, node);
496
680
  });
497
681
  });
682
+ const EM_DASH = "\u2014";
683
+ const DependencyTableFormatRule = (0, external_unified_lint_rule_namespaceObject.lintRule)("remark-lint:changeset-dependency-table-format", (tree, file)=>{
684
+ (0, external_unist_util_visit_namespaceObject.visit)(tree, "heading", (node, index)=>{
685
+ if (2 !== node.depth) return;
686
+ if ("dependencies" !== (0, external_mdast_util_to_string_namespaceObject.toString)(node).toLowerCase()) return;
687
+ if (void 0 === index) return;
688
+ const content = [];
689
+ for(let i = index + 1; i < tree.children.length; i++){
690
+ const child = tree.children[i];
691
+ if ("heading" === child.type) break;
692
+ content.push(child);
693
+ }
694
+ const tables = content.filter((n)=>"table" === n.type);
695
+ if (0 === tables.length) return void file.message(`Dependencies section must contain a table, not a list or paragraph. See: ${RULE_DOCS.CSH005}`, node);
696
+ const table = tables[0];
697
+ try {
698
+ const rows = parseDependencyTable(table);
699
+ for (const row of rows){
700
+ if ("added" === row.action && row.from !== EM_DASH) file.message(`'from' must be '\u2014' when action is 'added' (got '${row.from}'). See: ${RULE_DOCS.CSH005}`, table);
701
+ if ("removed" === row.action && row.to !== EM_DASH) file.message(`'to' must be '\u2014' when action is 'removed' (got '${row.to}'). See: ${RULE_DOCS.CSH005}`, table);
702
+ }
703
+ } catch (error) {
704
+ file.message(`${error instanceof Error ? error.message : String(error)}. See: ${RULE_DOCS.CSH005}`, table);
705
+ }
706
+ });
707
+ });
498
708
  const HeadingHierarchyRule = (0, external_unified_lint_rule_namespaceObject.lintRule)("remark-lint:changeset-heading-hierarchy", (tree, file)=>{
499
709
  let prevDepth = 0;
500
710
  (0, external_unist_util_visit_namespaceObject.visit)(tree, "heading", (node)=>{
@@ -527,9 +737,11 @@ const SilkChangesetPreset = [
527
737
  HeadingHierarchyRule,
528
738
  RequiredSectionsRule,
529
739
  ContentStructureRule,
530
- UncategorizedContentRule
740
+ UncategorizedContentRule,
741
+ DependencyTableFormatRule
531
742
  ];
532
743
  const SilkChangesetTransformPreset = [
744
+ AggregateDependencyTablesPlugin,
533
745
  MergeSectionsPlugin,
534
746
  ReorderSectionsPlugin,
535
747
  DeduplicateItemsPlugin,
@@ -537,9 +749,11 @@ const SilkChangesetTransformPreset = [
537
749
  IssueLinkRefsPlugin,
538
750
  NormalizeFormatPlugin
539
751
  ];
752
+ exports.AggregateDependencyTablesPlugin = __webpack_exports__.AggregateDependencyTablesPlugin;
540
753
  exports.ContentStructureRule = __webpack_exports__.ContentStructureRule;
541
754
  exports.ContributorFootnotesPlugin = __webpack_exports__.ContributorFootnotesPlugin;
542
755
  exports.DeduplicateItemsPlugin = __webpack_exports__.DeduplicateItemsPlugin;
756
+ exports.DependencyTableFormatRule = __webpack_exports__.DependencyTableFormatRule;
543
757
  exports.HeadingHierarchyRule = __webpack_exports__.HeadingHierarchyRule;
544
758
  exports.IssueLinkRefsPlugin = __webpack_exports__.IssueLinkRefsPlugin;
545
759
  exports.MergeSectionsPlugin = __webpack_exports__.MergeSectionsPlugin;
@@ -550,9 +764,11 @@ exports.SilkChangesetPreset = __webpack_exports__.SilkChangesetPreset;
550
764
  exports.SilkChangesetTransformPreset = __webpack_exports__.SilkChangesetTransformPreset;
551
765
  exports.UncategorizedContentRule = __webpack_exports__.UncategorizedContentRule;
552
766
  for(var __rspack_i in __webpack_exports__)if (-1 === [
767
+ "AggregateDependencyTablesPlugin",
553
768
  "ContentStructureRule",
554
769
  "ContributorFootnotesPlugin",
555
770
  "DeduplicateItemsPlugin",
771
+ "DependencyTableFormatRule",
556
772
  "HeadingHierarchyRule",
557
773
  "IssueLinkRefsPlugin",
558
774
  "MergeSectionsPlugin",