@savvy-web/silk-effects 9.6.0 → 10.0.1

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 (52) hide show
  1. package/changesets/api/dependency-table.js +12 -12
  2. package/changesets/api/linter.js +23 -19
  3. package/changesets/api/transformer.js +38 -33
  4. package/changesets/categories/index.js +1 -1
  5. package/changesets/categories/types.js +2 -2
  6. package/changesets/changelog/getDependencyReleaseLine.js +1 -1
  7. package/changesets/changelog/index.js +2 -2
  8. package/changesets/constants.js +1 -1
  9. package/changesets/errors.js +1 -1
  10. package/changesets/index.js +11 -40
  11. package/changesets/markdownlint/rules/content-structure.js +2 -2
  12. package/changesets/markdownlint/rules/heading-hierarchy.js +2 -2
  13. package/changesets/markdownlint/rules/required-sections.js +2 -2
  14. package/changesets/markdownlint/rules/uncategorized-content.js +2 -2
  15. package/changesets/markdownlint/rules/utils.js +6 -6
  16. package/changesets/rules/content-structure.js +44 -0
  17. package/changesets/rules/dependency-table-format.js +54 -0
  18. package/changesets/rules/heading-hierarchy.js +37 -0
  19. package/changesets/rules/required-sections.js +34 -0
  20. package/changesets/rules/types.js +20 -0
  21. package/changesets/rules/uncategorized-content.js +30 -0
  22. package/changesets/schemas/changeset.js +1 -1
  23. package/changesets/transforms/aggregate-dependency-tables.js +96 -0
  24. package/changesets/transforms/contributor-footnotes.js +290 -0
  25. package/changesets/transforms/deduplicate-items.js +47 -0
  26. package/changesets/transforms/issue-link-refs.js +47 -0
  27. package/changesets/transforms/maintenance-note.js +65 -0
  28. package/changesets/transforms/merge-sections.js +56 -0
  29. package/changesets/transforms/normalize-format.js +53 -0
  30. package/changesets/transforms/reorder-sections.js +53 -0
  31. package/changesets/utils/blocks.js +67 -0
  32. package/changesets/utils/dependency-table.js +29 -36
  33. package/changesets/utils/markdown-emit.js +11 -36
  34. package/index.d.ts +54 -1199
  35. package/package.json +11 -18
  36. package/tsdoc-metadata.json +1 -1
  37. package/changesets/remark/plugins/aggregate-dependency-tables.js +0 -98
  38. package/changesets/remark/plugins/contributor-footnotes.js +0 -334
  39. package/changesets/remark/plugins/deduplicate-items.js +0 -31
  40. package/changesets/remark/plugins/issue-link-refs.js +0 -57
  41. package/changesets/remark/plugins/maintenance-note.js +0 -74
  42. package/changesets/remark/plugins/merge-sections.js +0 -42
  43. package/changesets/remark/plugins/normalize-format.js +0 -46
  44. package/changesets/remark/plugins/reorder-sections.js +0 -48
  45. package/changesets/remark/presets.js +0 -118
  46. package/changesets/remark/rules/content-structure.js +0 -21
  47. package/changesets/remark/rules/dependency-table-format.js +0 -37
  48. package/changesets/remark/rules/heading-hierarchy.js +0 -18
  49. package/changesets/remark/rules/required-sections.js +0 -16
  50. package/changesets/remark/rules/uncategorized-content.js +0 -30
  51. package/changesets/utils/remark-pipeline.js +0 -35
  52. package/changesets/utils/version-blocks.js +0 -107
