@savvy-web/silk-effects 0.6.0 → 1.0.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.
Files changed (155) hide show
  1. package/README.md +48 -17
  2. package/_virtual/_rolldown/runtime.js +18 -0
  3. package/changesets/api/categories.js +247 -0
  4. package/changesets/api/changelog.js +134 -0
  5. package/changesets/api/dependency-table.js +163 -0
  6. package/changesets/api/linter.js +168 -0
  7. package/changesets/api/transformer.js +140 -0
  8. package/changesets/categories/index.js +299 -0
  9. package/changesets/categories/types.js +66 -0
  10. package/changesets/changelog/formatting.js +119 -0
  11. package/changesets/changelog/getDependencyReleaseLine.js +114 -0
  12. package/changesets/changelog/getReleaseLine.js +122 -0
  13. package/changesets/changelog/index.js +99 -0
  14. package/changesets/constants.js +43 -0
  15. package/changesets/errors.js +305 -0
  16. package/changesets/index.js +146 -0
  17. package/changesets/markdownlint/index.js +29 -0
  18. package/changesets/markdownlint/rules/content-structure.js +98 -0
  19. package/changesets/markdownlint/rules/dependency-table-format.js +170 -0
  20. package/changesets/markdownlint/rules/heading-hierarchy.js +61 -0
  21. package/changesets/markdownlint/rules/required-sections.js +54 -0
  22. package/changesets/markdownlint/rules/uncategorized-content.js +54 -0
  23. package/changesets/markdownlint/rules/utils.js +30 -0
  24. package/changesets/remark/plugins/aggregate-dependency-tables.js +47 -0
  25. package/changesets/remark/plugins/contributor-footnotes.js +123 -0
  26. package/changesets/remark/plugins/deduplicate-items.js +30 -0
  27. package/changesets/remark/plugins/issue-link-refs.js +58 -0
  28. package/changesets/remark/plugins/merge-sections.js +43 -0
  29. package/changesets/remark/plugins/normalize-format.js +47 -0
  30. package/changesets/remark/plugins/reorder-sections.js +34 -0
  31. package/changesets/remark/presets.js +119 -0
  32. package/changesets/remark/rules/content-structure.js +22 -0
  33. package/changesets/remark/rules/dependency-table-format.js +40 -0
  34. package/changesets/remark/rules/heading-hierarchy.js +19 -0
  35. package/changesets/remark/rules/required-sections.js +17 -0
  36. package/changesets/remark/rules/uncategorized-content.js +31 -0
  37. package/changesets/schemas/changeset.js +146 -0
  38. package/changesets/schemas/dependency-table.js +189 -0
  39. package/changesets/schemas/git.js +69 -0
  40. package/changesets/schemas/github.js +175 -0
  41. package/changesets/schemas/options.js +182 -0
  42. package/changesets/schemas/package-scope.js +128 -0
  43. package/changesets/schemas/primitives.js +72 -0
  44. package/changesets/schemas/version-files.js +151 -0
  45. package/changesets/services/branch-analyzer.js +278 -0
  46. package/changesets/services/changelog.js +50 -0
  47. package/changesets/services/config-inspector.js +390 -0
  48. package/changesets/services/github.js +178 -0
  49. package/changesets/services/markdown.js +106 -0
  50. package/changesets/services/workspace-snapshot.js +182 -0
  51. package/changesets/utils/commit-parser.js +80 -0
  52. package/changesets/utils/dep-diff.js +77 -0
  53. package/changesets/utils/dependency-table.js +347 -0
  54. package/changesets/utils/issue-refs.js +101 -0
  55. package/changesets/utils/jsonpath.js +175 -0
  56. package/changesets/utils/logger.js +50 -0
  57. package/changesets/utils/markdown-link.js +57 -0
  58. package/changesets/utils/publishability.js +39 -0
  59. package/changesets/utils/remark-pipeline.js +79 -0
  60. package/changesets/utils/section-parser.js +94 -0
  61. package/changesets/utils/strip-frontmatter.js +46 -0
  62. package/changesets/utils/version-blocks.js +108 -0
  63. package/changesets/utils/version-files.js +336 -0
  64. package/changesets/utils/worktree-snapshot.js +142 -0
  65. package/changesets/vendor/github-info.js +55 -0
  66. package/commitlint/config/factory.js +69 -0
  67. package/commitlint/config/plugins.js +227 -0
  68. package/commitlint/config/rules.js +155 -0
  69. package/commitlint/config/schema.js +46 -0
  70. package/commitlint/detection/dco.js +53 -0
  71. package/commitlint/detection/scopes.js +45 -0
  72. package/commitlint/formatter/format.js +85 -0
  73. package/commitlint/formatter/messages.js +79 -0
  74. package/commitlint/hook/diagnostics/branch.js +36 -0
  75. package/commitlint/hook/diagnostics/cache.js +37 -0
  76. package/commitlint/hook/diagnostics/commitlint-config.js +36 -0
  77. package/commitlint/hook/diagnostics/open-issues.js +56 -0
  78. package/commitlint/hook/diagnostics/package-manager.js +51 -0
  79. package/commitlint/hook/diagnostics/signing.js +107 -0
  80. package/commitlint/hook/envelope.js +46 -0
  81. package/commitlint/hook/output.js +45 -0
  82. package/commitlint/hook/parse-bash-command.js +105 -0
  83. package/commitlint/hook/rules/closes-trailer.js +31 -0
  84. package/commitlint/hook/rules/forbidden-content.js +32 -0
  85. package/commitlint/hook/rules/plan-leakage.js +36 -0
  86. package/commitlint/hook/rules/signing-flag-conflict.js +25 -0
  87. package/commitlint/hook/rules/soft-wrap.js +37 -0
  88. package/commitlint/hook/rules/types.js +14 -0
  89. package/commitlint/hook/rules/verbosity.js +31 -0
  90. package/commitlint/hook/silence-logger.js +39 -0
  91. package/commitlint/index.js +146 -0
  92. package/commitlint/prompt/config.js +91 -0
  93. package/commitlint/prompt/emojis.js +74 -0
  94. package/commitlint/prompt/prompter.js +135 -0
  95. package/commitlint/static.js +73 -0
  96. package/errors/BiomeSyncError.js +21 -0
  97. package/errors/ChangesetConfigError.js +20 -0
  98. package/errors/ConfigNotFoundError.js +21 -0
  99. package/errors/SectionParseError.js +16 -0
  100. package/errors/SectionValidationError.js +16 -0
  101. package/errors/SectionWriteError.js +16 -0
  102. package/errors/TagFormatError.js +20 -0
  103. package/errors/ToolNotFoundError.js +11 -0
  104. package/errors/ToolResolutionError.js +11 -0
  105. package/errors/ToolVersionMismatchError.js +11 -0
  106. package/errors/VersioningDetectionError.js +20 -0
  107. package/errors/WorkspaceAnalysisError.js +21 -0
  108. package/index.d.ts +9743 -8380
  109. package/index.js +36 -6657
  110. package/lint/Handler.js +39 -0
  111. package/lint/cli/sections.js +65 -0
  112. package/lint/cli/templates/markdownlint.gen.js +183 -0
  113. package/lint/config/Preset.js +152 -0
  114. package/lint/config/createConfig.js +89 -0
  115. package/lint/handlers/Biome.js +179 -0
  116. package/lint/handlers/Markdown.js +139 -0
  117. package/lint/handlers/PackageJson.js +130 -0
  118. package/lint/handlers/PnpmWorkspace.js +141 -0
  119. package/lint/handlers/ShellScripts.js +58 -0
  120. package/lint/handlers/TypeScript.js +134 -0
  121. package/lint/handlers/Yaml.js +167 -0
  122. package/lint/index.js +52 -0
  123. package/lint/utils/Command.js +285 -0
  124. package/lint/utils/Filter.js +100 -0
  125. package/lint/utils/Workspace.js +86 -0
  126. package/package.json +52 -63
  127. package/schemas/CommentStyle.js +16 -0
  128. package/schemas/ResolvedTool.js +63 -0
  129. package/schemas/SavvySections.js +113 -0
  130. package/schemas/SectionBlock.js +70 -0
  131. package/schemas/SectionDefinition.js +121 -0
  132. package/schemas/SectionResults.js +12 -0
  133. package/schemas/TagStrategySchemas.js +18 -0
  134. package/schemas/ToolDefinition.js +39 -0
  135. package/schemas/ToolResults.js +14 -0
  136. package/schemas/VersioningSchemas.js +95 -0
  137. package/schemas/WorkspaceAnalysisSchemas.js +190 -0
  138. package/services/BiomeSchemaSync.js +133 -0
  139. package/services/ChangesetConfig.js +78 -0
  140. package/services/ChangesetConfigReader.js +106 -0
  141. package/services/ConfigDiscovery.js +71 -0
  142. package/services/ManagedSection.js +288 -0
  143. package/services/SilkPublishability.js +193 -0
  144. package/services/SilkWorkspaceAnalyzer.js +213 -0
  145. package/services/TagStrategy.js +54 -0
  146. package/services/ToolDiscovery.js +229 -0
  147. package/services/VersioningStrategy.js +67 -0
  148. package/tsdoc-metadata.json +11 -11
  149. package/turbo/digest.js +127 -0
  150. package/turbo/errors.js +48 -0
  151. package/turbo/index.js +32 -0
  152. package/turbo/schemas/DryRun.js +57 -0
  153. package/turbo/schemas/results.js +61 -0
  154. package/turbo/services/TurboInspector.js +100 -0
  155. package/utils/ToolCommand.js +40 -0
