@savvy-web/changesets 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/remark.d.cts CHANGED
@@ -1,7 +1,54 @@
1
1
  /**
2
- * Consolidated remark plugins for changeset validation and CHANGELOG transformation.
2
+ * \@savvy-web/changesets/remark
3
3
  *
4
- * Re-exports all lint rules and transform plugins from a single entry point.
4
+ * Remark plugins for changeset validation and CHANGELOG transformation.
5
+ *
6
+ * This entry point re-exports all lint rules, transform plugins, and presets
7
+ * from a single import path. The exports are organized into three groups:
8
+ *
9
+ * **Lint rules** validate changeset markdown structure before changelog generation:
10
+ * - {@link HeadingHierarchyRule} (CSH001) -- heading depth and ordering
11
+ * - {@link RequiredSectionsRule} (CSH002) -- recognized section headings
12
+ * - {@link ContentStructureRule} (CSH003) -- non-empty sections, code fences, list items
13
+ * - {@link UncategorizedContentRule} (CSH004) -- content before the first heading
14
+ * - {@link DependencyTableFormatRule} (CSH005) -- dependency table schema compliance
15
+ *
16
+ * **Transform plugins** normalize and clean up generated CHANGELOG markdown:
17
+ * - {@link AggregateDependencyTablesPlugin} -- merge duplicate dependency sections
18
+ * - {@link MergeSectionsPlugin} -- merge duplicate h3 section headings
19
+ * - {@link ReorderSectionsPlugin} -- sort sections by category priority
20
+ * - {@link DeduplicateItemsPlugin} -- remove duplicate list items
21
+ * - {@link ContributorFootnotesPlugin} -- aggregate contributor attributions
22
+ * - {@link IssueLinkRefsPlugin} -- convert inline issue links to reference-style
23
+ * - {@link NormalizeFormatPlugin} -- remove empty sections and lists
24
+ *
25
+ * **Presets** bundle related rules or plugins for convenient consumption:
26
+ * - {@link SilkChangesetPreset} -- all five lint rules
27
+ * - {@link SilkChangesetTransformPreset} -- all seven transform plugins in execution order
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * import {
32
+ * SilkChangesetPreset,
33
+ * SilkChangesetTransformPreset,
34
+ * } from "\@savvy-web/changesets/remark";
35
+ * import remarkParse from "remark-parse";
36
+ * import remarkStringify from "remark-stringify";
37
+ * import { unified } from "unified";
38
+ *
39
+ * // Lint a changeset file
40
+ * const linter = unified().use(remarkParse);
41
+ * for (const rule of SilkChangesetPreset) {
42
+ * linter.use(rule);
43
+ * }
44
+ *
45
+ * // Transform a CHANGELOG
46
+ * const transformer = unified().use(remarkParse);
47
+ * for (const plugin of SilkChangesetTransformPreset) {
48
+ * transformer.use(plugin);
49
+ * }
50
+ * transformer.use(remarkStringify);
51
+ * ```
5
52
  *
6
53
  * @packageDocumentation
7
54
  */
@@ -10,46 +57,24 @@ import { Plugin } from 'unified';
10
57
  import { Plugin as Plugin_2 } from 'unified-lint-rule';
11
58
  import { Root } from 'mdast';
12
59
 
60
+ export declare const AggregateDependencyTablesPlugin: Plugin<[], Root>;
61
+
13
62
  export declare const ContentStructureRule: Plugin_2<Root, unknown>;
14
63
 
15
- /**
16
- * Extract inline contributor attributions and aggregate them into
17
- * a summary paragraph at the end of each version block.
18
- */
19
64
  export declare const ContributorFootnotesPlugin: Plugin<[], Root>;
20
65
 
21
- /**
22
- * Remove duplicate list items within each h3 section.
23
- *
24
- * Items are compared by their plain text content. If a list becomes
25
- * empty after deduplication, it is removed from the tree.
26
- */
27
66
  export declare const DeduplicateItemsPlugin: Plugin<[], Root>;
28
67
 
68
+ export declare const DependencyTableFormatRule: Plugin_2<Root, unknown>;
69
+
29
70
  export declare const HeadingHierarchyRule: Plugin_2<Root, unknown>;
