@savvy-web/silk-effects 0.6.1 → 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,47 @@
1
+ import { getVersionBlocks } from "../../utils/version-blocks.js";
2
+ import { toString } from "mdast-util-to-string";
3
+
4
+ //#region 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
+ //#endregion
47
+ export { NormalizeFormatPlugin };
@@ -0,0 +1,34 @@
1
+ import { fromHeading } from "../../categories/index.js";
2
+ import { getBlockSections, getHeadingText, getVersionBlocks } from "../../utils/version-blocks.js";
3
+
4
+ //#region 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
+ const ReorderSectionsPlugin = () => {
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 sections = getBlockSections(tree, block);
17
+ if (sections.length <= 1) continue;
18
+ const preamble = [];
19
+ for (let i = block.startIndex; i < block.endIndex; i++) if (i < sections[0].headingIndex) preamble.push(tree.children[i]);
20
+ else break;
21
+ const sorted = [...sections].sort((a, b) => {
22
+ return (fromHeading(getHeadingText(a.heading))?.priority ?? UNKNOWN_PRIORITY) - (fromHeading(getHeadingText(b.heading))?.priority ?? UNKNOWN_PRIORITY);
23
+ });
24
+ if (sorted.every((s, i) => s.headingIndex === sections[i].headingIndex)) continue;
25
+ const newChildren = [...preamble];
26
+ for (const section of sorted) newChildren.push(section.heading, ...section.contentNodes);
27
+ const blockLength = block.endIndex - block.startIndex;
28
+ tree.children.splice(block.startIndex, blockLength, ...newChildren);
29
+ }
30
+ };
31
+ };
32
+
33
+ //#endregion
34
+ export { ReorderSectionsPlugin };
@@ -0,0 +1,119 @@
1
+ import { ContentStructureRule } from "./rules/content-structure.js";
2
+ import { HeadingHierarchyRule } from "./rules/heading-hierarchy.js";
3
+ import { RequiredSectionsRule } from "./rules/required-sections.js";
4
+ import { UncategorizedContentRule } from "./rules/uncategorized-content.js";
5
+ import { ContributorFootnotesPlugin } from "./plugins/contributor-footnotes.js";
6
+ import { DeduplicateItemsPlugin } from "./plugins/deduplicate-items.js";
7
+ import { IssueLinkRefsPlugin } from "./plugins/issue-link-refs.js";
8
+ import { MergeSectionsPlugin } from "./plugins/merge-sections.js";
9
+ import { NormalizeFormatPlugin } from "./plugins/normalize-format.js";
10
+ import { ReorderSectionsPlugin } from "./plugins/reorder-sections.js";
11
+ import { AggregateDependencyTablesPlugin } from "./plugins/aggregate-dependency-tables.js";
12
+ import { DependencyTableFormatRule } from "./rules/dependency-table-format.js";
13
+
14
+ //#region 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
+ //#endregion
119
+ export { SilkChangesetPreset, SilkChangesetTransformPreset };
@@ -0,0 +1,22 @@
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
+ //#region 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
+ //#endregion
22
+ export { ContentStructureRule };
@@ -0,0 +1,40 @@
1
+ import { parseDependencyTable } from "../../utils/dependency-table.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
+ //#region src/changesets/remark/rules/dependency-table-format.ts
8
+ /** @internal */
9
+ const EM_DASH = "—";
10
+ const DependencyTableFormatRule = lintRule("remark-lint:changeset-dependency-table-format", (tree, file) => {
11
+ visit(tree, "heading", (node, index) => {
12
+ if (node.depth !== 2) return;
13
+ if (toString(node).toLowerCase() !== "dependencies") return;
14
+ if (index === void 0) return;
15
+ const content = [];
16
+ for (let i = index + 1; i < tree.children.length; i++) {
17
+ const child = tree.children[i];
18
+ if (child.type === "heading") break;
19
+ content.push(child);
20
+ }
21
+ const tables = content.filter((n) => n.type === "table");
22
+ if (tables.length === 0) {
23
+ file.message(`Dependencies section must contain a table, not a list or paragraph. See: ${RULE_DOCS.CSH005}`, node);
24
+ return;
25
+ }
26
+ const table = tables[0];
27
+ try {
28
+ const rows = parseDependencyTable(table);
29
+ for (const row of rows) {
30
+ 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);
31
+ 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);
32
+ }
33
+ } catch (error) {
34
+ file.message(`${error instanceof Error ? error.message : String(error)}. See: ${RULE_DOCS.CSH005}`, table);
35
+ }
36
+ });
37
+ });
38
+
39
+ //#endregion
40
+ export { DependencyTableFormatRule };
@@ -0,0 +1,19 @@
1
+ import { RULE_DOCS } from "../../constants.js";
2
+ import { lintRule } from "unified-lint-rule";
3
+ import { visit } from "unist-util-visit";
4
+
5
+ //#region 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
+ //#endregion
19
+ export { HeadingHierarchyRule };
@@ -0,0 +1,17 @@
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
+ //#region 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
+ //#endregion
17
+ export { RequiredSectionsRule };
@@ -0,0 +1,31 @@
1
+ import { RULE_DOCS } from "../../constants.js";
2
+ import { lintRule } from "unified-lint-rule";
3
+
4
+ //#region 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 = 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
+ //#endregion
31
+ export { UncategorizedContentRule };
@@ -0,0 +1,146 @@
1
+ import { NonEmptyString } from "./primitives.js";
2
+ import { CommitHashSchema } from "./git.js";
3
+ import { Schema } from "effect";
4
+
5
+ //#region src/changesets/schemas/changeset.ts
6
+ /**
7
+ * Effect schemas for changeset entries and dependency updates.
8
+ *
9
+ * @remarks
10
+ * These schemas define the canonical representation of changeset data
11
+ * consumed by the changelog formatter (Layer 2) and validated by the
12
+ * remark-lint pre-validation layer (Layer 1). They enforce constraints
13
+ * such as summary length limits and valid dependency types at system
14
+ * boundaries.
15
+ *
16
+ * @see {@link https://github.com/changesets/changesets | Changesets documentation}
17
+ * @see {@link https://effect.website/docs/schema/introduction | Effect Schema documentation}
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+ /**
22
+ * Schema for a changeset summary (1--1000 characters).
23
+ *
24
+ * @remarks
25
+ * Enforces that every changeset has a non-empty summary and caps length
26
+ * at 1000 characters. Longer descriptions should go in the changeset body,
27
+ * not the summary line. Validation messages guide users toward correct usage.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * import { Schema } from "effect";
32
+ * import { ChangesetSummarySchema } from "@savvy-web/changesets";
33
+ *
34
+ * // Succeeds — valid summary
35
+ * const summary = Schema.decodeUnknownSync(ChangesetSummarySchema)(
36
+ * "Fix authentication timeout in login flow"
37
+ * );
38
+ *
39
+ * // Throws ParseError — empty string
40
+ * Schema.decodeUnknownSync(ChangesetSummarySchema)("");
41
+ * ```
42
+ *
43
+ * @public
44
+ */
45
+ const ChangesetSummarySchema = Schema.String.pipe(Schema.minLength(1, { message: () => "Changeset summary cannot be empty. Provide a 1-1000 character description of the change (e.g., \"Fix authentication timeout in login flow\")" }), Schema.maxLength(1e3, { message: () => "Changeset summary exceeds the 1000 character limit. Shorten the summary to at most 1000 characters — use the changeset body for additional details" }));
46
+ /**
47
+ * Schema for a changeset object.
48
+ *
49
+ * @remarks
50
+ * Represents a single changeset entry as consumed by the changelog formatter.
51
+ * The `summary` is the human-readable description, `id` is a unique identifier
52
+ * (typically the changeset filename without extension), and `commit` is the
53
+ * optional git SHA that introduced the changeset.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { Schema } from "effect";
58
+ * import { ChangesetSchema } from "@savvy-web/changesets";
59
+ * import type { Changeset } from "@savvy-web/changesets";
60
+ *
61
+ * const changeset: Changeset = Schema.decodeUnknownSync(ChangesetSchema)({
62
+ * summary: "Add retry logic to API client",
63
+ * id: "brave-dogs-laugh",
64
+ * commit: "a1b2c3d",
65
+ * });
66
+ * ```
67
+ *
68
+ * @see {@link Changeset} for the inferred TypeScript type
69
+ * @see {@link ChangesetSummarySchema} for summary validation rules
70
+ * @see {@link CommitHashSchema} for commit hash format requirements
71
+ *
72
+ * @public
73
+ */
74
+ const ChangesetSchema = Schema.Struct({
75
+ /** The changeset summary text. */
76
+ summary: ChangesetSummarySchema,
77
+ /** Unique changeset identifier. */
78
+ id: Schema.String,
79
+ /** Git commit hash associated with this changeset. */
80
+ commit: Schema.optional(CommitHashSchema)
81
+ });
82
+ /**
83
+ * Schema for npm dependency types.
84
+ *
85
+ * @remarks
86
+ * Represents the four standard `package.json` dependency fields using their
87
+ * plural key names as they appear in the manifest. For the singular,
88
+ * table-oriented variant that includes `workspace` and `config` types,
89
+ * see {@link DependencyTableTypeSchema}.
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * import { Schema } from "effect";
94
+ * import { DependencyTypeSchema } from "@savvy-web/changesets";
95
+ * import type { DependencyType } from "@savvy-web/changesets";
96
+ *
97
+ * const depType: DependencyType = Schema.decodeUnknownSync(DependencyTypeSchema)(
98
+ * "devDependencies"
99
+ * );
100
+ * ```
101
+ *
102
+ * @see {@link DependencyTableTypeSchema} for the extended singular-form variant
103
+ *
104
+ * @public
105
+ */
106
+ const DependencyTypeSchema = Schema.Literal("dependencies", "devDependencies", "peerDependencies", "optionalDependencies");
107
+ /**
108
+ * Schema for a dependency update entry.
109
+ *
110
+ * @remarks
111
+ * Represents a single dependency version change as reported by
112
+ * the Changesets API. Captures the package name, which dependency
113
+ * field it belongs to, and the old and new version strings.
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * import { Schema } from "effect";
118
+ * import { DependencyUpdateSchema } from "@savvy-web/changesets";
119
+ * import type { DependencyUpdate } from "@savvy-web/changesets";
120
+ *
121
+ * const update: DependencyUpdate = Schema.decodeUnknownSync(DependencyUpdateSchema)({
122
+ * name: "effect",
123
+ * type: "dependencies",
124
+ * oldVersion: "3.18.0",
125
+ * newVersion: "3.19.1",
126
+ * });
127
+ * ```
128
+ *
129
+ * @see {@link DependencyUpdate} for the inferred TypeScript type
130
+ * @see {@link DependencyTableRowSchema} for the table-formatted variant
131
+ *
132
+ * @public
133
+ */
134
+ const DependencyUpdateSchema = Schema.Struct({
135
+ /** Package name (must be non-empty). */
136
+ name: NonEmptyString.annotations({ message: () => "Package name cannot be empty" }),
137
+ /** npm dependency type. */
138
+ type: DependencyTypeSchema,
139
+ /** Previous version string. */
140
+ oldVersion: Schema.String,
141
+ /** New version string. */
142
+ newVersion: Schema.String
143
+ });
144
+
145
+ //#endregion
146
+ export { ChangesetSchema, ChangesetSummarySchema, DependencyTypeSchema, DependencyUpdateSchema };
@@ -0,0 +1,189 @@
1
+ import { NonEmptyString } from "./primitives.js";
2
+ import { Schema } from "effect";
3
+
4
+ //#region src/changesets/schemas/dependency-table.ts
5
+ /**
6
+ * Effect schemas for structured dependency table entries.
7
+ *
8
+ * @remarks
9
+ * These schemas define the canonical representation for dependency
10
+ * changes tracked as markdown tables in changeset files and CHANGELOGs.
11
+ * The table format provides a richer view than {@link DependencyUpdateSchema}
12
+ * by supporting singular dependency type names, workspace/config types,
13
+ * and explicit add/update/remove actions.
14
+ *
15
+ * A dependency table row looks like this in a CHANGELOG:
16
+ *
17
+ * | Dependency | Type | Action | From | To |
18
+ * |---|---|---|---|---|
19
+ * | effect | dependency | updated | 3.18.0 | 3.19.1 |
20
+ *
21
+ * @see {@link DependencyUpdateSchema} for the simpler Changesets API format
22
+ * @see {@link https://effect.website/docs/schema/introduction | Effect Schema documentation}
23
+ *
24
+ * @packageDocumentation
25
+ */
26
+ /**
27
+ * Valid dependency table actions.
28
+ *
29
+ * @remarks
30
+ * Represents the three possible operations on a dependency: `"added"` for
31
+ * new dependencies, `"updated"` for version changes, and `"removed"` for
32
+ * deletions. Used in the "Action" column of dependency tables.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { Schema } from "effect";
37
+ * import { DependencyActionSchema } from "@savvy-web/changesets";
38
+ * import type { DependencyAction } from "@savvy-web/changesets";
39
+ *
40
+ * const action: DependencyAction = Schema.decodeUnknownSync(DependencyActionSchema)("updated");
41
+ * ```
42
+ *
43
+ * @public
44
+ */
45
+ const DependencyActionSchema = Schema.Literal("added", "updated", "removed");
46
+ /**
47
+ * Extended dependency types for table format.
48
+ *
49
+ * @remarks
50
+ * Unlike {@link DependencyTypeSchema} (which uses plural npm field names like
51
+ * `"dependencies"`), this schema uses singular forms (`"dependency"`) and adds
52
+ * two additional types: `"workspace"` for monorepo workspace references and
53
+ * `"config"` for configuration toolchain updates (e.g., ESLint, TypeScript).
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { Schema } from "effect";
58
+ * import { DependencyTableTypeSchema } from "@savvy-web/changesets";
59
+ * import type { DependencyTableType } from "@savvy-web/changesets";
60
+ *
61
+ * const tableType: DependencyTableType = Schema.decodeUnknownSync(
62
+ * DependencyTableTypeSchema
63
+ * )("workspace");
64
+ * ```
65
+ *
66
+ * @see {@link DependencyTypeSchema} for the plural npm-field variant
67
+ *
68
+ * @public
69
+ */
70
+ const DependencyTableTypeSchema = Schema.Literal("dependency", "devDependency", "peerDependency", "optionalDependency", "workspace", "config");
71
+ /**
72
+ * Version string or em dash (U+2014) sentinel for added/removed entries.
73
+ *
74
+ * @remarks
75
+ * Accepts either a semver-like version string (with optional `~` or `^`
76
+ * prefix and pre-release/build suffixes) or the em dash character `\u2014`
77
+ * which serves as a sentinel: the "From" column uses `\u2014` for newly
78
+ * added dependencies, and the "To" column uses it for removed ones.
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * import { Schema } from "effect";
83
+ * import { VersionOrEmptySchema } from "@savvy-web/changesets";
84
+ *
85
+ * // Succeeds — semver version
86
+ * Schema.decodeUnknownSync(VersionOrEmptySchema)("^3.19.1");
87
+ *
88
+ * // Succeeds — em dash sentinel for "no version"
89
+ * Schema.decodeUnknownSync(VersionOrEmptySchema)("\u2014");
90
+ *
91
+ * // Throws ParseError — arbitrary text
92
+ * Schema.decodeUnknownSync(VersionOrEmptySchema)("latest");
93
+ * ```
94
+ *
95
+ * @public
96
+ */
97
+ const VersionOrEmptySchema = Schema.String.pipe(Schema.pattern(/^(\u2014|[~^]?\d+\.\d+\.\d+(?:[-+.][\w.+-]*)?)$/));
98
+ /**
99
+ * Schema for a single dependency table row.
100
+ *
101
+ * @remarks
102
+ * Represents one row of a dependency update table in a CHANGELOG.
103
+ * Each row captures the dependency name, its type, the change action,
104
+ * and the "from" and "to" version strings. For added dependencies the
105
+ * `from` field is an em dash; for removed dependencies the `to` field
106
+ * is an em dash.
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * import { Schema } from "effect";
111
+ * import { DependencyTableRowSchema } from "@savvy-web/changesets";
112
+ * import type { DependencyTableRow } from "@savvy-web/changesets";
113
+ *
114
+ * const row: DependencyTableRow = Schema.decodeUnknownSync(DependencyTableRowSchema)({
115
+ * dependency: "effect",
116
+ * type: "dependency",
117
+ * action: "updated",
118
+ * from: "3.18.0",
119
+ * to: "3.19.1",
120
+ * });
121
+ *
122
+ * // Newly added dependency — "from" is em dash
123
+ * const addedRow: DependencyTableRow = Schema.decodeUnknownSync(DependencyTableRowSchema)({
124
+ * dependency: "@effect/cli",
125
+ * type: "dependency",
126
+ * action: "added",
127
+ * from: "\u2014",
128
+ * to: "0.50.0",
129
+ * });
130
+ * ```
131
+ *
132
+ * @see {@link DependencyTableRow} for the inferred TypeScript type
133
+ * @see {@link DependencyTableSchema} for a non-empty array of rows
134
+ *
135
+ * @public
136
+ */
137
+ const DependencyTableRowSchema = Schema.Struct({
138
+ /** Package or toolchain name. */
139
+ dependency: NonEmptyString,
140
+ /** Dependency type. */
141
+ type: DependencyTableTypeSchema,
142
+ /** Change action. */
143
+ action: DependencyActionSchema,
144
+ /** Previous version (em dash for added). */
145
+ from: VersionOrEmptySchema,
146
+ /** New version (em dash for removed). */
147
+ to: VersionOrEmptySchema
148
+ });
149
+ /**
150
+ * Schema for a dependency table (non-empty array of rows).
151
+ *
152
+ * @remarks
153
+ * Validates that the table contains at least one row. Used to represent
154
+ * the full dependency update table in a changeset or CHANGELOG entry.
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * import { Schema } from "effect";
159
+ * import { DependencyTableSchema } from "@savvy-web/changesets";
160
+ *
161
+ * const table = Schema.decodeUnknownSync(DependencyTableSchema)([
162
+ * {
163
+ * dependency: "effect",
164
+ * type: "dependency",
165
+ * action: "updated",
166
+ * from: "3.18.0",
167
+ * to: "3.19.1",
168
+ * },
169
+ * {
170
+ * dependency: "typescript",
171
+ * type: "config",
172
+ * action: "updated",
173
+ * from: "5.6.0",
174
+ * to: "5.7.2",
175
+ * },
176
+ * ]);
177
+ *
178
+ * // Throws ParseError — empty array
179
+ * Schema.decodeUnknownSync(DependencyTableSchema)([]);
180
+ * ```
181
+ *
182
+ * @see {@link DependencyTableRowSchema} for the individual row schema
183
+ *
184
+ * @public
185
+ */
186
+ const DependencyTableSchema = Schema.Array(DependencyTableRowSchema).pipe(Schema.minItems(1));
187
+
188
+ //#endregion
189
+ export { DependencyActionSchema, DependencyTableRowSchema, DependencyTableSchema, DependencyTableTypeSchema, VersionOrEmptySchema };