@@ -1,46 +0,0 @@
1
- import { getVersionBlocks } from "../../utils/version-blocks.js";
2
- import { toString } from "mdast-util-to-string";
3
-
4
- // src/changesets/remark/plugins/normalize-format.ts
5
- /**
6
- * Check if a node is a heading at one of the given depths.
7
- *
8
- * @param node - The AST node to check
9
- * @param depths - Allowed heading depths
10
- * @returns `true` if the node is a heading at one of the specified depths
11
- *
12
- * @internal
13
- */
14
- function isHeadingAtDepth(node, depths) {
15
- return node.type === "heading" && depths.includes(node.depth);
16
- }
17
- const NormalizeFormatPlugin = () => {
18
- return (tree) => {
19
- const blocks = getVersionBlocks(tree);
20
- const indicesToRemove = /* @__PURE__ */ new Set();
21
- for (const block of blocks) for (let i = block.startIndex; i < block.endIndex; i++) {
22
- const node = tree.children[i];
23
- if (node.type === "list" && node.children.length === 0) {
24
- indicesToRemove.add(i);
25
- continue;
26
- }
27
- if (!isHeadingAtDepth(node, [3])) continue;
28
- let hasContent = false;
29
- for (let j = i + 1; j < block.endIndex; j++) {
30
- const next = tree.children[j];
31
- if (next.type === "paragraph" && toString(next).trim() === "") {
32
- indicesToRemove.add(j);
33
- continue;
34
- }
35
- if (isHeadingAtDepth(next, [2, 3])) break;
36
- hasContent = true;
37
- break;
38
- }
39
- if (!hasContent) indicesToRemove.add(i);
40
- }
41
- const sorted = [...indicesToRemove].sort((a, b) => b - a);
42
- for (const idx of sorted) tree.children.splice(idx, 1);
43
- };
44
- };
45
-
46
- export { NormalizeFormatPlugin };
@@ -1,48 +0,0 @@
1
- import { fromHeading } from "../../categories/index.js";
2
- import { getBlockSections, getHeadingText, getVersionBlocks } from "../../utils/version-blocks.js";
3
-
4
- // src/changesets/remark/plugins/reorder-sections.ts
5
- /**
6
- * Default priority for unrecognized headings (sorts after all known categories).
7
- *
8
- * @internal
9
- */
10
- const UNKNOWN_PRIORITY = 999;
11
- /**
12
- * Priority for the `Thanks` section emitted by {@link ContributorFootnotesPlugin}.
13
- * Sorts after everything — including unknown headings — so the contributor
14
- * credits always close the version block.
15
- *
16
- * @internal
17
- */
18
- const THANKS_PRIORITY = 1e3;
19
- /**
20
- * Resolve the sort priority for a section heading.
21
- *
22
- * @internal
23
- */
24
- function headingPriority(text) {
25
- if (text.trim().toLowerCase() === "thanks") return THANKS_PRIORITY;
26
- return fromHeading(text)?.priority ?? UNKNOWN_PRIORITY;
27
- }
28
- const ReorderSectionsPlugin = () => {
29
- return (tree) => {
30
- const blocks = getVersionBlocks(tree);
31
- for (let b = blocks.length - 1; b >= 0; b--) {
32
- const block = blocks[b];
33
- const sections = getBlockSections(tree, block);
34
- if (sections.length <= 1) continue;
35
- const preamble = [];
36
- for (let i = block.startIndex; i < block.endIndex; i++) if (i < sections[0].headingIndex) preamble.push(tree.children[i]);
37
- else break;
38
- const sorted = [...sections].sort((a, b) => headingPriority(getHeadingText(a.heading)) - headingPriority(getHeadingText(b.heading)));
39
- if (sorted.every((s, i) => s.headingIndex === sections[i].headingIndex)) continue;
40
- const newChildren = [...preamble];
41
- for (const section of sorted) newChildren.push(section.heading, ...section.contentNodes);
42
- const blockLength = block.endIndex - block.startIndex;
43
- tree.children.splice(block.startIndex, blockLength, ...newChildren);
44
- }
45
- };
46
- };
47
-
48
- export { ReorderSectionsPlugin };
@@ -1,118 +0,0 @@
1
- import { ContentStructureRule } from "./rules/content-structure.js";
2
- import { DependencyTableFormatRule } from "./rules/dependency-table-format.js";
3
- import { HeadingHierarchyRule } from "./rules/heading-hierarchy.js";
4
- import { RequiredSectionsRule } from "./rules/required-sections.js";
5
- import { UncategorizedContentRule } from "./rules/uncategorized-content.js";
6
- import { ContributorFootnotesPlugin } from "./plugins/contributor-footnotes.js";
7
- import { AggregateDependencyTablesPlugin } from "./plugins/aggregate-dependency-tables.js";
8
- import { DeduplicateItemsPlugin } from "./plugins/deduplicate-items.js";
9
- import { IssueLinkRefsPlugin } from "./plugins/issue-link-refs.js";
10
- import { MergeSectionsPlugin } from "./plugins/merge-sections.js";
11
- import { NormalizeFormatPlugin } from "./plugins/normalize-format.js";
12
- import { ReorderSectionsPlugin } from "./plugins/reorder-sections.js";
13
-
14
- // src/changesets/remark/presets.ts
15
- /**
16
- * Remark preset collections for changeset lint rules and transform plugins.
17
- *
18
- * @remarks
19
- * Presets bundle related remark plugins into ordered arrays for convenient
20
- * consumption. Each preset is a `readonly` tuple so that TypeScript can
21
- * narrow element types. Iterate and `.use()` each entry with `unified()`.
22
- *
23
- * @see {@link SilkChangesetPreset} for lint rules
24
- * @see {@link SilkChangesetTransformPreset} for transform plugins
25
- */
26
- /**
27
- * Preset combining all changeset lint rules for convenient consumption.
28
- *
29
- * @remarks
30
- * Includes the following rules in order:
31
- *
32
- * 1. {@link HeadingHierarchyRule} (CSH001) -- no h1, no depth skips
33
- * 2. {@link RequiredSectionsRule} (CSH002) -- h2 headings must match a known category
34
- * 3. {@link ContentStructureRule} (CSH003) -- non-empty sections, code fence languages, non-empty list items
35
- * 4. {@link UncategorizedContentRule} (CSH004) -- no content before the first h2 heading
36
- * 5. {@link DependencyTableFormatRule} (CSH005) -- dependency table column/value validation
37
- *
38
- * Rule execution order is not significant for lint rules; all rules run
39
- * independently over the same AST and report warnings to the virtual file.
40
- *
41
- * @example
42
- * ```typescript
43
- * import { SilkChangesetPreset } from "\@savvy-web/changesets/remark";
44
- * import remarkParse from "remark-parse";
45
- * import { unified } from "unified";
46
- * import { read } from "to-vfile";
47
- *
48
- * const processor = unified().use(remarkParse);
49
- * for (const rule of SilkChangesetPreset) {
50
- * processor.use(rule);
51
- * }
52
- *
53
- * const file = await read("changeset.md");
54
- * const result = await processor.process(file);
55
- * console.log(result.messages); // lint warnings
56
- * ```
57
- *
58
- * @see {@link SilkChangesetTransformPreset} for the corresponding transform preset
59
- *
60
- * @public
61
- */
62
- const SilkChangesetPreset = [
63
- HeadingHierarchyRule,
64
- RequiredSectionsRule,
65
- ContentStructureRule,
66
- UncategorizedContentRule,
67
- DependencyTableFormatRule
68
- ];
69
- /**
70
- * Ordered array of all transform plugins in the correct execution order.
71
- *
72
- * @remarks
73
- * Plugin ordering is significant -- each plugin may depend on the output of
74
- * earlier plugins in the pipeline:
75
- *
76
- * 1. {@link AggregateDependencyTablesPlugin} -- merge duplicate dependency sections (must run first so downstream plugins see a single Dependencies section)
77
- * 2. {@link MergeSectionsPlugin} -- merge duplicate h3 headings (must run before reorder so that priority is computed on consolidated sections)
78
- * 3. {@link ReorderSectionsPlugin} -- sort sections by category priority (Breaking Changes first, Other last)
79
- * 4. {@link DeduplicateItemsPlugin} -- remove duplicate list items within each section
80
- * 5. {@link ContributorFootnotesPlugin} -- extract inline `Thanks \@user!` attributions and aggregate them into a summary paragraph per version block
81
- * 6. {@link IssueLinkRefsPlugin} -- convert inline `[#N](url)` links to reference-style `[#N]` with definitions at the end of each version block
82
- * 7. {@link NormalizeFormatPlugin} -- final cleanup removing empty sections and empty lists
83
- *
84
- * @example
85
- * ```typescript
86
- * import { SilkChangesetTransformPreset } from "\@savvy-web/changesets/remark";
87
- * import remarkGfm from "remark-gfm";
88
- * import remarkParse from "remark-parse";
89
- * import remarkStringify from "remark-stringify";
90
- * import { unified } from "unified";
91
- * import { read } from "to-vfile";
92
- *
93
- * const processor = unified().use(remarkParse).use(remarkGfm);
94
- * for (const plugin of SilkChangesetTransformPreset) {
95
- * processor.use(plugin);
96
- * }
97
- * processor.use(remarkStringify);
98
- *
99
- * const file = await read("CHANGELOG.md");
100
- * const result = await processor.process(file);
101
- * console.log(String(result));
102
- * ```
103
- *
104
- * @see {@link SilkChangesetPreset} for the corresponding lint preset
105
- *
106
- * @public
107
- */
108
- const SilkChangesetTransformPreset = [
109
- AggregateDependencyTablesPlugin,
110
- MergeSectionsPlugin,
111
- ReorderSectionsPlugin,
112
- DeduplicateItemsPlugin,
113
- ContributorFootnotesPlugin,
114
- IssueLinkRefsPlugin,
115
- NormalizeFormatPlugin
116
- ];
117
-
118
- export { SilkChangesetPreset, SilkChangesetTransformPreset };
@@ -1,21 +0,0 @@
1
- import { RULE_DOCS } from "../../constants.js";
2
- import { toString } from "mdast-util-to-string";
3
- import { lintRule } from "unified-lint-rule";
4
- import { visit } from "unist-util-visit";
5
-
6
- // src/changesets/remark/rules/content-structure.ts
7
- const ContentStructureRule = lintRule("remark-lint:changeset-content-structure", (tree, file) => {
8
- visit(tree, "heading", (node, index, parent) => {
9
- if (node.depth !== 2 || parent == null || index == null) return;
10
- const next = parent.children[index + 1];
11
- if (!next || next.type === "heading" && next.depth === 2) file.message(`Empty section: heading has no content before the next section or end of file. Add a list of changes (e.g., "- Added feature X") under this heading, or remove the empty heading. See: ${RULE_DOCS.CSH003}`, node);
12
- });
13
- visit(tree, "code", (node) => {
14
- if (!node.lang) file.message(`Code block is missing a language identifier. Add a language after the opening fence (e.g., \`\`\`ts, \`\`\`json, \`\`\`bash). See: ${RULE_DOCS.CSH003}`, node);
15
- });
16
- visit(tree, "listItem", (node) => {
17
- if (!toString(node).trim()) file.message(`Empty list item. Each list item must contain descriptive text (e.g., "- Fixed login timeout issue"). See: ${RULE_DOCS.CSH003}`, node);
18
- });
19
- });
20
-
21
- export { ContentStructureRule };
@@ -1,37 +0,0 @@
1
- import { parseDependencyTable } from "../../utils/dependency-table.js";
2
- import { RULE_DOCS } from "../../constants.js";
3
- import { scanDependencySection } from "../../utils/dependency-section.js";
4
- import { toString } from "mdast-util-to-string";
5
- import { lintRule } from "unified-lint-rule";
6
- import { visit } from "unist-util-visit";
7
-
8
- // src/changesets/remark/rules/dependency-table-format.ts
9
- /** @internal */
10
- const EM_DASH = "—";
11
- const DependencyTableFormatRule = lintRule("remark-lint:changeset-dependency-table-format", (tree, file) => {
12
- visit(tree, "heading", (node, index) => {
13
- if (node.depth !== 2) return;
14
- if (toString(node).toLowerCase() !== "dependencies") return;
15
- if (index === void 0) return;
16
- const scan = scanDependencySection(tree.children, index + 1, {
17
- isHeading: (n) => n.type === "heading",
18
- isTable: (n) => n.type === "table"
19
- });
20
- if (scan.table === void 0) {
21
- file.message(`Dependencies section must contain a table, not a list or paragraph. See: ${RULE_DOCS.CSH005}`, node);
22
- return;
23
- }
24
- const table = scan.table;
25
- try {
26
- const rows = parseDependencyTable(table);
27
- for (const row of rows) {
28
- if (row.action === "added" && row.from !== EM_DASH) file.message(`'from' must be '\u2014' when action is 'added' (got '${row.from}'). See: ${RULE_DOCS.CSH005}`, table);
29
- if (row.action === "removed" && row.to !== EM_DASH) file.message(`'to' must be '\u2014' when action is 'removed' (got '${row.to}'). See: ${RULE_DOCS.CSH005}`, table);
30
- }
31
- } catch (error) {
32
- file.message(`${error instanceof Error ? error.message : String(error)}. See: ${RULE_DOCS.CSH005}`, table);
33
- }
34
- });
35
- });
36
-
37
- export { DependencyTableFormatRule };
@@ -1,18 +0,0 @@
1
- import { RULE_DOCS } from "../../constants.js";
2
- import { lintRule } from "unified-lint-rule";
3
- import { visit } from "unist-util-visit";
4
-
5
- // src/changesets/remark/rules/heading-hierarchy.ts
6
- const HeadingHierarchyRule = lintRule("remark-lint:changeset-heading-hierarchy", (tree, file) => {
7
- let prevDepth = 0;
8
- visit(tree, "heading", (node) => {
9
- if (node.depth === 1) {
10
- file.message(`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}`, node);
11
- return;
12
- }
13
- if (prevDepth > 0 && node.depth > prevDepth + 1) file.message(`Heading level skipped: expected h${prevDepth + 1} or lower, found h${node.depth}. Headings must increase sequentially (h2 → h3 → h4). Add the missing intermediate level or reduce this heading's depth. See: ${RULE_DOCS.CSH001}`, node);
14
- prevDepth = node.depth;
15
- });
16
- });
17
-
18
- export { HeadingHierarchyRule };
@@ -1,16 +0,0 @@
1
- import { allHeadings, isValidHeading } from "../../categories/index.js";
2
- import { RULE_DOCS } from "../../constants.js";
3
- import { toString } from "mdast-util-to-string";
4
- import { lintRule } from "unified-lint-rule";
5
- import { visit } from "unist-util-visit";
6
-
7
- // src/changesets/remark/rules/required-sections.ts
8
- const RequiredSectionsRule = lintRule("remark-lint:changeset-required-sections", (tree, file) => {
9
- visit(tree, "heading", (node) => {
10
- if (node.depth !== 2) return;
11
- const text = toString(node);
12
- if (!isValidHeading(text)) file.message(`Unknown section "${text}". Valid h2 headings are: ${allHeadings().join(", ")}. Heading comparison is case-insensitive. See: ${RULE_DOCS.CSH002}`, node);
13
- });
14
- });
15
-
16
- export { RequiredSectionsRule };
@@ -1,30 +0,0 @@
1
- import { RULE_DOCS } from "../../constants.js";
2
- import { lintRule } from "unified-lint-rule";
3
-
4
- // src/changesets/remark/rules/uncategorized-content.ts
5
- /**
6
- * Node types to ignore when detecting uncategorized content (e.g., HTML comments).
7
- *
8
- * @internal
9
- */
10
- const IGNORED_TYPES = /* @__PURE__ */ new Set(["html"]);
11
- /**
12
- * Determine whether a node is substantive content (not a heading or ignored type).
13
- *
14
- * @param node - The AST node to check
15
- * @returns `true` if the node is content that should be under a heading
16
- *
17
- * @internal
18
- */
19
- function isContentNode(node) {
20
- if (node.type === "heading") return false;
21
- return !IGNORED_TYPES.has(node.type);
22
- }
23
- const UncategorizedContentRule = lintRule("remark-lint:changeset-uncategorized-content", (tree, file) => {
24
- for (const node of tree.children) {
25
- if (node.type === "heading" && node.depth === 2) break;
26
- if (isContentNode(node)) file.message(`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}`, node);
27
- }
28
- });
29
-
30
- export { UncategorizedContentRule };
@@ -1,35 +0,0 @@
1
- import { emitMarkdown } from "./markdown-emit.js";
2
- import remarkGfm from "remark-gfm";
3
- import remarkParse from "remark-parse";
4
- import { unified } from "unified";
5
-
6
- // src/changesets/utils/remark-pipeline.ts
7
- /**
8
- * Stringify an MDAST AST back to markdown text synchronously.
9
- *
10
- * @remarks
11
- * Delegates to the canonical `@effected/markdown` emit boundary
12
- * (`emitMarkdown`), so the output is the kit's canonical form — ATX
13
- * headings, `-` bullets, `*`/`**` emphasis, `***` thematic breaks, one
14
- * blank line between blocks, a single trailing newline. GFM constructs
15
- * (tables, strikethrough) are serialized natively.
16
- *
17
- * @param tree - The MDAST root node
18
- * @returns The serialized markdown string
19
- *
20
- * @example
21
- * ```typescript
22
- * import { parseMarkdown, stringifyMarkdown } from "../utils/remark-pipeline.js";
23
- *
24
- * const tree = parseMarkdown("# Hello\n\nWorld.");
25
- * const md = stringifyMarkdown(tree);
26
- * // "# Hello\n\nWorld.\n"
27
- * ```
28
- *
29
- * @internal
30
- */
31
- function stringifyMarkdown(tree) {
32
- return emitMarkdown(tree);
33
- }
34
-
35
- export { stringifyMarkdown };
@@ -1,107 +0,0 @@
1
- import { toString } from "mdast-util-to-string";
2
-
3
- // src/changesets/utils/version-blocks.ts
4
- /**
5
- * Extract all version blocks from a CHANGELOG AST.
6
- *
7
- * @remarks
8
- * Scans `tree.children` linearly for h2-level headings. Each h2 starts
9
- * a new version block whose content extends to the next h2 or the end
10
- * of the document. The `endIndex` of each block is adjusted in a second
11
- * pass to stop at the next block's `headingIndex`.
12
- *
13
- * h1 headings (typically the package name) are ignored and do not
14
- * create version blocks.
15
- *
16
- * @param tree - The MDAST root node of a CHANGELOG document
17
- * @returns Array of version blocks in document order
18
- *
19
- * @example
20
- * ```typescript
21
- * import { getVersionBlocks } from "../utils/version-blocks.js";
22
- * import { parseMarkdown } from "../utils/remark-pipeline.js";
23
- *
24
- * const tree = parseMarkdown("# my-pkg\n\n## 1.1.0\n\nChanges.\n\n## 1.0.0\n\nInitial.");
25
- * const blocks = getVersionBlocks(tree);
26
- * // blocks.length === 2
27
- * // blocks[0] covers "## 1.1.0" section
28
- * // blocks[1] covers "## 1.0.0" section
29
- * ```
30
- *
31
- * @internal
32
- */
33
- function getVersionBlocks(tree) {
34
- const blocks = [];
35
- for (let i = 0; i < tree.children.length; i++) {
36
- const node = tree.children[i];
37
- if (node.type === "heading" && node.depth === 2) blocks.push({
38
- headingIndex: i,
39
- startIndex: i + 1,
40
- endIndex: tree.children.length
41
- });
42
- }
43
- for (let i = 0; i < blocks.length - 1; i++) blocks[i].endIndex = blocks[i + 1].headingIndex;
44
- return blocks;
45
- }
46
- /**
47
- * Extract h3 sections within a version block.
48
- *
49
- * @remarks
50
- * Iterates over the children of the given version block (from
51
- * `block.startIndex` to `block.endIndex`). Each h3 heading starts
52
- * a new section, and subsequent non-heading nodes are collected
53
- * into that section's `contentNodes` array until the next h3 or
54
- * the end of the block.
55
- *
56
- * Nodes before the first h3 within the block (e.g., a version
57
- * summary paragraph) are not included in any section.
58
- *
59
- * @param tree - The MDAST root node of a CHANGELOG document
60
- * @param block - The version block to extract sections from
61
- * @returns Array of sections in document order
62
- *
63
- * @example
64
- * ```typescript
65
- * import { getVersionBlocks, getBlockSections } from "../utils/version-blocks.js";
66
- * import { parseMarkdown } from "../utils/remark-pipeline.js";
67
- *
68
- * const tree = parseMarkdown("## 1.0.0\n\n### Features\n\n- New API\n\n### Bug Fixes\n\n- Fixed crash");
69
- * const blocks = getVersionBlocks(tree);
70
- * const sections = getBlockSections(tree, blocks[0]);
71
- * // sections.length === 2
72
- * // sections[0].heading text === "Features"
73
- * ```
74
- *
75
- * @internal
76
- */
77
- function getBlockSections(tree, block) {
78
- const sections = [];
79
- for (let i = block.startIndex; i < block.endIndex; i++) {
80
- const node = tree.children[i];
81
- if (node.type === "heading" && node.depth === 3) sections.push({
82
- heading: node,
83
- headingIndex: i,
84
- contentNodes: []
85
- });
86
- else if (sections.length > 0) sections[sections.length - 1].contentNodes.push(node);
87
- }
88
- return sections;
89
- }
90
- /**
91
- * Get the plain text content of a heading node.
92
- *
93
- * @remarks
94
- * Delegates to `mdast-util-to-string` to extract concatenated text
95
- * from all inline children of the heading (handling bold, italic,
96
- * code spans, etc.).
97
- *
98
- * @param heading - The MDAST heading node
99
- * @returns The heading text content as a plain string
100
- *
101
- * @internal
102
- */
103
- function getHeadingText(heading) {
104
- return toString(heading);
105
- }
106
-
107
- export { getBlockSections, getHeadingText, getVersionBlocks };