30
71
 
31
- /**
32
- * Convert inline issue links to reference-style links with definitions
33
- * at the end of each version block.
34
- */
35
72
  export declare const IssueLinkRefsPlugin: Plugin<[], Root>;
36
73
 
37
- /**
38
- * Merge duplicate h3 sections within each version block.
39
- *
40
- * Groups sections by heading text (case-insensitive via `fromHeading`),
41
- * keeps the first occurrence, and splices content from duplicates into it.
42
- */
43
74
  export declare const MergeSectionsPlugin: Plugin<[], Root>;
44
75
 
45
- /**
46
- * Remove empty sections and empty lists from the document.
47
- */
48
76
  export declare const NormalizeFormatPlugin: Plugin<[], Root>;
49
77
 
50
- /**
51
- * Reorder h3 sections within each version block by category priority.
52
- */
53
78
  export declare const ReorderSectionsPlugin: Plugin<[], Root>;
54
79
 
55
80
  export declare const RequiredSectionsRule: Plugin_2<Root, unknown>;
@@ -57,37 +82,81 @@ export declare const RequiredSectionsRule: Plugin_2<Root, unknown>;
57
82
  /**
58
83
  * Preset combining all changeset lint rules for convenient consumption.
59
84
  *
85
+ * @remarks
86
+ * Includes the following rules in order:
87
+ *
88
+ * 1. {@link HeadingHierarchyRule} (CSH001) -- no h1, no depth skips
89
+ * 2. {@link RequiredSectionsRule} (CSH002) -- h2 headings must match a known category
90
+ * 3. {@link ContentStructureRule} (CSH003) -- non-empty sections, code fence languages, non-empty list items
91
+ * 4. {@link UncategorizedContentRule} (CSH004) -- no content before the first h2 heading
92
+ * 5. {@link DependencyTableFormatRule} (CSH005) -- dependency table column/value validation
93
+ *
94
+ * Rule execution order is not significant for lint rules; all rules run
95
+ * independently over the same AST and report warnings to the virtual file.
96
+ *
60
97
  * @example
61
98
  * ```typescript
62
99
  * import { SilkChangesetPreset } from "\@savvy-web/changesets/remark";
63
100
  * import remarkParse from "remark-parse";
64
101
  * import { unified } from "unified";
102
+ * import { read } from "to-vfile";
65
103
  *
66
104
  * const processor = unified().use(remarkParse);
67
105
  * for (const rule of SilkChangesetPreset) {
68
106
  * processor.use(rule);
69
107
  * }
108
+ *
109
+ * const file = await read("changeset.md");
110
+ * const result = await processor.process(file);
111
+ * console.log(result.messages); // lint warnings
70
112
  * ```
71
113
  *
114
+ * @see {@link SilkChangesetTransformPreset} for the corresponding transform preset
115
+ *
72
116
  * @public
73
117
  */
74
- export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>];
118
+ export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>];
75
119
 