@@ -0,0 +1,170 @@
1
+ import { RULE_DOCS } from "../../constants.js";
2
+ import { getHeadingLevel, getHeadingText } from "./utils.js";
3
+
4
+ //#region src/changesets/markdownlint/rules/dependency-table-format.ts
5
+ const EM_DASH = "—";
6
+ const VALID_TYPES = new Set([
7
+ "dependency",
8
+ "devDependency",
9
+ "peerDependency",
10
+ "optionalDependency",
11
+ "workspace",
12
+ "config"
13
+ ]);
14
+ const VALID_ACTIONS = new Set([
15
+ "added",
16
+ "updated",
17
+ "removed"
18
+ ]);
19
+ const EXPECTED_HEADERS = [
20
+ "dependency",
21
+ "type",
22
+ "action",
23
+ "from",
24
+ "to"
25
+ ];
26
+ const VERSION_RE = /^(\u2014|[~^]?\d+\.\d+\.\d+(?:[-+.][\w.+-]*)?)$/;
27
+ /**
28
+ * Extract text from a `tableHeader` or `tableData` cell token.
29
+ *
30
+ * The cell contains: `tableCellDivider`, whitespace, `tableContent`, whitespace.
31
+ * The `tableContent` token has a `.text` property with the cell value.
32
+ *
33
+ * @param cell - A GFM table cell token (header or data)
34
+ * @returns The trimmed text content of the cell, or an empty string
35
+ *
36
+ * @internal
37
+ */
38
+ function getCellText(cell) {
39
+ const content = cell.children.find((c) => c.type === "tableContent");
40
+ return content ? content.text.trim() : "";
41
+ }
42
+ /**
43
+ * Extract all cell texts from a `tableRow` token.
44
+ *
45
+ * @param row - A GFM `tableRow` token
46
+ * @returns An array of trimmed cell text values
47
+ *
48
+ * @internal
49
+ */
50
+ function getRowCells(row) {
51
+ return row.children.filter((c) => c.type === "tableHeader" || c.type === "tableData").map(getCellText);
52
+ }
53
+ /**
54
+ * The markdownlint `Rule` object for CSH005 (`changeset-dependency-table-format`).
55
+ *
56
+ * @public
57
+ */
58
+ const DependencyTableFormatRule = {
59
+ names: ["changeset-dependency-table-format", "CSH005"],
60
+ description: "Dependencies section must contain a valid dependency table",
61
+ tags: ["changeset"],
62
+ parser: "micromark",
63
+ function: function CSH005(params, onError) {
64
+ const tokens = params.parsers.micromark.tokens;
65
+ for (let i = 0; i < tokens.length; i++) {
66
+ const token = tokens[i];
67
+ if (token.type !== "atxHeading") continue;
68
+ if (getHeadingLevel(token) !== 2) continue;
69
+ if (getHeadingText(token).toLowerCase() !== "dependencies") continue;
70
+ const headingLine = token.startLine;
71
+ let tableToken = null;
72
+ for (let j = i + 1; j < tokens.length; j++) {
73
+ const next = tokens[j];
74
+ if (next.type === "lineEnding" || next.type === "lineEndingBlank") continue;
75
+ if (next.type === "atxHeading") break;
76
+ if (next.type === "table") tableToken = next;
77
+ break;
78
+ }
79
+ if (tableToken === null) {
80
+ onError({
81
+ lineNumber: headingLine,
82
+ detail: `Dependencies section must contain a table, not a list or paragraph. See: ${RULE_DOCS.CSH005}`
83
+ });
84
+ continue;
85
+ }
86
+ const tableHead = tableToken.children.find((c) => c.type === "tableHead");
87
+ if (!tableHead) {
88
+ onError({
89
+ lineNumber: tableToken.startLine,
90
+ detail: `Dependencies table is missing a header row. See: ${RULE_DOCS.CSH005}`
91
+ });
92
+ continue;
93
+ }
94
+ const headerRow = tableHead.children.find((c) => c.type === "tableRow");
95
+ if (!headerRow) {
96
+ onError({
97
+ lineNumber: tableToken.startLine,
98
+ detail: `Dependencies table is missing a header row. See: ${RULE_DOCS.CSH005}`
99
+ });
100
+ continue;
101
+ }
102
+ const headers = getRowCells(headerRow).map((h) => h.toLowerCase());
103
+ if (headers.length !== EXPECTED_HEADERS.length || !headers.every((h, idx) => h === EXPECTED_HEADERS[idx])) {
104
+ onError({
105
+ lineNumber: headerRow.startLine,
106
+ detail: `Dependencies table must have columns: Dependency, Type, Action, From, To. Got: ${headers.join(", ")}. See: ${RULE_DOCS.CSH005}`
107
+ });
108
+ continue;
109
+ }
110
+ const tableBody = tableToken.children.find((c) => c.type === "tableBody");
111
+ if (!tableBody) {
112
+ onError({
113
+ lineNumber: tableToken.startLine,
114
+ detail: `Dependencies table must have at least one data row. See: ${RULE_DOCS.CSH005}`
115
+ });
116
+ continue;
117
+ }
118
+ const dataRows = tableBody.children.filter((c) => c.type === "tableRow");
119
+ if (dataRows.length === 0) {
120
+ onError({
121
+ lineNumber: tableToken.startLine,
122
+ detail: `Dependencies table must have at least one data row. See: ${RULE_DOCS.CSH005}`
123
+ });
124
+ continue;
125
+ }
126
+ for (const row of dataRows) {
127
+ const cells = getRowCells(row);
128
+ if (cells.length < 5) {
129
+ onError({
130
+ lineNumber: row.startLine,
131
+ detail: `Dependencies table row has too few columns (expected 5, got ${cells.length}). See: ${RULE_DOCS.CSH005}`
132
+ });
133
+ continue;
134
+ }
135
+ const [dependency, type, action, from, to] = cells;
136
+ if (!dependency) onError({
137
+ lineNumber: row.startLine,
138
+ detail: `Dependencies table row has an empty 'Dependency' cell. See: ${RULE_DOCS.CSH005}`
139
+ });
140
+ if (!VALID_TYPES.has(type)) onError({
141
+ lineNumber: row.startLine,
142
+ detail: `Invalid dependency type '${type}'. Valid types are: ${[...VALID_TYPES].join(", ")}. See: ${RULE_DOCS.CSH005}`
143
+ });
144
+ if (!VALID_ACTIONS.has(action)) onError({
145
+ lineNumber: row.startLine,
146
+ detail: `Invalid dependency action '${action}'. Valid actions are: ${[...VALID_ACTIONS].join(", ")}. See: ${RULE_DOCS.CSH005}`
147
+ });
148
+ if (from && !VERSION_RE.test(from)) onError({
149
+ lineNumber: row.startLine,
150
+ detail: `Invalid 'from' value '${from}'. Must be a semver string or em dash (\u2014). See: ${RULE_DOCS.CSH005}`
151
+ });
152
+ if (to && !VERSION_RE.test(to)) onError({
153
+ lineNumber: row.startLine,
154
+ detail: `Invalid 'to' value '${to}'. Must be a semver string or em dash (\u2014). See: ${RULE_DOCS.CSH005}`
155
+ });
156
+ if (action === "added" && from !== EM_DASH) onError({
157
+ lineNumber: row.startLine,
158
+ detail: `'from' must be '\u2014' when action is 'added' (got '${from}'). See: ${RULE_DOCS.CSH005}`
159
+ });
160
+ if (action === "removed" && to !== EM_DASH) onError({
161
+ lineNumber: row.startLine,
162
+ detail: `'to' must be '\u2014' when action is 'removed' (got '${to}'). See: ${RULE_DOCS.CSH005}`
163
+ });
164
+ }
165
+ }
166
+ }
167
+ };
168
+
169
+ //#endregion
170
+ export { DependencyTableFormatRule };
@@ -0,0 +1,61 @@
1
+ import { RULE_DOCS } from "../../constants.js";
2
+ import { getHeadingLevel } from "./utils.js";
3
+
4
+ //#region src/changesets/markdownlint/rules/heading-hierarchy.ts
5
+ /**
6
+ * markdownlint rule: `changeset-heading-hierarchy` (CSH001).
7
+ *
8
+ * Validates heading structure in changeset markdown files by inspecting
9
+ * `atxHeading` micromark tokens for three constraints:
10
+ *
11
+ * 1. **No h1 headings** -- h1 is reserved for the version title generated by
12
+ * the changelog formatter.
13
+ * 2. **Start at h2** -- the first heading in a changeset must be h2.
14
+ * 3. **No depth skips** -- heading levels must increase sequentially
15
+ * (h2 then h3, not h2 then h4).
16
+ *
17
+ * @remarks
18
+ * This rule mirrors the remark-lint rule `remarkLintHeadingHierarchy` but uses
19
+ * markdownlint's micromark token API so it can run inside markdownlint-cli2 and
20
+ * the VS Code markdownlint extension.
21
+ *
22
+ * @example
23
+ * ```json
24
+ * {
25
+ * "changeset-heading-hierarchy": true
26
+ * }
27
+ * ```
28
+ *
29
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH001.md | CSH001 rule documentation}
30
+ * @see `src/remark/rules/heading-hierarchy.ts` for the corresponding remark-lint rule
31
+ *
32
+ * @public
33
+ */
34
+ const HeadingHierarchyRule = {
35
+ names: ["changeset-heading-hierarchy", "CSH001"],
36
+ description: "Heading hierarchy must start at h2 with no h1 or depth skips",
37
+ tags: ["changeset"],
38
+ parser: "micromark",
39
+ function: function CSH001(params, onError) {
40
+ let prevDepth = 0;
41
+ for (const token of params.parsers.micromark.tokens) {
42
+ if (token.type !== "atxHeading") continue;
43
+ const depth = getHeadingLevel(token);
44
+ if (depth === 1) {
45
+ onError({
46
+ lineNumber: token.startLine,
47
+ detail: `h1 headings are not allowed in changeset files. Use h2 (##) for top-level sections like "## Features" or "## Bug Fixes". h1 is reserved for the version title generated by the changelog formatter. See: ${RULE_DOCS.CSH001}`
48
+ });
49
+ continue;
50
+ }
51
+ if (prevDepth > 0 && depth > prevDepth + 1) onError({
52
+ lineNumber: token.startLine,
53
+ detail: `Heading level skipped: expected h${prevDepth + 1} or lower, found h${depth}. Headings must increase sequentially (h2 → h3 → h4). Add the missing intermediate level or reduce this heading's depth. See: ${RULE_DOCS.CSH001}`
54
+ });
55
+ prevDepth = depth;
56
+ }
57
+ }
58
+ };
59
+
60
+ //#endregion
61
+ export { HeadingHierarchyRule };
@@ -0,0 +1,54 @@
1
+ import { allHeadings, isValidHeading } from "../../categories/index.js";
2
+ import { RULE_DOCS } from "../../constants.js";
3
+ import { getHeadingLevel, getHeadingText } from "./utils.js";
4
+
5
+ //#region src/changesets/markdownlint/rules/required-sections.ts
6
+ /**
7
+ * markdownlint rule: `changeset-required-sections` (CSH002).
8
+ *
9
+ * Validates that every h2 (`atxHeading` with depth 2) in a changeset markdown
10
+ * file matches a known category heading from the category system. When an
11
+ * unrecognized heading is found, the error detail lists all valid headings.
12
+ *
13
+ * @remarks
14
+ * Heading comparison is case-insensitive. The set of valid headings is provided
15
+ * by `allHeadings()` and `isValidHeading()` from the category system
16
+ * (`src/categories/index.ts`). This rule inspects `atxHeading` micromark
17
+ * tokens and extracts their text via the {@link getHeadingText} utility.
18
+ *
19
+ * This rule mirrors the remark-lint rule `remarkLintRequiredSections` but uses
20
+ * markdownlint's micromark token API so it can run inside markdownlint-cli2 and
21
+ * the VS Code markdownlint extension.
22
+ *
23
+ * @example
24
+ * ```json
25
+ * {
26
+ * "changeset-required-sections": true
27
+ * }
28
+ * ```
29
+ *
30
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH002.md | CSH002 rule documentation}
31
+ * @see `src/remark/rules/required-sections.ts` for the corresponding remark-lint rule
32
+ *
33
+ * @public
34
+ */
35
+ const RequiredSectionsRule = {
36
+ names: ["changeset-required-sections", "CSH002"],
37
+ description: "Section headings must match known changeset categories",
38
+ tags: ["changeset"],
39
+ parser: "micromark",
40
+ function: function CSH002(params, onError) {
41
+ for (const token of params.parsers.micromark.tokens) {
42
+ if (token.type !== "atxHeading") continue;
43
+ if (getHeadingLevel(token) !== 2) continue;
44
+ const text = getHeadingText(token);
45
+ if (!isValidHeading(text)) onError({
46
+ lineNumber: token.startLine,
47
+ detail: `Unknown section "${text}". Valid h2 headings are: ${allHeadings().join(", ")}. Heading comparison is case-insensitive. See: ${RULE_DOCS.CSH002}`
48
+ });
49
+ }
50
+ }
51
+ };
52
+
53
+ //#endregion
54
+ export { RequiredSectionsRule };
@@ -0,0 +1,54 @@
1
+ import { RULE_DOCS } from "../../constants.js";
2
+ import { getHeadingLevel } from "./utils.js";
3
+
4
+ //#region src/changesets/markdownlint/rules/uncategorized-content.ts
5
+ /**
6
+ * markdownlint rule: `changeset-uncategorized-content` (CSH004).
7
+ *
8
+ * Detects content that appears before the first h2 heading in a changeset
9
+ * markdown file. All substantive content must be placed under a categorized
10
+ * section (`## heading`).
11
+ *
12
+ * @remarks
13
+ * The rule iterates over the top-level micromark token stream and stops at the
14
+ * first `atxHeading` with depth 2. Any token encountered before that heading
15
+ * that is not a `lineEnding`, `lineEndingBlank`, or `htmlFlow` (HTML comments)
16
+ * triggers an error. This ensures that changeset content is always grouped
17
+ * under a recognized category heading.
18
+ *
19
+ * This rule mirrors the remark-lint rule `remarkLintUncategorizedContent` but
20
+ * uses markdownlint's micromark token API so it can run inside
21
+ * markdownlint-cli2 and the VS Code markdownlint extension.
22
+ *
23
+ * @example
24
+ * ```json
25
+ * {
26
+ * "changeset-uncategorized-content": true
27
+ * }
28
+ * ```
29
+ *
30
+ * @see {@link https://github.com/savvy-web/changesets/blob/main/docs/rules/CSH004.md | CSH004 rule documentation}
31
+ * @see `src/remark/rules/uncategorized-content.ts` for the corresponding remark-lint rule
32
+ *
33
+ * @public
34
+ */
35
+ const UncategorizedContentRule = {
36
+ names: ["changeset-uncategorized-content", "CSH004"],
37
+ description: "All content must be placed under a category heading (## heading)",
38
+ tags: ["changeset"],
39
+ parser: "micromark",
40
+ function: function CSH004(params, onError) {
41
+ const tokens = params.parsers.micromark.tokens;
42
+ for (const token of tokens) {
43
+ if (token.type === "atxHeading" && getHeadingLevel(token) === 2) break;
44
+ if (token.type === "lineEnding" || token.type === "lineEndingBlank" || token.type === "htmlFlow") continue;
45
+ if (token.type !== "atxHeading") onError({
46
+ lineNumber: token.startLine,
47
+ detail: `Content must be placed under a category heading (## heading). Move this content under an appropriate section like "## Features" or "## Bug Fixes". If it doesn't fit an existing category, use "## Other". See: ${RULE_DOCS.CSH004}`
48
+ });
49
+ }
50
+ }
51
+ };
52
+
53
+ //#endregion
54
+ export { UncategorizedContentRule };
@@ -0,0 +1,30 @@
1
+ import { RULE_DOCS } from "../../constants.js";
2
+
3
+ //#region src/changesets/markdownlint/rules/utils.ts
4
+ /**
5
+ * Get the heading level (1-6) from an `atxHeading` token.
6
+ *
7
+ * @param heading - The `atxHeading` micromark token
8
+ * @returns The heading depth (number of `#` characters), or 0 if no sequence found
9
+ *
10
+ * @internal
11
+ */
12
+ function getHeadingLevel(heading) {
13
+ const sequence = heading.children.find((c) => c.type === "atxHeadingSequence");
14
+ return sequence ? sequence.text.length : 0;
15
+ }
16
+ /**
17
+ * Get the plain text content of an `atxHeading` token.
18
+ *
19
+ * @param heading - The `atxHeading` micromark token
20
+ * @returns The heading text, or empty string if no text token found
21
+ *
22
+ * @internal
23
+ */
24
+ function getHeadingText(heading) {
25
+ const textToken = heading.children.find((c) => c.type === "atxHeadingText");
26
+ return textToken ? textToken.text : "";
27
+ }
28
+
29
+ //#endregion
30
+ export { getHeadingLevel, getHeadingText };
@@ -0,0 +1,47 @@
1
+ import { collapseDependencyRows, parseDependencyTable, serializeDependencyTable, sortDependencyRows } from "../../utils/dependency-table.js";
2
+ import { getBlockSections, getHeadingText, getVersionBlocks } from "../../utils/version-blocks.js";
3
+
4
+ //#region src/changesets/remark/plugins/aggregate-dependency-tables.ts
5
+ const AggregateDependencyTablesPlugin = () => {
6
+ return (tree) => {
7
+ const blocks = getVersionBlocks(tree);
8
+ for (let b = blocks.length - 1; b >= 0; b--) {
9
+ const depSections = getBlockSections(tree, blocks[b]).filter((s) => getHeadingText(s.heading).toLowerCase() === "dependencies");
10
+ if (depSections.length === 0) continue;
11
+ const allRows = [];
12
+ const legacyContent = [];
13
+ for (const section of depSections) for (const node of section.contentNodes) if (node.type === "table") try {
14
+ const rows = parseDependencyTable(node);
15
+ allRows.push(...rows);
16
+ } catch {
17
+ legacyContent.push(node);
18
+ }
19
+ else legacyContent.push(node);
20
+ const collapsed = sortDependencyRows(collapseDependencyRows(allRows));
21
+ const indicesToRemove = [];
22
+ for (const section of depSections) {
23
+ indicesToRemove.push(section.headingIndex);
24
+ for (let c = 0; c < section.contentNodes.length; c++) indicesToRemove.push(section.headingIndex + 1 + c);
25
+ }
26
+ indicesToRemove.sort((a, b) => b - a);
27
+ for (const idx of indicesToRemove) tree.children.splice(idx, 1);
28
+ if (collapsed.length === 0 && legacyContent.length === 0) continue;
29
+ const insertAt = depSections[0].headingIndex;
30
+ const newNodes = [];
31
+ newNodes.push({
32
+ type: "heading",
33
+ depth: 3,
34
+ children: [{
35
+ type: "text",
36
+ value: "Dependencies"
37
+ }]
38
+ });
39
+ if (collapsed.length > 0) newNodes.push(serializeDependencyTable(collapsed));
40
+ newNodes.push(...legacyContent);
41
+ tree.children.splice(insertAt, 0, ...newNodes);
42
+ }
43
+ };
44
+ };
45
+
46
+ //#endregion
47
+ export { AggregateDependencyTablesPlugin };
@@ -0,0 +1,123 @@
1
+ import { getVersionBlocks } from "../../utils/version-blocks.js";
2
+ import { visit } from "unist-util-visit";
3
+
4
+ //#region src/changesets/remark/plugins/contributor-footnotes.ts
5
+ /**
6
+ * Pattern matching `Thanks @user!` at end of text.
7
+ *
8
+ * @internal
9
+ */
10
+ const ATTRIBUTION_PLAIN_RE = /\s*Thanks @(\w[\w-]*)!$/;
11
+ /**
12
+ * Try to extract a linked attribution from the end of a paragraph's children.
13
+ * Pattern: text "...Thanks " + link "\@user" + text "!"
14
+ *
15
+ * @param children - The phrasing content children of a paragraph node
16
+ * @returns The contributor and the index to start removal from, or `undefined`
17
+ *
18
+ * @internal
19
+ */
20
+ function extractLinkedAttribution(children) {
21
+ if (children.length < 3) return void 0;
22
+ const last = children[children.length - 1];
23
+ const secondLast = children[children.length - 2];
24
+ const thirdLast = children[children.length - 3];
25
+ if (last.type !== "text" || last.value !== "!") return void 0;
26
+ if (secondLast.type !== "link") return void 0;
27
+ const linkNode = secondLast;
28
+ if (linkNode.children.length !== 1 || linkNode.children[0].type !== "text") return void 0;
29
+ const linkText = linkNode.children[0].value;
30
+ if (!linkText.startsWith("@")) return void 0;
31
+ const username = linkText.slice(1);
32
+ if (thirdLast.type !== "text") return void 0;
33
+ const textNode = thirdLast;
34
+ if (!/\s*Thanks $/.test(textNode.value)) return void 0;
35
+ return {
36
+ contributor: {
37
+ username,
38
+ url: linkNode.url
39
+ },
40
+ removeFrom: children.length - 3
41
+ };
42
+ }
43
+ const ContributorFootnotesPlugin = () => {
44
+ return (tree) => {
45
+ const blocks = getVersionBlocks(tree);
46
+ for (let b = blocks.length - 1; b >= 0; b--) {
47
+ const block = blocks[b];
48
+ const contributors = /* @__PURE__ */ new Map();
49
+ for (let i = block.startIndex; i < block.endIndex; i++) {
50
+ const node = tree.children[i];
51
+ if (node.type !== "list") continue;
52
+ visit(node, "paragraph", (para) => {
53
+ const linked = extractLinkedAttribution(para.children);
54
+ if (linked) {
55
+ const key = linked.contributor.username.toLowerCase();
56
+ if (!contributors.has(key)) contributors.set(key, linked.contributor);
57
+ const textNode = para.children[linked.removeFrom];
58
+ textNode.value = textNode.value.replace(/\s*Thanks $/, "");
59
+ para.children.splice(linked.removeFrom + 1, 2);
60
+ if (textNode.value === "") para.children.splice(linked.removeFrom, 1);
61
+ return;
62
+ }
63
+ const last = para.children[para.children.length - 1];
64
+ if (last?.type === "text") {
65
+ const textNode = last;
66
+ const match = textNode.value.match(ATTRIBUTION_PLAIN_RE);
67
+ if (match) {
68
+ const username = match[1];
69
+ const key = username.toLowerCase();
70
+ if (!contributors.has(key)) contributors.set(key, {
71
+ username,
72
+ url: void 0
73
+ });
74
+ textNode.value = textNode.value.replace(ATTRIBUTION_PLAIN_RE, "");
75
+ }
76
+ }
77
+ });
78
+ }
79
+ if (contributors.size === 0) continue;
80
+ const sorted = [...contributors.values()].sort((a, b) => a.username.toLowerCase().localeCompare(b.username.toLowerCase()));
81
+ const phrasingChildren = [];
82
+ phrasingChildren.push({
83
+ type: "text",
84
+ value: "Thanks to "
85
+ });
86
+ for (let i = 0; i < sorted.length; i++) {
87
+ const contrib = sorted[i];
88
+ if (i > 0 && sorted.length > 2) phrasingChildren.push({
89
+ type: "text",
90
+ value: ", "
91
+ });
92
+ if (i > 0 && i === sorted.length - 1) phrasingChildren.push({
93
+ type: "text",
94
+ value: sorted.length === 2 ? " and " : "and "
95
+ });
96
+ if (contrib.url) phrasingChildren.push({
97
+ type: "link",
98
+ url: contrib.url,
99
+ children: [{
100
+ type: "text",
101
+ value: `@${contrib.username}`
102
+ }]
103
+ });
104
+ else phrasingChildren.push({
105
+ type: "text",
106
+ value: `@${contrib.username}`
107
+ });
108
+ }
109
+ phrasingChildren.push({
110
+ type: "text",
111
+ value: " for their contributions!"
112
+ });
113
+ const paragraph = {
114
+ type: "paragraph",
115
+ children: phrasingChildren
116
+ };
117
+ tree.children.splice(block.endIndex, 0, paragraph);
118
+ }
119
+ };
120
+ };
121
+
122
+ //#endregion
123
+ export { ContributorFootnotesPlugin };
@@ -0,0 +1,30 @@
1
+ import { getBlockSections, getVersionBlocks } from "../../utils/version-blocks.js";
2
+ import { toString } from "mdast-util-to-string";
3
+
4
+ //#region src/changesets/remark/plugins/deduplicate-items.ts
5
+ const DeduplicateItemsPlugin = () => {
6
+ return (tree) => {
7
+ const blocks = getVersionBlocks(tree);
8
+ for (const block of blocks) {
9
+ const sections = getBlockSections(tree, block);
10
+ for (const section of sections) for (const node of section.contentNodes) {
11
+ if (node.type !== "list") continue;
12
+ const list = node;
13
+ const seen = /* @__PURE__ */ new Set();
14
+ list.children = list.children.filter((item) => {
15
+ const text = toString(item);
16
+ if (seen.has(text)) return false;
17
+ seen.add(text);
18
+ return true;
19
+ });
20
+ }
21
+ }
22
+ tree.children = tree.children.filter((node) => {
23
+ if (node.type === "list") return node.children.length > 0;
24
+ return true;
25
+ });
26
+ };
27
+ };
28
+
29
+ //#endregion
30
+ export { DeduplicateItemsPlugin };
@@ -0,0 +1,58 @@
1
+ import { getVersionBlocks } from "../../utils/version-blocks.js";
2
+ import { SKIP, visit } from "unist-util-visit";
3
+
4
+ //#region src/changesets/remark/plugins/issue-link-refs.ts
5
+ /**
6
+ * Pattern matching issue numbers like `#123`.
7
+ *
8
+ * @internal
9
+ */
10
+ const ISSUE_RE = /^#\d+$/;
11
+ const IssueLinkRefsPlugin = () => {
12
+ return (tree) => {
13
+ const blocks = getVersionBlocks(tree);
14
+ for (let b = blocks.length - 1; b >= 0; b--) {
15
+ const block = blocks[b];
16
+ const definitions = /* @__PURE__ */ new Map();
17
+ for (let i = block.startIndex; i < block.endIndex; i++) {
18
+ const node = tree.children[i];
19
+ visit(node, "link", (linkNode, index, parent) => {
20
+ if (!parent || index === void 0) return;
21
+ if (linkNode.children.length !== 1 || linkNode.children[0].type !== "text") return;
22
+ const text = linkNode.children[0].value;
23
+ if (!ISSUE_RE.test(text)) return;
24
+ const label = text;
25
+ if (!definitions.has(label)) definitions.set(label, {
26
+ label,
27
+ url: linkNode.url
28
+ });
29
+ const ref = {
30
+ type: "linkReference",
31
+ identifier: label,
32
+ label,
33
+ referenceType: "full",
34
+ children: [{
35
+ type: "text",
36
+ value: label
37
+ }]
38
+ };
39
+ parent.children[index] = ref;
40
+ return SKIP;
41
+ });
42
+ }
43
+ if (definitions.size === 0) continue;
44
+ const defNodes = [...definitions.values()].sort((a, b) => {
45
+ return Number.parseInt(a.label.slice(1), 10) - Number.parseInt(b.label.slice(1), 10);
46
+ }).map((def) => ({
47
+ type: "definition",
48
+ identifier: def.label,
49
+ label: def.label,
50
+ url: def.url
51
+ }));
52
+ tree.children.splice(block.endIndex, 0, ...defNodes);
53
+ }
54
+ };
55
+ };
56
+
57
+ //#endregion
58
+ export { IssueLinkRefsPlugin };
@@ -0,0 +1,43 @@
1
+ import { fromHeading } from "../../categories/index.js";
2
+ import { getBlockSections, getHeadingText, getVersionBlocks } from "../../utils/version-blocks.js";
3
+
4
+ //#region src/changesets/remark/plugins/merge-sections.ts
5
+ const MergeSectionsPlugin = () => {
6
+ return (tree) => {
7
+ const blocks = getVersionBlocks(tree);
8
+ for (let b = blocks.length - 1; b >= 0; b--) {
9
+ const sections = getBlockSections(tree, blocks[b]);
10
+ if (sections.length <= 1) continue;
11
+ const groups = /* @__PURE__ */ new Map();
12
+ for (let s = 0; s < sections.length; s++) {
13
+ const text = getHeadingText(sections[s].heading);
14
+ const category = fromHeading(text);
15
+ const key = category ? category.heading.toLowerCase() : text.toLowerCase();
16
+ const existing = groups.get(key);
17
+ if (existing) existing.push(s);
18
+ else groups.set(key, [s]);
19
+ }
20
+ const indicesToRemove = [];
21
+ for (const indices of groups.values()) {
22
+ if (indices.length <= 1) continue;
23
+ const target = sections[indices[0]];
24
+ const targetEndIndex = target.headingIndex + 1 + target.contentNodes.length;
25
+ const mergedContent = [];
26
+ for (let d = 1; d < indices.length; d++) {
27
+ const dup = sections[indices[d]];
28
+ mergedContent.push(...dup.contentNodes);
29
+ indicesToRemove.push(dup.headingIndex);
30
+ for (let c = 0; c < dup.contentNodes.length; c++) indicesToRemove.push(dup.headingIndex + 1 + c);
31
+ }
32
+ tree.children.splice(targetEndIndex, 0, ...mergedContent);
33
+ const shift = mergedContent.length;
34
+ for (let r = 0; r < indicesToRemove.length; r++) if (indicesToRemove[r] >= targetEndIndex) indicesToRemove[r] += shift;
35
+ }
36
+ indicesToRemove.sort((a, b) => b - a);
37
+ for (const idx of indicesToRemove) tree.children.splice(idx, 1);
38
+ }
39
+ };
40
+ };
41
+
42
+ //#endregion
43
+ export { MergeSectionsPlugin };