76
120
  /**
77
121
  * Ordered array of all transform plugins in the correct execution order.
78
122
  *
79
123
  * @remarks
80
- * Plugin ordering is significant:
81
- * 1. `MergeSectionsPlugin` -- merge duplicate h3 headings (must run before reorder)
82
- * 2. `ReorderSectionsPlugin` -- sort sections by category priority
83
- * 3. `DeduplicateItemsPlugin` -- remove duplicate list items
84
- * 4. `ContributorFootnotesPlugin` -- aggregate contributor attributions
85
- * 5. `IssueLinkRefsPlugin` -- convert inline issue links to reference-style
86
- * 6. `NormalizeFormatPlugin` -- final cleanup (remove empty sections/lists)
124
+ * Plugin ordering is significant -- each plugin may depend on the output of
125
+ * earlier plugins in the pipeline:
126
+ *
127
+ * 1. {@link AggregateDependencyTablesPlugin} -- merge duplicate dependency sections (must run first so downstream plugins see a single Dependencies section)
128
+ * 2. {@link MergeSectionsPlugin} -- merge duplicate h3 headings (must run before reorder so that priority is computed on consolidated sections)
129
+ * 3. {@link ReorderSectionsPlugin} -- sort sections by category priority (Breaking Changes first, Other last)
130
+ * 4. {@link DeduplicateItemsPlugin} -- remove duplicate list items within each section
131
+ * 5. {@link ContributorFootnotesPlugin} -- extract inline `Thanks \@user!` attributions and aggregate them into a summary paragraph per version block
132
+ * 6. {@link IssueLinkRefsPlugin} -- convert inline `[#N](url)` links to reference-style `[#N]` with definitions at the end of each version block
133
+ * 7. {@link NormalizeFormatPlugin} -- final cleanup removing empty sections and empty lists
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * import { SilkChangesetTransformPreset } from "\@savvy-web/changesets/remark";
138
+ * import remarkGfm from "remark-gfm";
139
+ * import remarkParse from "remark-parse";
140
+ * import remarkStringify from "remark-stringify";
141
+ * import { unified } from "unified";
142
+ * import { read } from "to-vfile";
143
+ *
144
+ * const processor = unified().use(remarkParse).use(remarkGfm);
145
+ * for (const plugin of SilkChangesetTransformPreset) {
146
+ * processor.use(plugin);
147
+ * }
148
+ * processor.use(remarkStringify);
149
+ *
150
+ * const file = await read("CHANGELOG.md");
151
+ * const result = await processor.process(file);
152
+ * console.log(String(result));
153
+ * ```
154
+ *
155
+ * @see {@link SilkChangesetPreset} for the corresponding lint preset
87
156
  *
88
157
  * @public
89
158
  */
90
- export declare const SilkChangesetTransformPreset: readonly [Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>];
159
+ export declare const SilkChangesetTransformPreset: readonly [Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>];
91
160
 
92
161
  export declare const UncategorizedContentRule: Plugin_2<Root, unknown>;
93
162
 
package/esm/160.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { getInfo } from "@changesets/get-github-info";
2
- import { Layer, unified, Schema, remark_stringify, Effect, GitHubApiError, VersionFilesSchema, remark_gfm, ConfigurationError, Context, remark_parse } from "./795.js";
3
- import { external_mdast_util_to_string_toString } from "./689.js";
2
+ import { Layer, unified, Schema, external_mdast_util_to_string_toString, remark_stringify, Effect, remark_gfm, Context } from "./855.js";
3
+ import { GitHubApiError, VersionFilesSchema, ConfigurationError, remark_parse } from "./725.js";
4
+ import { PositiveInteger, serializeDependencyTableToMarkdown } from "./891.js";
4
5
  import { resolveCommitType, fromHeading } from "./60.js";
5
6
  const REPO_PATTERN = /^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/;
6
7
  const RepoSchema = Schema.String.pipe(Schema.pattern(REPO_PATTERN, {
@@ -87,44 +88,44 @@ const MarkdownLive = Layer.succeed(MarkdownService, {
87
88
  parse: (content)=>Effect.sync(()=>parseMarkdown(content)),
88
89
  stringify: (tree)=>Effect.sync(()=>stringifyMarkdown(tree))
89
90
  });
90
- function logWarning(message, ...args) {
91
- if ("u" > typeof process && process.env.VITEST) return;
92
- if ("u" > typeof process && "true" === process.env.GITHUB_ACTIONS) {
93
- const text = args.length > 0 ? `${message} ${args.join(" ")}` : message;
94
- console.warn(`::warning::${text}`);
95
- } else console.warn(message, ...args);
91
+ const FIELD_MAP = [
92
+ [
93
+ "dependencies",
94
+ "dependency"
95
+ ],
96
+ [
97
+ "devDependencies",
98
+ "devDependency"
99
+ ],
100
+ [
101
+ "peerDependencies",
102
+ "peerDependency"
103
+ ],
104
+ [
105
+ "optionalDependencies",
106
+ "optionalDependency"
107
+ ]
108
+ ];
109
+ function inferDependencyType(dep) {
110
+ const pkg = dep.packageJson;
111
+ for (const [field, type] of FIELD_MAP){
112
+ const section = pkg[field];
113
+ if ("object" == typeof section && null !== section && dep.name in section) return type;
114
+ }
115
+ return "dependency";
96
116
  }
97
- function getDependencyReleaseLine(changesets, dependenciesUpdated, options) {
117
+ function getDependencyReleaseLine(_changesets, dependenciesUpdated, _options) {
98
118
  return Effect.gen(function*() {
99
119
  if (0 === dependenciesUpdated.length) return "";
100
- const github = yield* GitHubService;
101
- let apiFailures = 0;
102
- const totalWithCommit = changesets.filter((cs)=>cs.commit).length;
103
- const commitLinks = yield* Effect.forEach(changesets, (cs)=>{
104
- const commit = cs.commit;
105
- if (!commit) return Effect.succeed(null);
106
- return github.getInfo({
107
- commit,
108
- repo: options.repo
109
- }).pipe(Effect.map((info)=>info.links.commit), Effect.catchAll((error)=>{
110
- apiFailures++;
111
- logWarning(`Failed to fetch GitHub info for commit ${commit}:`, String(error));
112
- return Effect.succeed(`[\`${commit.substring(0, 7)}\`](https://github.com/${options.repo}/commit/${commit})`);
120
+ yield* GitHubService;
121
+ const rows = dependenciesUpdated.map((dep)=>({
122
+ dependency: dep.name,
123
+ type: inferDependencyType(dep),
124
+ action: "updated",
125
+ from: dep.oldVersion,
126
+ to: dep.newVersion
113
127
  }));
114
- }, {
115
- concurrency: 10
116
- });
117
- if (apiFailures > 0) {
118
- const successRate = ((totalWithCommit - apiFailures) / totalWithCommit * 100).toFixed(1);
119
- logWarning(`GitHub API calls completed with ${apiFailures}/${totalWithCommit} failures (${successRate}% success rate)`);
120
- }
121
- const validLinks = commitLinks.filter(Boolean);
122
- const changesetLink = validLinks.length > 0 ? `- Updated dependencies [${validLinks.join(", ")}]:` : "- Updated dependencies:";
123
- const updatedDependenciesList = dependenciesUpdated.map((dep)=>` - ${dep.name}@${dep.newVersion}`);
124
- return [
125
- changesetLink,
126
- ...updatedDependenciesList
127
- ].join("\n");
128
+ return serializeDependencyTableToMarkdown(rows);
128
129
  });
129
130
  }
130
131
  const MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/;
@@ -132,8 +133,6 @@ function extractUrlFromMarkdown(linkOrUrl) {
132
133
  const match = MARKDOWN_LINK_PATTERN.exec(linkOrUrl);
133
134
  return match ? match[2] : linkOrUrl;
134
135
  }
135
- const NonEmptyString = Schema.String.pipe(Schema.minLength(1));
136
- const PositiveInteger = Schema.Number.pipe(Schema.int(), Schema.positive());
137
136
  function isValidUrl(value) {
138
137
  try {
139
138
  new URL(value);
@@ -202,6 +201,13 @@ function parseIssueReferences(commitMessage) {
202
201
  refs: extractIssueNumbers(REFS_ISSUE_PATTERN, commitMessage)
203
202
  };
204
203
  }
204
+ function logWarning(message, ...args) {
205
+ if ("u" > typeof process && process.env.VITEST) return;
206
+ if ("u" > typeof process && "true" === process.env.GITHUB_ACTIONS) {
207
+ const text = args.length > 0 ? `${message} ${args.join(" ")}` : message;
208
+ console.warn(`::warning::${text}`);
209
+ } else console.warn(message, ...args);
210
+ }
205
211
  function parseChangesetSections(summary) {
206
212
  const tree = parseMarkdown(summary);
207
213
  const h2Indices = [];
@@ -368,4 +374,4 @@ const changelogFunctions = {
368
374
  };
369
375
  const changelog = changelogFunctions;
370
376
  export default changelog;
371
- export { ChangesetOptionsSchema, GitHubInfoSchema, GitHubLive, GitHubService, GitHubServiceBase, IssueNumberSchema, MarkdownLive, MarkdownService, MarkdownServiceBase, NonEmptyString, PositiveInteger, RepoSchema, UrlOrMarkdownLinkSchema, UsernameSchema, makeGitHubTest };
377
+ export { ChangesetOptionsSchema, GitHubInfoSchema, GitHubLive, GitHubService, GitHubServiceBase, IssueNumberSchema, MarkdownLive, MarkdownService, MarkdownServiceBase, RepoSchema, UrlOrMarkdownLinkSchema, UsernameSchema, makeGitHubTest };
package/esm/260.js CHANGED
@@ -3,6 +3,7 @@ const RULE_DOCS = {
3
3
  CSH001: `${DOCS_BASE}/CSH001.md`,
4
4
  CSH002: `${DOCS_BASE}/CSH002.md`,
5
5
  CSH003: `${DOCS_BASE}/CSH003.md`,
6
- CSH004: `${DOCS_BASE}/CSH004.md`
6
+ CSH004: `${DOCS_BASE}/CSH004.md`,
7
+ CSH005: `${DOCS_BASE}/CSH005.md`
7
8
  };
8
9
  export { RULE_DOCS };
package/esm/273.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
2
  import { join, relative, resolve } from "node:path";
3
- import { unified, remark_gfm, remark_stringify, remark_parse } from "./795.js";
3
+ import { remark_parse } from "./725.js";
4
+ import { unified, remark_stringify, remark_gfm } from "./855.js";
4
5
  import { UncategorizedContentRule, MergeSectionsPlugin, DeduplicateItemsPlugin, IssueLinkRefsPlugin, ContentStructureRule, HeadingHierarchyRule, NormalizeFormatPlugin, ReorderSectionsPlugin, ContributorFootnotesPlugin, RequiredSectionsRule } from "./622.js";
5
6
  function stripFrontmatter(content) {
6
7
  return content.replace(/^---\n[\s\S]*?\n---\n?/, "");
package/esm/622.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { lintRule } from "unified-lint-rule";
2
2
  import { SKIP, visit } from "unist-util-visit";
3
- import { external_mdast_util_to_string_toString } from "./689.js";
3
+ import { external_mdast_util_to_string_toString } from "./855.js";
4
4
  import { RULE_DOCS } from "./260.js";
5
5
  import { isValidHeading, fromHeading, allHeadings } from "./60.js";
6
6
  const ContentStructureRule = lintRule("remark-lint:changeset-content-structure", (tree, file)=>{
@@ -348,4 +348,4 @@ const ReorderSectionsPlugin = ()=>(tree)=>{
348
348
  tree.children.splice(block.startIndex, blockLength, ...newChildren);
349
349
  }
350
350
  };
351
- export { ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, HeadingHierarchyRule, IssueLinkRefsPlugin, MergeSectionsPlugin, NormalizeFormatPlugin, ReorderSectionsPlugin, RequiredSectionsRule, UncategorizedContentRule };
351
+ export { ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, HeadingHierarchyRule, IssueLinkRefsPlugin, MergeSectionsPlugin, NormalizeFormatPlugin, ReorderSectionsPlugin, RequiredSectionsRule, UncategorizedContentRule, getBlockSections, getHeadingText, getVersionBlocks, lintRule, visit };
@@ -1,4 +1,4 @@
1
- import { Context, Data, Effect, Layer, Schema } from "effect";
1
+ import { Data, Schema } from "./855.js";
2
2
  const ChangesetValidationErrorBase = Data.TaggedError("ChangesetValidationError");
3
3
  class ChangesetValidationError extends ChangesetValidationErrorBase {
4
4
  get message() {
@@ -49,8 +49,5 @@ const VersionFileConfigSchema = Schema.Struct({
49
49
  paths: Schema.optional(Schema.Array(JsonPathSchema))
50
50
  });
51
51
  const VersionFilesSchema = Schema.Array(VersionFileConfigSchema);
52
- export { default as remark_gfm } from "remark-gfm";
53
52
  export { default as remark_parse } from "remark-parse";
54
- export { default as remark_stringify } from "remark-stringify";
55
- export { unified } from "unified";
56
- export { ChangesetValidationError, ChangesetValidationErrorBase, ConfigurationError, ConfigurationErrorBase, Context, Data, Effect, GitHubApiError, GitHubApiErrorBase, JsonPathSchema, Layer, MarkdownParseError, MarkdownParseErrorBase, Schema, VersionFileConfigSchema, VersionFileError, VersionFileErrorBase, VersionFilesSchema };
53
+ export { ChangesetValidationError, ChangesetValidationErrorBase, ConfigurationError, ConfigurationErrorBase, GitHubApiError, GitHubApiErrorBase, JsonPathSchema, MarkdownParseError, MarkdownParseErrorBase, VersionFileConfigSchema, VersionFileError, VersionFileErrorBase, VersionFilesSchema };
package/esm/855.js ADDED
@@ -0,0 +1,5 @@
1
+ export { Context, Data, Effect, Layer, Schema } from "effect";
2
+ export { default as remark_gfm } from "remark-gfm";
3
+ export { default as remark_stringify } from "remark-stringify";
4
+ export { unified } from "unified";
5
+ export { toString as external_mdast_util_to_string_toString } from "mdast-util-to-string";
package/esm/891.js ADDED
@@ -0,0 +1,147 @@
1
+ import { external_mdast_util_to_string_toString, unified, Schema, remark_gfm, remark_stringify } from "./855.js";
2
+ const NonEmptyString = Schema.String.pipe(Schema.minLength(1));
3
+ const PositiveInteger = Schema.Number.pipe(Schema.int(), Schema.positive());
4
+ const DependencyActionSchema = Schema.Literal("added", "updated", "removed");
5
+ const DependencyTableTypeSchema = Schema.Literal("dependency", "devDependency", "peerDependency", "optionalDependency", "workspace", "config");
6
+ const VersionOrEmptySchema = Schema.String.pipe(Schema.pattern(/^(\u2014|[~^]?\d+\.\d+\.\d+(?:[-+.][\w.+-]*)?)$/));
7
+ const DependencyTableRowSchema = Schema.Struct({
8
+ dependency: NonEmptyString,
9
+ type: DependencyTableTypeSchema,
10
+ action: DependencyActionSchema,
11
+ from: VersionOrEmptySchema,
12
+ to: VersionOrEmptySchema
13
+ });
14
+ const DependencyTableSchema = Schema.Array(DependencyTableRowSchema).pipe(Schema.minItems(1));
15
+ const COLUMN_HEADERS = [
16
+ "Dependency",
17
+ "Type",
18
+ "Action",
19
+ "From",
20
+ "To"
21
+ ];
22
+ const COLUMN_KEYS = [
23
+ "dependency",
24
+ "type",
25
+ "action",
26
+ "from",
27
+ "to"
28
+ ];
29
+ const decode = Schema.decodeUnknownSync(DependencyTableRowSchema);
30
+ function parseDependencyTable(table) {
31
+ const rows = table.children;
32
+ if (rows.length < 2) throw new Error("Dependency table must have at least one data row");
33
+ const headerRow = rows[0];
34
+ const headers = headerRow.children.map((cell)=>external_mdast_util_to_string_toString(cell).trim().toLowerCase());
35
+ const expected = COLUMN_HEADERS.map((h)=>h.toLowerCase());
36
+ if (headers.length !== expected.length || !headers.every((h, i)=>h === expected[i])) throw new Error(`Table must have columns: ${COLUMN_HEADERS.join(", ")}. Got: ${headers.join(", ")}`);
37
+ const result = [];
38
+ for(let i = 1; i < rows.length; i++){
39
+ const cells = rows[i].children;
40
+ const raw = {};
41
+ for(let c = 0; c < COLUMN_KEYS.length; c++)raw[COLUMN_KEYS[c]] = external_mdast_util_to_string_toString(cells[c]).trim();
42
+ result.push(decode(raw));
43
+ }
44
+ return result;
45
+ }
46
+ function makeCell(text) {
47
+ return {
48
+ type: "tableCell",
49
+ children: [
50
+ {
51
+ type: "text",
52
+ value: text
53
+ }
54
+ ]
55
+ };
56
+ }
57
+ function makeRow(texts) {
58
+ return {
59
+ type: "tableRow",
60
+ children: texts.map(makeCell)
61
+ };
62
+ }
63
+ function serializeDependencyTable(rows) {
64
+ const headerRow = makeRow([
65
+ ...COLUMN_HEADERS
66
+ ]);
67
+ const dataRows = rows.map((row)=>makeRow(COLUMN_KEYS.map((key)=>row[key])));
68
+ return {
69
+ type: "table",
70
+ children: [
71
+ headerRow,
72
+ ...dataRows
73
+ ]
74
+ };
75
+ }
76
+ function serializeDependencyTableToMarkdown(rows) {
77
+ const table = serializeDependencyTable(rows);
78
+ const tree = {
79
+ type: "root",
80
+ children: [
81
+ table
82
+ ]
83
+ };
84
+ return unified().use(remark_gfm).use(remark_stringify).stringify(tree).trim();
85
+ }
86
+ const ACTION_ORDER = {
87
+ removed: 0,
88
+ updated: 1,
89
+ added: 2
90
+ };
91
+ function collapseDependencyRows(rows) {
92
+ const groups = new Map();
93
+ for (const row of rows){
94
+ const key = `${row.dependency}\0${row.type}`;
95
+ const existing = groups.get(key);
96
+ if (!existing) {
97
+ groups.set(key, {
98
+ ...row
99
+ });
100
+ continue;
101
+ }
102
+ const merged = collapseTwo(existing, row);
103
+ if (null === merged) groups.delete(key);
104
+ else groups.set(key, merged);
105
+ }
106
+ return [
107
+ ...groups.values()
108
+ ];
109
+ }
110
+ function collapseTwo(first, second) {
111
+ const a = first.action;
112
+ const b = second.action;
113
+ if ("updated" === a && "updated" === b) return {
114
+ ...first,
115
+ to: second.to
116
+ };
117
+ if ("added" === a && "updated" === b) return {
118
+ ...first,
119
+ to: second.to
120
+ };
121
+ if ("added" === a && "removed" === b) return null;
122
+ if ("updated" === a && "removed" === b) return {
123
+ ...first,
124
+ action: "removed",
125
+ to: "\u2014"
126
+ };
127
+ if ("removed" === a && "added" === b) return {
128
+ ...first,
129
+ action: "updated",
130
+ to: second.to
131
+ };
132
+ return {
133
+ ...second
134
+ };
135
+ }
136
+ function sortDependencyRows(rows) {
137
+ return [
138
+ ...rows
139
+ ].sort((a, b)=>{
140
+ const actionDiff = (ACTION_ORDER[a.action] ?? 99) - (ACTION_ORDER[b.action] ?? 99);
141
+ if (0 !== actionDiff) return actionDiff;
142
+ const typeDiff = a.type.localeCompare(b.type);
143
+ if (0 !== typeDiff) return typeDiff;
144
+ return a.dependency.localeCompare(b.dependency);
145
+ });
146
+ }
147
+ export { DependencyActionSchema, DependencyTableRowSchema, DependencyTableSchema, DependencyTableTypeSchema, NonEmptyString, PositiveInteger, VersionOrEmptySchema, collapseDependencyRows, parseDependencyTable, serializeDependencyTable, serializeDependencyTableToMarkdown, sortDependencyRows };
@@ -6,7 +6,8 @@ import { applyEdits, modify, parse } from "jsonc-parser";
6
6
  import { findProjectRoot, getWorkspaceInfos } from "workspace-tools";
7
7
  import { globSync } from "tinyglobby";
8
8
  import { readFileSync, ChangelogTransformer, ChangesetLinter, resolve, existsSync, relative, mkdirSync, writeFileSync, join } from "../273.js";
9
- import { VersionFilesSchema, Schema, VersionFileError, Data, Effect } from "../795.js";
9
+ import { Effect, Schema, Data } from "../855.js";
10
+ import { VersionFileError, VersionFilesSchema } from "../725.js";
10
11
  const dirArg = Args.directory({
11
12
  name: "dir"
12
13
  }).pipe(Args.withDefault(".changeset"));
@@ -49,7 +50,8 @@ const RULE_NAMES = [
49
50
  "changeset-heading-hierarchy",
50
51
  "changeset-required-sections",
51
52
  "changeset-content-structure",
52
- "changeset-uncategorized-content"
53
+ "changeset-uncategorized-content",
54
+ "changeset-dependency-table-format"
53
55
  ];
54
56
  const DEFAULT_CONFIG = {
55
57
  $schema: "https://unpkg.com/@changesets/config@3.1.1/schema.json",
@@ -785,7 +787,7 @@ const rootCommand = Command.make("savvy-changesets").pipe(Command.withSubcommand
785
787
  ]));
786
788
  const cli = Command.run(rootCommand, {
787
789
  name: "savvy-changesets",
788
- version: "0.4.2"
790
+ version: "0.5.0"
789
791
  });
790
792
  function runCli() {
791
793
  const main = Effect.suspend(()=>cli(process.argv)).pipe(Effect.provide(NodeContext.layer));
@@ -1,8 +1,22 @@
1
1
  /**
2
- * Changesets API changelog formatter.
2
+ * Changesets API changelog formatter — `\@savvy-web/changesets/changelog`
3
3
  *
4
- * This module exports the `ChangelogFunctions` required by the Changesets API.
5
- * Configure in `.changeset/config.json`:
4
+ * This module is the default export consumed by the Changesets CLI via the
5
+ * `changelog` field in `.changeset/config.json`. It implements the
6
+ * `ChangelogFunctions` interface from `\@changesets/types`, wiring the
7
+ * Effect-based formatting pipeline into the async API that Changesets expects.
8
+ *
9
+ * @remarks
10
+ * The module composes two Effect programs — {@link getReleaseLine} and
11
+ * {@link getDependencyReleaseLine} — and runs each through
12
+ * `Effect.runPromise` with a merged layer of {@link GitHubLive} (for commit
13
+ * metadata) and {@link MarkdownLive} (for mdast parsing). Options are
14
+ * validated at the boundary via `validateChangesetOptions` before being
15
+ * passed to the formatters.
16
+ *
17
+ * ### Configuration
18
+ *
19
+ * Add the following to your `.changeset/config.json`:
6
20
  *
7
21
  * ```json
8
22
  * {
@@ -10,11 +24,46 @@
10
24
  * }
11
25
  * ```
12
26
  *
27
+ * The `repo` option is **required** and must be in `owner/repo` format.
28
+ *
29
+ * ### Pipeline
30
+ *
31
+ * 1. **Options validation** — the raw `options` object from the Changesets
32
+ * config is decoded through `ChangesetOptionsSchema`.
33
+ * 2. **Release line formatting** — each changeset is formatted by
34
+ * `getReleaseLine`, which resolves GitHub metadata, parses sections,
35
+ * and produces structured markdown with commit links and attribution.
36
+ * 3. **Dependency table formatting** — bulk dependency updates are
37
+ * formatted by `getDependencyReleaseLine` into a markdown table.
38
+ *
39
+ * @example Configuring in `.changeset/config.json`
40
+ * ```json
41
+ * {
42
+ * "$schema": "https://unpkg.com/\@changesets/config\@3.1.1/schema.json",
43
+ * "changelog": ["\@savvy-web/changesets/changelog", { "repo": "savvy-web/my-package" }],
44
+ * "commit": false,
45
+ * "access": "public"
46
+ * }
47
+ * ```
48
+ *
49
+ * @see {@link getReleaseLine} in `./getReleaseLine.ts` for individual changeset formatting
50
+ * @see {@link getDependencyReleaseLine} in `./getDependencyReleaseLine.ts` for dependency table formatting
51
+ *
13
52
  * @packageDocumentation
14
53
  */
15
54
 
16
55
  import { ChangelogFunctions } from '@changesets/types';
17
56
 
57
+ /**
58
+ * Changesets API `ChangelogFunctions` implementation.
59
+ *
60
+ * This object satisfies the `ChangelogFunctions` contract from
61
+ * `\@changesets/types`. Each method validates options, runs the
62
+ * corresponding Effect program with the merged service layer, and
63
+ * returns a `Promise<string>`.
64
+ *
65
+ * @internal
66
+ */
18
67
  declare const changelogFunctions: ChangelogFunctions;
19
68
  export default changelogFunctions;
20
69