@savvy-web/changesets 0.2.1 → 0.4.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/README.md +17 -59
- package/cjs/changelog.cjs +2 -2
- package/cjs/index.cjs +32 -12
- package/cjs/index.d.cts +3 -3
- package/cjs/markdownlint.cjs +41 -7
- package/cjs/markdownlint.d.cts +9 -0
- package/cjs/remark.cjs +32 -8
- package/cjs/remark.d.cts +3 -1
- package/esm/160.js +2 -2
- package/esm/260.js +8 -0
- package/esm/273.js +2 -2
- package/esm/{234.js → 622.js} +21 -7
- package/esm/bin/savvy-changesets.js +41 -14
- package/esm/index.d.ts +3 -3
- package/esm/index.js +3 -3
- package/esm/markdownlint.d.ts +9 -0
- package/esm/markdownlint.js +33 -8
- package/esm/remark.d.ts +3 -1
- package/esm/remark.js +4 -3
- package/package.json +97 -97
package/README.md
CHANGED
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
[![npm version][npm-badge]][npm-url]
|
|
4
4
|
[![License: MIT][license-badge]][license-url]
|
|
5
|
+
[![Node.js >= 24][node-badge]][node-url]
|
|
5
6
|
|
|
6
7
|
Custom changelog formatter and markdown processing pipeline for the Silk Suite. Replaces the default `@changesets/cli/changelog` formatter with a three-layer architecture that validates changeset files, formats structured changelog entries, and post-processes the generated CHANGELOG.md.
|
|
7
8
|
|
|
8
9
|
## Features
|
|
9
10
|
|
|
10
|
-
- **Section-aware changesets** --
|
|
11
|
+
- **Section-aware changesets** -- Categorize changes with h2 headings (Features, Bug Fixes, Breaking Changes, etc.)
|
|
11
12
|
- **Three-layer pipeline** -- Pre-validation (remark-lint), changelog formatting (Changesets API), and post-processing (remark-transform)
|
|
12
|
-
- **
|
|
13
|
-
- **CLI tooling** -- `savvy-changesets` binary with init, lint, transform, check, and version subcommands for CI and local use
|
|
13
|
+
- **CLI tooling** -- `savvy-changesets` binary with init, lint, check, transform, and version subcommands
|
|
14
14
|
- **GitHub integration** -- Automatic PR links, commit references, and contributor attribution
|
|
15
|
-
- **Version file syncing** -- Bump version fields in additional JSON files
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
15
|
+
- **Version file syncing** -- Bump version fields in additional JSON files using glob patterns and JSONPath expressions
|
|
16
|
+
- **Editor support** -- markdownlint rules for real-time validation in VS Code and CI
|
|
17
|
+
- **AI-agent-friendly errors** -- All lint and validation errors include inline fix instructions and documentation links, so AI agents can resolve issues without examining source code
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
|
|
22
|
+
npm install @savvy-web/changesets -D
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Quick Start
|
|
@@ -30,7 +30,7 @@ Bootstrap your repository:
|
|
|
30
30
|
savvy-changesets init
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
This creates `.changeset/config.json` with auto-detected GitHub repo settings. Or configure manually:
|
|
33
|
+
This creates `.changeset/config.json` with auto-detected GitHub repo settings and configures markdownlint rules. Or configure manually:
|
|
34
34
|
|
|
35
35
|
```json
|
|
36
36
|
{
|
|
@@ -41,7 +41,7 @@ This creates `.changeset/config.json` with auto-detected GitHub repo settings. O
|
|
|
41
41
|
}
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Write
|
|
44
|
+
Write section-aware changeset files:
|
|
45
45
|
|
|
46
46
|
```markdown
|
|
47
47
|
---
|
|
@@ -58,58 +58,14 @@ Added a new authentication system with OAuth2 support.
|
|
|
58
58
|
- Updated integration test fixtures
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
## Version File Syncing
|
|
62
|
-
|
|
63
|
-
If your project has JSON files beyond `package.json` that contain version fields (e.g., `plugin.json`, `marketplace.json`), add `versionFiles` to your changelog options to keep them in sync during `changeset version`:
|
|
64
|
-
|
|
65
|
-
```json
|
|
66
|
-
{
|
|
67
|
-
"changelog": ["@savvy-web/changesets/changelog", {
|
|
68
|
-
"repo": "owner/repo",
|
|
69
|
-
"versionFiles": [
|
|
70
|
-
{ "glob": "plugin.json" },
|
|
71
|
-
{ "glob": ".claude-plugin/marketplace.json", "paths": ["$.metadata.version", "$.plugins[*].version"] }
|
|
72
|
-
]
|
|
73
|
-
}]
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
When `paths` is omitted it defaults to `["$.version"]`. In monorepos, each matched file inherits the version from its nearest workspace package. See [Configuration -- versionFiles](./docs/configuration.md#versionfiles-optional) for full details including supported JSONPath syntax.
|
|
78
|
-
|
|
79
|
-
## markdownlint Integration
|
|
80
|
-
|
|
81
|
-
Register the custom rules in your base config (e.g., `lib/configs/.markdownlint-cli2.jsonc`):
|
|
82
|
-
|
|
83
|
-
```jsonc
|
|
84
|
-
{
|
|
85
|
-
"customRules": [
|
|
86
|
-
"@savvy-web/changesets/markdownlint"
|
|
87
|
-
],
|
|
88
|
-
"config": {
|
|
89
|
-
"changeset-heading-hierarchy": false,
|
|
90
|
-
"changeset-required-sections": false,
|
|
91
|
-
"changeset-content-structure": false
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Then enable the rules only for changeset files by creating `.changeset/.markdownlint.json`:
|
|
97
|
-
|
|
98
|
-
```json
|
|
99
|
-
{
|
|
100
|
-
"extends": "../lib/configs/.markdownlint-cli2.jsonc",
|
|
101
|
-
"default": false,
|
|
102
|
-
"changeset-heading-hierarchy": true,
|
|
103
|
-
"changeset-required-sections": true,
|
|
104
|
-
"changeset-content-structure": true,
|
|
105
|
-
"MD041": false
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
61
|
## Documentation
|
|
110
62
|
|
|
111
|
-
- [Section-Aware Pipeline](./docs/section-aware-pipeline.md) -- End-to-end walkthrough of how section-aware changesets flow through the
|
|
112
|
-
- [
|
|
63
|
+
- [Section-Aware Pipeline](./docs/section-aware-pipeline.md) -- End-to-end walkthrough of how section-aware changesets flow through the pipeline
|
|
64
|
+
- [Configuration](./docs/configuration.md) -- Options, version files, markdownlint integration, CI scripts
|
|
65
|
+
- [CLI Reference](./docs/cli.md) -- All commands and options
|
|
66
|
+
- [API Reference](./docs/api.md) -- Classes, types, Effect services, remark plugins
|
|
67
|
+
- [Architecture](./docs/architecture.md) -- Three-layer pipeline design and export map
|
|
68
|
+
- [Markdownlint Rule Docs](./docs/rules/) -- Per-rule documentation with examples, fix instructions, and rationale
|
|
113
69
|
|
|
114
70
|
## License
|
|
115
71
|
|
|
@@ -119,3 +75,5 @@ MIT
|
|
|
119
75
|
[npm-url]: https://www.npmjs.com/package/@savvy-web/changesets
|
|
120
76
|
[license-badge]: https://img.shields.io/badge/License-MIT-yellow.svg
|
|
121
77
|
[license-url]: https://opensource.org/licenses/MIT
|
|
78
|
+
[node-badge]: https://img.shields.io/badge/node-%3E%3D24-brightgreen
|
|
79
|
+
[node-url]: https://nodejs.org/
|
package/cjs/changelog.cjs
CHANGED
|
@@ -181,7 +181,7 @@ var __webpack_modules__ = {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
const UsernameSchema = effect__rspack_import_0.Schema.String.pipe(effect__rspack_import_0.Schema.pattern(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/, {
|
|
184
|
-
message: ()=>
|
|
184
|
+
message: ()=>'Invalid GitHub username format. Usernames must contain only alphanumeric characters and hyphens, and cannot start or end with a hyphen. Example: "octocat" or "my-user-123"'
|
|
185
185
|
}));
|
|
186
186
|
const IssueNumberSchema = _primitives_js__rspack_import_1.e.annotations({
|
|
187
187
|
title: "IssueNumber",
|
|
@@ -192,7 +192,7 @@ var __webpack_modules__ = {
|
|
|
192
192
|
const match = _utils_markdown_link_js__rspack_import_2.o.exec(value);
|
|
193
193
|
return match?.[2] ? isValidUrl(match[2]) : false;
|
|
194
194
|
}, {
|
|
195
|
-
message: ()=>
|
|
195
|
+
message: ()=>'Value must be a valid URL or a markdown link. Expected a plain URL (e.g., "https://github.com/owner/repo/pull/42") or a markdown link (e.g., "[#42](https://github.com/owner/repo/pull/42)")'
|
|
196
196
|
}));
|
|
197
197
|
const GitHubInfoSchema = effect__rspack_import_0.Schema.Struct({
|
|
198
198
|
user: effect__rspack_import_0.Schema.optional(UsernameSchema),
|
package/cjs/index.cjs
CHANGED
|
@@ -490,7 +490,7 @@ var __webpack_modules__ = {
|
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
492
|
const UsernameSchema = effect__rspack_import_0.Schema.String.pipe(effect__rspack_import_0.Schema.pattern(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/, {
|
|
493
|
-
message: ()=>
|
|
493
|
+
message: ()=>'Invalid GitHub username format. Usernames must contain only alphanumeric characters and hyphens, and cannot start or end with a hyphen. Example: "octocat" or "my-user-123"'
|
|
494
494
|
}));
|
|
495
495
|
const IssueNumberSchema = _primitives_js__rspack_import_1.e.annotations({
|
|
496
496
|
title: "IssueNumber",
|
|
@@ -501,7 +501,7 @@ var __webpack_modules__ = {
|
|
|
501
501
|
const match = _utils_markdown_link_js__rspack_import_2.o.exec(value);
|
|
502
502
|
return match?.[2] ? isValidUrl(match[2]) : false;
|
|
503
503
|
}, {
|
|
504
|
-
message: ()=>
|
|
504
|
+
message: ()=>'Value must be a valid URL or a markdown link. Expected a plain URL (e.g., "https://github.com/owner/repo/pull/42") or a markdown link (e.g., "[#42](https://github.com/owner/repo/pull/42)")'
|
|
505
505
|
}));
|
|
506
506
|
const GitHubInfoSchema = effect__rspack_import_0.Schema.Struct({
|
|
507
507
|
user: effect__rspack_import_0.Schema.optional(UsernameSchema),
|
|
@@ -830,25 +830,32 @@ var __webpack_exports__ = {};
|
|
|
830
830
|
var external_mdast_util_to_string_ = __webpack_require__("mdast-util-to-string");
|
|
831
831
|
const external_unified_lint_rule_namespaceObject = require("unified-lint-rule");
|
|
832
832
|
const external_unist_util_visit_namespaceObject = require("unist-util-visit");
|
|
833
|
+
const DOCS_BASE = "https://github.com/savvy-web/changesets/blob/main/docs/rules";
|
|
834
|
+
const RULE_DOCS = {
|
|
835
|
+
CSH001: `${DOCS_BASE}/CSH001.md`,
|
|
836
|
+
CSH002: `${DOCS_BASE}/CSH002.md`,
|
|
837
|
+
CSH003: `${DOCS_BASE}/CSH003.md`,
|
|
838
|
+
CSH004: `${DOCS_BASE}/CSH004.md`
|
|
839
|
+
};
|
|
833
840
|
const ContentStructureRule = (0, external_unified_lint_rule_namespaceObject.lintRule)("remark-lint:changeset-content-structure", (tree, file)=>{
|
|
834
841
|
(0, external_unist_util_visit_namespaceObject.visit)(tree, "heading", (node, index, parent)=>{
|
|
835
842
|
if (2 !== node.depth || null == parent || null == index) return;
|
|
836
843
|
const next = parent.children[index + 1];
|
|
837
|
-
if (!next || "heading" === next.type && 2 === next.depth) file.message(
|
|
844
|
+
if (!next || "heading" === next.type && 2 === next.depth) 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);
|
|
838
845
|
});
|
|
839
846
|
(0, external_unist_util_visit_namespaceObject.visit)(tree, "code", (node)=>{
|
|
840
|
-
if (!node.lang) file.message(
|
|
847
|
+
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);
|
|
841
848
|
});
|
|
842
849
|
(0, external_unist_util_visit_namespaceObject.visit)(tree, "listItem", (node)=>{
|
|
843
850
|
const text = (0, external_mdast_util_to_string_.toString)(node).trim();
|
|
844
|
-
if (!text) file.message(
|
|
851
|
+
if (!text) file.message(`Empty list item. Each list item must contain descriptive text (e.g., "- Fixed login timeout issue"). See: ${RULE_DOCS.CSH003}`, node);
|
|
845
852
|
});
|
|
846
853
|
});
|
|
847
854
|
const HeadingHierarchyRule = (0, external_unified_lint_rule_namespaceObject.lintRule)("remark-lint:changeset-heading-hierarchy", (tree, file)=>{
|
|
848
855
|
let prevDepth = 0;
|
|
849
856
|
(0, external_unist_util_visit_namespaceObject.visit)(tree, "heading", (node)=>{
|
|
850
|
-
if (1 === node.depth) return void file.message(
|
|
851
|
-
if (prevDepth > 0 && node.depth > prevDepth + 1) file.message(`Heading level skipped: expected h${prevDepth + 1} or lower, found h${node.depth}`, node);
|
|
857
|
+
if (1 === node.depth) return void 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);
|
|
858
|
+
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);
|
|
852
859
|
prevDepth = node.depth;
|
|
853
860
|
});
|
|
854
861
|
});
|
|
@@ -856,9 +863,22 @@ var __webpack_exports__ = {};
|
|
|
856
863
|
(0, external_unist_util_visit_namespaceObject.visit)(tree, "heading", (node)=>{
|
|
857
864
|
if (2 !== node.depth) return;
|
|
858
865
|
const text = (0, external_mdast_util_to_string_.toString)(node);
|
|
859
|
-
if (!(0, categories.Lr)(text)) file.message(`Unknown section "${text}". Valid
|
|
866
|
+
if (!(0, categories.Lr)(text)) file.message(`Unknown section "${text}". Valid h2 headings are: ${(0, categories.Rr)().join(", ")}. Heading comparison is case-insensitive. See: ${RULE_DOCS.CSH002}`, node);
|
|
860
867
|
});
|
|
861
868
|
});
|
|
869
|
+
const IGNORED_TYPES = new Set([
|
|
870
|
+
"html"
|
|
871
|
+
]);
|
|
872
|
+
function isContentNode(node) {
|
|
873
|
+
if ("heading" === node.type) return false;
|
|
874
|
+
return !IGNORED_TYPES.has(node.type);
|
|
875
|
+
}
|
|
876
|
+
const UncategorizedContentRule = (0, external_unified_lint_rule_namespaceObject.lintRule)("remark-lint:changeset-uncategorized-content", (tree, file)=>{
|
|
877
|
+
for (const node of tree.children){
|
|
878
|
+
if ("heading" === node.type && 2 === node.depth) break;
|
|
879
|
+
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);
|
|
880
|
+
}
|
|
881
|
+
});
|
|
862
882
|
function stripFrontmatter(content) {
|
|
863
883
|
return content.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
864
884
|
}
|
|
@@ -869,7 +889,7 @@ var __webpack_exports__ = {};
|
|
|
869
889
|
}
|
|
870
890
|
static validateContent(content, filePath = "<input>") {
|
|
871
891
|
const body = stripFrontmatter(content);
|
|
872
|
-
const processor = (0, external_unified_.unified)().use(external_remark_parse_default()).use(external_remark_stringify_default()).use(HeadingHierarchyRule).use(RequiredSectionsRule).use(ContentStructureRule);
|
|
892
|
+
const processor = (0, external_unified_.unified)().use(external_remark_parse_default()).use(external_remark_stringify_default()).use(HeadingHierarchyRule).use(RequiredSectionsRule).use(ContentStructureRule).use(UncategorizedContentRule);
|
|
873
893
|
const file = processor.processSync(body);
|
|
874
894
|
return file.messages.map((msg)=>({
|
|
875
895
|
file: filePath,
|
|
@@ -1215,14 +1235,14 @@ var __webpack_exports__ = {};
|
|
|
1215
1235
|
description: external_effect_.Schema.String
|
|
1216
1236
|
});
|
|
1217
1237
|
const CommitHashSchema = external_effect_.Schema.String.pipe(external_effect_.Schema.pattern(/^[a-f0-9]{7,}$/, {
|
|
1218
|
-
message: ()=>
|
|
1238
|
+
message: ()=>'Commit hash must be 7 or more lowercase hexadecimal characters (0-9, a-f). Example: "a1b2c3d" or a full 40-character SHA like "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2". Uppercase letters are not allowed'
|
|
1219
1239
|
}));
|
|
1220
1240
|
const VersionTypeSchema = external_effect_.Schema.Literal("major", "minor", "patch", "none");
|
|
1221
1241
|
var primitives = __webpack_require__("./src/schemas/primitives.ts");
|
|
1222
1242
|
const ChangesetSummarySchema = external_effect_.Schema.String.pipe(external_effect_.Schema.minLength(1, {
|
|
1223
|
-
message: ()=>
|
|
1243
|
+
message: ()=>'Changeset summary cannot be empty. Provide a 1-1000 character description of the change (e.g., "Fix authentication timeout in login flow")'
|
|
1224
1244
|
}), external_effect_.Schema.maxLength(1000, {
|
|
1225
|
-
message: ()=>"Changeset summary
|
|
1245
|
+
message: ()=>"Changeset summary exceeds the 1000 character limit. Shorten the summary to at most 1000 characters — use the changeset body for additional details"
|
|
1226
1246
|
}));
|
|
1227
1247
|
const ChangesetSchema = external_effect_.Schema.Struct({
|
|
1228
1248
|
summary: ChangesetSummarySchema,
|
package/cjs/index.d.cts
CHANGED
|
@@ -256,9 +256,9 @@ export declare interface Changeset extends Schema.Schema.Type<typeof ChangesetSc
|
|
|
256
256
|
/**
|
|
257
257
|
* Static class for linting changeset files.
|
|
258
258
|
*
|
|
259
|
-
* Runs the
|
|
260
|
-
* content-structure) against changeset markdown
|
|
261
|
-
* diagnostic messages.
|
|
259
|
+
* Runs the four remark-lint rules (heading-hierarchy, required-sections,
|
|
260
|
+
* content-structure, uncategorized-content) against changeset markdown
|
|
261
|
+
* and returns structured diagnostic messages.
|
|
262
262
|
*
|
|
263
263
|
* @example
|
|
264
264
|
* ```typescript
|
package/cjs/markdownlint.cjs
CHANGED
|
@@ -26,9 +26,17 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
27
|
ContentStructureRule: ()=>ContentStructureRule,
|
|
28
28
|
HeadingHierarchyRule: ()=>HeadingHierarchyRule,
|
|
29
|
+
UncategorizedContentRule: ()=>UncategorizedContentRule,
|
|
29
30
|
default: ()=>markdownlint,
|
|
30
31
|
RequiredSectionsRule: ()=>RequiredSectionsRule
|
|
31
32
|
});
|
|
33
|
+
const DOCS_BASE = "https://github.com/savvy-web/changesets/blob/main/docs/rules";
|
|
34
|
+
const RULE_DOCS = {
|
|
35
|
+
CSH001: `${DOCS_BASE}/CSH001.md`,
|
|
36
|
+
CSH002: `${DOCS_BASE}/CSH002.md`,
|
|
37
|
+
CSH003: `${DOCS_BASE}/CSH003.md`,
|
|
38
|
+
CSH004: `${DOCS_BASE}/CSH004.md`
|
|
39
|
+
};
|
|
32
40
|
function getHeadingLevel(heading) {
|
|
33
41
|
const sequence = heading.children.find((c)=>"atxHeadingSequence" === c.type);
|
|
34
42
|
return sequence ? sequence.text.length : 0;
|
|
@@ -63,7 +71,7 @@ const ContentStructureRule = {
|
|
|
63
71
|
const nextIdx = i + 1 < h2Indices.length ? h2Indices[i + 1] : tokens.length;
|
|
64
72
|
if (!hasContentBetween(tokens, currentIdx, nextIdx)) onError({
|
|
65
73
|
lineNumber: tokens[currentIdx].startLine,
|
|
66
|
-
detail:
|
|
74
|
+
detail: `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}`
|
|
67
75
|
});
|
|
68
76
|
}
|
|
69
77
|
for (const token of tokens){
|
|
@@ -72,7 +80,7 @@ const ContentStructureRule = {
|
|
|
72
80
|
const hasInfo = openingFence?.children.some((c)=>"codeFencedFenceInfo" === c.type) ?? false;
|
|
73
81
|
if (!hasInfo) onError({
|
|
74
82
|
lineNumber: token.startLine,
|
|
75
|
-
detail:
|
|
83
|
+
detail: `Code block is missing a language identifier. Add a language after the opening fence (e.g., \`\`\`ts, \`\`\`json, \`\`\`bash). See: ${RULE_DOCS.CSH003}`
|
|
76
84
|
});
|
|
77
85
|
}
|
|
78
86
|
for (const token of tokens){
|
|
@@ -90,7 +98,7 @@ const ContentStructureRule = {
|
|
|
90
98
|
}
|
|
91
99
|
if (!hasContent) onError({
|
|
92
100
|
lineNumber: children[i].startLine,
|
|
93
|
-
detail:
|
|
101
|
+
detail: `Empty list item. Each list item must contain descriptive text (e.g., "- Fixed login timeout issue"). See: ${RULE_DOCS.CSH003}`
|
|
94
102
|
});
|
|
95
103
|
}
|
|
96
104
|
}
|
|
@@ -114,13 +122,13 @@ const HeadingHierarchyRule = {
|
|
|
114
122
|
if (1 === depth) {
|
|
115
123
|
onError({
|
|
116
124
|
lineNumber: token.startLine,
|
|
117
|
-
detail:
|
|
125
|
+
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}`
|
|
118
126
|
});
|
|
119
127
|
continue;
|
|
120
128
|
}
|
|
121
129
|
if (prevDepth > 0 && depth > prevDepth + 1) onError({
|
|
122
130
|
lineNumber: token.startLine,
|
|
123
|
-
detail: `Heading level skipped: expected h${prevDepth + 1} or lower, found h${depth}`
|
|
131
|
+
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}`
|
|
124
132
|
});
|
|
125
133
|
prevDepth = depth;
|
|
126
134
|
}
|
|
@@ -271,25 +279,51 @@ const RequiredSectionsRule = {
|
|
|
271
279
|
const text = getHeadingText(token);
|
|
272
280
|
if (!isValidHeading(text)) onError({
|
|
273
281
|
lineNumber: token.startLine,
|
|
274
|
-
detail: `Unknown section "${text}". Valid
|
|
282
|
+
detail: `Unknown section "${text}". Valid h2 headings are: ${allHeadings().join(", ")}. Heading comparison is case-insensitive. See: ${RULE_DOCS.CSH002}`
|
|
275
283
|
});
|
|
276
284
|
}
|
|
277
285
|
}
|
|
278
286
|
};
|
|
287
|
+
const UncategorizedContentRule = {
|
|
288
|
+
names: [
|
|
289
|
+
"changeset-uncategorized-content",
|
|
290
|
+
"CSH004"
|
|
291
|
+
],
|
|
292
|
+
description: "All content must be placed under a category heading (## heading)",
|
|
293
|
+
tags: [
|
|
294
|
+
"changeset"
|
|
295
|
+
],
|
|
296
|
+
parser: "micromark",
|
|
297
|
+
function: function(params, onError) {
|
|
298
|
+
const tokens = params.parsers.micromark.tokens;
|
|
299
|
+
for (const token of tokens){
|
|
300
|
+
if ("atxHeading" === token.type && 2 === getHeadingLevel(token)) break;
|
|
301
|
+
if ("lineEnding" !== token.type && "lineEndingBlank" !== token.type && "htmlFlow" !== token.type) {
|
|
302
|
+
if ("atxHeading" !== token.type) onError({
|
|
303
|
+
lineNumber: token.startLine,
|
|
304
|
+
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}`
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
};
|
|
279
310
|
const SilkChangesetsRules = [
|
|
280
311
|
HeadingHierarchyRule,
|
|
281
312
|
RequiredSectionsRule,
|
|
282
|
-
ContentStructureRule
|
|
313
|
+
ContentStructureRule,
|
|
314
|
+
UncategorizedContentRule
|
|
283
315
|
];
|
|
284
316
|
const markdownlint = SilkChangesetsRules;
|
|
285
317
|
exports.ContentStructureRule = __webpack_exports__.ContentStructureRule;
|
|
286
318
|
exports.HeadingHierarchyRule = __webpack_exports__.HeadingHierarchyRule;
|
|
287
319
|
exports.RequiredSectionsRule = __webpack_exports__.RequiredSectionsRule;
|
|
320
|
+
exports.UncategorizedContentRule = __webpack_exports__.UncategorizedContentRule;
|
|
288
321
|
exports["default"] = __webpack_exports__["default"];
|
|
289
322
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
290
323
|
"ContentStructureRule",
|
|
291
324
|
"HeadingHierarchyRule",
|
|
292
325
|
"RequiredSectionsRule",
|
|
326
|
+
"UncategorizedContentRule",
|
|
293
327
|
"default"
|
|
294
328
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
295
329
|
Object.defineProperty(exports, '__esModule', {
|
package/cjs/markdownlint.d.cts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - `changeset-heading-hierarchy` (CSH001): Enforce h2 start, no h1, no depth skips
|
|
9
9
|
* - `changeset-required-sections` (CSH002): Validate section headings match known categories
|
|
10
10
|
* - `changeset-content-structure` (CSH003): Content quality validation
|
|
11
|
+
* - `changeset-uncategorized-content` (CSH004): Reject content before first h2 heading
|
|
11
12
|
*
|
|
12
13
|
* @packageDocumentation
|
|
13
14
|
*/
|
|
@@ -58,4 +59,12 @@ export declare const RequiredSectionsRule: Rule;
|
|
|
58
59
|
declare const SilkChangesetsRules: Rule[];
|
|
59
60
|
export default SilkChangesetsRules;
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* markdownlint rule: changeset-uncategorized-content (CSH004)
|
|
64
|
+
*
|
|
65
|
+
* Detects content that appears before the first h2 heading in a changeset file.
|
|
66
|
+
* All content must be placed under a categorized section (## heading).
|
|
67
|
+
*/
|
|
68
|
+
export declare const UncategorizedContentRule: Rule;
|
|
69
|
+
|
|
61
70
|
export { }
|
package/cjs/remark.cjs
CHANGED
|
@@ -24,6 +24,7 @@ var __webpack_require__ = {};
|
|
|
24
24
|
var __webpack_exports__ = {};
|
|
25
25
|
__webpack_require__.r(__webpack_exports__);
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
UncategorizedContentRule: ()=>UncategorizedContentRule,
|
|
27
28
|
SilkChangesetTransformPreset: ()=>SilkChangesetTransformPreset,
|
|
28
29
|
NormalizeFormatPlugin: ()=>NormalizeFormatPlugin,
|
|
29
30
|
IssueLinkRefsPlugin: ()=>IssueLinkRefsPlugin,
|
|
@@ -473,25 +474,32 @@ const ReorderSectionsPlugin = ()=>(tree)=>{
|
|
|
473
474
|
}
|
|
474
475
|
};
|
|
475
476
|
const external_unified_lint_rule_namespaceObject = require("unified-lint-rule");
|
|
477
|
+
const DOCS_BASE = "https://github.com/savvy-web/changesets/blob/main/docs/rules";
|
|
478
|
+
const RULE_DOCS = {
|
|
479
|
+
CSH001: `${DOCS_BASE}/CSH001.md`,
|
|
480
|
+
CSH002: `${DOCS_BASE}/CSH002.md`,
|
|
481
|
+
CSH003: `${DOCS_BASE}/CSH003.md`,
|
|
482
|
+
CSH004: `${DOCS_BASE}/CSH004.md`
|
|
483
|
+
};
|
|
476
484
|
const ContentStructureRule = (0, external_unified_lint_rule_namespaceObject.lintRule)("remark-lint:changeset-content-structure", (tree, file)=>{
|
|
477
485
|
(0, external_unist_util_visit_namespaceObject.visit)(tree, "heading", (node, index, parent)=>{
|
|
478
486
|
if (2 !== node.depth || null == parent || null == index) return;
|
|
479
487
|
const next = parent.children[index + 1];
|
|
480
|
-
if (!next || "heading" === next.type && 2 === next.depth) file.message(
|
|
488
|
+
if (!next || "heading" === next.type && 2 === next.depth) 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);
|
|
481
489
|
});
|
|
482
490
|
(0, external_unist_util_visit_namespaceObject.visit)(tree, "code", (node)=>{
|
|
483
|
-
if (!node.lang) file.message(
|
|
491
|
+
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);
|
|
484
492
|
});
|
|
485
493
|
(0, external_unist_util_visit_namespaceObject.visit)(tree, "listItem", (node)=>{
|
|
486
494
|
const text = (0, external_mdast_util_to_string_namespaceObject.toString)(node).trim();
|
|
487
|
-
if (!text) file.message(
|
|
495
|
+
if (!text) file.message(`Empty list item. Each list item must contain descriptive text (e.g., "- Fixed login timeout issue"). See: ${RULE_DOCS.CSH003}`, node);
|
|
488
496
|
});
|
|
489
497
|
});
|
|
490
498
|
const HeadingHierarchyRule = (0, external_unified_lint_rule_namespaceObject.lintRule)("remark-lint:changeset-heading-hierarchy", (tree, file)=>{
|
|
491
499
|
let prevDepth = 0;
|
|
492
500
|
(0, external_unist_util_visit_namespaceObject.visit)(tree, "heading", (node)=>{
|
|
493
|
-
if (1 === node.depth) return void file.message(
|
|
494
|
-
if (prevDepth > 0 && node.depth > prevDepth + 1) file.message(`Heading level skipped: expected h${prevDepth + 1} or lower, found h${node.depth}`, node);
|
|
501
|
+
if (1 === node.depth) return void 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);
|
|
502
|
+
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);
|
|
495
503
|
prevDepth = node.depth;
|
|
496
504
|
});
|
|
497
505
|
});
|
|
@@ -499,13 +507,27 @@ const RequiredSectionsRule = (0, external_unified_lint_rule_namespaceObject.lint
|
|
|
499
507
|
(0, external_unist_util_visit_namespaceObject.visit)(tree, "heading", (node)=>{
|
|
500
508
|
if (2 !== node.depth) return;
|
|
501
509
|
const text = (0, external_mdast_util_to_string_namespaceObject.toString)(node);
|
|
502
|
-
if (!isValidHeading(text)) file.message(`Unknown section "${text}". Valid
|
|
510
|
+
if (!isValidHeading(text)) file.message(`Unknown section "${text}". Valid h2 headings are: ${allHeadings().join(", ")}. Heading comparison is case-insensitive. See: ${RULE_DOCS.CSH002}`, node);
|
|
503
511
|
});
|
|
504
512
|
});
|
|
513
|
+
const IGNORED_TYPES = new Set([
|
|
514
|
+
"html"
|
|
515
|
+
]);
|
|
516
|
+
function isContentNode(node) {
|
|
517
|
+
if ("heading" === node.type) return false;
|
|
518
|
+
return !IGNORED_TYPES.has(node.type);
|
|
519
|
+
}
|
|
520
|
+
const UncategorizedContentRule = (0, external_unified_lint_rule_namespaceObject.lintRule)("remark-lint:changeset-uncategorized-content", (tree, file)=>{
|
|
521
|
+
for (const node of tree.children){
|
|
522
|
+
if ("heading" === node.type && 2 === node.depth) break;
|
|
523
|
+
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);
|
|
524
|
+
}
|
|
525
|
+
});
|
|
505
526
|
const SilkChangesetPreset = [
|
|
506
527
|
HeadingHierarchyRule,
|
|
507
528
|
RequiredSectionsRule,
|
|
508
|
-
ContentStructureRule
|
|
529
|
+
ContentStructureRule,
|
|
530
|
+
UncategorizedContentRule
|
|
509
531
|
];
|
|
510
532
|
const SilkChangesetTransformPreset = [
|
|
511
533
|
MergeSectionsPlugin,
|
|
@@ -526,6 +548,7 @@ exports.ReorderSectionsPlugin = __webpack_exports__.ReorderSectionsPlugin;
|
|
|
526
548
|
exports.RequiredSectionsRule = __webpack_exports__.RequiredSectionsRule;
|
|
527
549
|
exports.SilkChangesetPreset = __webpack_exports__.SilkChangesetPreset;
|
|
528
550
|
exports.SilkChangesetTransformPreset = __webpack_exports__.SilkChangesetTransformPreset;
|
|
551
|
+
exports.UncategorizedContentRule = __webpack_exports__.UncategorizedContentRule;
|
|
529
552
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
530
553
|
"ContentStructureRule",
|
|
531
554
|
"ContributorFootnotesPlugin",
|
|
@@ -537,7 +560,8 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
|
537
560
|
"ReorderSectionsPlugin",
|
|
538
561
|
"RequiredSectionsRule",
|
|
539
562
|
"SilkChangesetPreset",
|
|
540
|
-
"SilkChangesetTransformPreset"
|
|
563
|
+
"SilkChangesetTransformPreset",
|
|
564
|
+
"UncategorizedContentRule"
|
|
541
565
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
542
566
|
Object.defineProperty(exports, '__esModule', {
|
|
543
567
|
value: true
|
package/cjs/remark.d.cts
CHANGED
|
@@ -71,7 +71,7 @@ export declare const RequiredSectionsRule: Plugin_2<Root, unknown>;
|
|
|
71
71
|
*
|
|
72
72
|
* @public
|
|
73
73
|
*/
|
|
74
|
-
export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>];
|
|
74
|
+
export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>];
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
77
|
* Ordered array of all transform plugins in the correct execution order.
|
|
@@ -89,4 +89,6 @@ export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plu
|
|
|
89
89
|
*/
|
|
90
90
|
export declare const SilkChangesetTransformPreset: readonly [Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>];
|
|
91
91
|
|
|
92
|
+
export declare const UncategorizedContentRule: Plugin_2<Root, unknown>;
|
|
93
|
+
|
|
92
94
|
export { }
|
package/esm/160.js
CHANGED
|
@@ -143,7 +143,7 @@ function isValidUrl(value) {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
const UsernameSchema = Schema.String.pipe(Schema.pattern(/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/, {
|
|
146
|
-
message: ()=>
|
|
146
|
+
message: ()=>'Invalid GitHub username format. Usernames must contain only alphanumeric characters and hyphens, and cannot start or end with a hyphen. Example: "octocat" or "my-user-123"'
|
|
147
147
|
}));
|
|
148
148
|
const IssueNumberSchema = PositiveInteger.annotations({
|
|
149
149
|
title: "IssueNumber",
|
|
@@ -154,7 +154,7 @@ const UrlOrMarkdownLinkSchema = Schema.String.pipe(Schema.filter((value)=>{
|
|
|
154
154
|
const match = MARKDOWN_LINK_PATTERN.exec(value);
|
|
155
155
|
return match?.[2] ? isValidUrl(match[2]) : false;
|
|
156
156
|
}, {
|
|
157
|
-
message: ()=>
|
|
157
|
+
message: ()=>'Value must be a valid URL or a markdown link. Expected a plain URL (e.g., "https://github.com/owner/repo/pull/42") or a markdown link (e.g., "[#42](https://github.com/owner/repo/pull/42)")'
|
|
158
158
|
}));
|
|
159
159
|
const GitHubInfoSchema = Schema.Struct({
|
|
160
160
|
user: Schema.optional(UsernameSchema),
|
package/esm/260.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const DOCS_BASE = "https://github.com/savvy-web/changesets/blob/main/docs/rules";
|
|
2
|
+
const RULE_DOCS = {
|
|
3
|
+
CSH001: `${DOCS_BASE}/CSH001.md`,
|
|
4
|
+
CSH002: `${DOCS_BASE}/CSH002.md`,
|
|
5
|
+
CSH003: `${DOCS_BASE}/CSH003.md`,
|
|
6
|
+
CSH004: `${DOCS_BASE}/CSH004.md`
|
|
7
|
+
};
|
|
8
|
+
export { RULE_DOCS };
|
package/esm/273.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, relative, resolve } from "node:path";
|
|
3
3
|
import { unified, remark_gfm, remark_stringify, remark_parse } from "./795.js";
|
|
4
|
-
import { MergeSectionsPlugin, DeduplicateItemsPlugin, IssueLinkRefsPlugin, ContentStructureRule, HeadingHierarchyRule, NormalizeFormatPlugin, ReorderSectionsPlugin, ContributorFootnotesPlugin, RequiredSectionsRule } from "./
|
|
4
|
+
import { UncategorizedContentRule, MergeSectionsPlugin, DeduplicateItemsPlugin, IssueLinkRefsPlugin, ContentStructureRule, HeadingHierarchyRule, NormalizeFormatPlugin, ReorderSectionsPlugin, ContributorFootnotesPlugin, RequiredSectionsRule } from "./622.js";
|
|
5
5
|
function stripFrontmatter(content) {
|
|
6
6
|
return content.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
7
7
|
}
|
|
@@ -12,7 +12,7 @@ class ChangesetLinter {
|
|
|
12
12
|
}
|
|
13
13
|
static validateContent(content, filePath = "<input>") {
|
|
14
14
|
const body = stripFrontmatter(content);
|
|
15
|
-
const processor = unified().use(remark_parse).use(remark_stringify).use(HeadingHierarchyRule).use(RequiredSectionsRule).use(ContentStructureRule);
|
|
15
|
+
const processor = unified().use(remark_parse).use(remark_stringify).use(HeadingHierarchyRule).use(RequiredSectionsRule).use(ContentStructureRule).use(UncategorizedContentRule);
|
|
16
16
|
const file = processor.processSync(body);
|
|
17
17
|
return file.messages.map((msg)=>({
|
|
18
18
|
file: filePath,
|
package/esm/{234.js → 622.js}
RENAMED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import { lintRule } from "unified-lint-rule";
|
|
2
2
|
import { SKIP, visit } from "unist-util-visit";
|
|
3
3
|
import { external_mdast_util_to_string_toString } from "./689.js";
|
|
4
|
+
import { RULE_DOCS } from "./260.js";
|
|
4
5
|
import { isValidHeading, fromHeading, allHeadings } from "./60.js";
|
|
5
6
|
const ContentStructureRule = lintRule("remark-lint:changeset-content-structure", (tree, file)=>{
|
|
6
7
|
visit(tree, "heading", (node, index, parent)=>{
|
|
7
8
|
if (2 !== node.depth || null == parent || null == index) return;
|
|
8
9
|
const next = parent.children[index + 1];
|
|
9
|
-
if (!next || "heading" === next.type && 2 === next.depth) file.message(
|
|
10
|
+
if (!next || "heading" === next.type && 2 === next.depth) 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);
|
|
10
11
|
});
|
|
11
12
|
visit(tree, "code", (node)=>{
|
|
12
|
-
if (!node.lang) file.message(
|
|
13
|
+
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);
|
|
13
14
|
});
|
|
14
15
|
visit(tree, "listItem", (node)=>{
|
|
15
16
|
const text = external_mdast_util_to_string_toString(node).trim();
|
|
16
|
-
if (!text) file.message(
|
|
17
|
+
if (!text) file.message(`Empty list item. Each list item must contain descriptive text (e.g., "- Fixed login timeout issue"). See: ${RULE_DOCS.CSH003}`, node);
|
|
17
18
|
});
|
|
18
19
|
});
|
|
19
20
|
const HeadingHierarchyRule = lintRule("remark-lint:changeset-heading-hierarchy", (tree, file)=>{
|
|
20
21
|
let prevDepth = 0;
|
|
21
22
|
visit(tree, "heading", (node)=>{
|
|
22
|
-
if (1 === node.depth) return void file.message(
|
|
23
|
-
if (prevDepth > 0 && node.depth > prevDepth + 1) file.message(`Heading level skipped: expected h${prevDepth + 1} or lower, found h${node.depth}`, node);
|
|
23
|
+
if (1 === node.depth) return void 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);
|
|
24
|
+
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);
|
|
24
25
|
prevDepth = node.depth;
|
|
25
26
|
});
|
|
26
27
|
});
|
|
@@ -28,9 +29,22 @@ const RequiredSectionsRule = lintRule("remark-lint:changeset-required-sections",
|
|
|
28
29
|
visit(tree, "heading", (node)=>{
|
|
29
30
|
if (2 !== node.depth) return;
|
|
30
31
|
const text = external_mdast_util_to_string_toString(node);
|
|
31
|
-
if (!isValidHeading(text)) file.message(`Unknown section "${text}". Valid
|
|
32
|
+
if (!isValidHeading(text)) file.message(`Unknown section "${text}". Valid h2 headings are: ${allHeadings().join(", ")}. Heading comparison is case-insensitive. See: ${RULE_DOCS.CSH002}`, node);
|
|
32
33
|
});
|
|
33
34
|
});
|
|
35
|
+
const IGNORED_TYPES = new Set([
|
|
36
|
+
"html"
|
|
37
|
+
]);
|
|
38
|
+
function isContentNode(node) {
|
|
39
|
+
if ("heading" === node.type) return false;
|
|
40
|
+
return !IGNORED_TYPES.has(node.type);
|
|
41
|
+
}
|
|
42
|
+
const UncategorizedContentRule = lintRule("remark-lint:changeset-uncategorized-content", (tree, file)=>{
|
|
43
|
+
for (const node of tree.children){
|
|
44
|
+
if ("heading" === node.type && 2 === node.depth) break;
|
|
45
|
+
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);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
34
48
|
function getVersionBlocks(tree) {
|
|
35
49
|
const blocks = [];
|
|
36
50
|
for(let i = 0; i < tree.children.length; i++){
|
|
@@ -334,4 +348,4 @@ const ReorderSectionsPlugin = ()=>(tree)=>{
|
|
|
334
348
|
tree.children.splice(block.startIndex, blockLength, ...newChildren);
|
|
335
349
|
}
|
|
336
350
|
};
|
|
337
|
-
export { ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, HeadingHierarchyRule, IssueLinkRefsPlugin, MergeSectionsPlugin, NormalizeFormatPlugin, ReorderSectionsPlugin, RequiredSectionsRule };
|
|
351
|
+
export { ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, HeadingHierarchyRule, IssueLinkRefsPlugin, MergeSectionsPlugin, NormalizeFormatPlugin, ReorderSectionsPlugin, RequiredSectionsRule, UncategorizedContentRule };
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { Args, Command, Options } from "@effect/cli";
|
|
3
3
|
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
5
|
+
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
5
6
|
import { findProjectRoot, getWorkspaceInfos } from "workspace-tools";
|
|
6
|
-
import { parse } from "jsonc-parser";
|
|
7
7
|
import { globSync } from "tinyglobby";
|
|
8
8
|
import { readFileSync, ChangelogTransformer, ChangesetLinter, resolve, existsSync, relative, mkdirSync, writeFileSync, join } from "../273.js";
|
|
9
9
|
import { VersionFilesSchema, Schema, VersionFileError, Data, Effect } from "../795.js";
|
|
@@ -48,7 +48,8 @@ const MARKDOWNLINT_CONFIG_PATHS = [
|
|
|
48
48
|
const RULE_NAMES = [
|
|
49
49
|
"changeset-heading-hierarchy",
|
|
50
50
|
"changeset-required-sections",
|
|
51
|
-
"changeset-content-structure"
|
|
51
|
+
"changeset-content-structure",
|
|
52
|
+
"changeset-uncategorized-content"
|
|
52
53
|
];
|
|
53
54
|
const DEFAULT_CONFIG = {
|
|
54
55
|
$schema: "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
|
@@ -91,9 +92,10 @@ function detectGitHubRepo(cwd) {
|
|
|
91
92
|
} catch {}
|
|
92
93
|
return null;
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
const JSONC_FORMAT = {
|
|
96
|
+
tabSize: 1,
|
|
97
|
+
insertSpaces: false
|
|
98
|
+
};
|
|
97
99
|
function resolveWorkspaceRoot(cwd) {
|
|
98
100
|
return findProjectRoot(cwd) ?? cwd;
|
|
99
101
|
}
|
|
@@ -155,13 +157,38 @@ function handleBaseMarkdownlint(root) {
|
|
|
155
157
|
const foundPath = findMarkdownlintConfig(root);
|
|
156
158
|
if (!foundPath) return `Warning: no markdownlint config found (checked ${MARKDOWNLINT_CONFIG_PATHS.join(", ")})`;
|
|
157
159
|
const fullPath = join(root, foundPath);
|
|
158
|
-
|
|
159
|
-
const parsed =
|
|
160
|
-
if (!Array.isArray(parsed.customRules)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
let text = readFileSync(fullPath, "utf-8");
|
|
161
|
+
const parsed = parse(text);
|
|
162
|
+
if (!Array.isArray(parsed.customRules) || !parsed.customRules.includes(CUSTOM_RULES_ENTRY)) {
|
|
163
|
+
const edits = modify(text, [
|
|
164
|
+
"customRules",
|
|
165
|
+
-1
|
|
166
|
+
], CUSTOM_RULES_ENTRY, {
|
|
167
|
+
formattingOptions: JSONC_FORMAT,
|
|
168
|
+
isArrayInsertion: true
|
|
169
|
+
});
|
|
170
|
+
text = applyEdits(text, edits);
|
|
171
|
+
}
|
|
172
|
+
const currentConfig = parse(text).config;
|
|
173
|
+
if ("object" != typeof currentConfig || null === currentConfig) {
|
|
174
|
+
const edits = modify(text, [
|
|
175
|
+
"config"
|
|
176
|
+
], {}, {
|
|
177
|
+
formattingOptions: JSONC_FORMAT
|
|
178
|
+
});
|
|
179
|
+
text = applyEdits(text, edits);
|
|
180
|
+
}
|
|
181
|
+
const config = parse(text).config;
|
|
182
|
+
for (const rule of RULE_NAMES)if (!(rule in config)) {
|
|
183
|
+
const edits = modify(text, [
|
|
184
|
+
"config",
|
|
185
|
+
rule
|
|
186
|
+
], false, {
|
|
187
|
+
formattingOptions: JSONC_FORMAT
|
|
188
|
+
});
|
|
189
|
+
text = applyEdits(text, edits);
|
|
190
|
+
}
|
|
191
|
+
writeFileSync(fullPath, text);
|
|
165
192
|
return `Updated ${foundPath}`;
|
|
166
193
|
},
|
|
167
194
|
catch: (error)=>new InitError({
|
|
@@ -247,7 +274,7 @@ function checkBaseMarkdownlint(root) {
|
|
|
247
274
|
];
|
|
248
275
|
try {
|
|
249
276
|
const raw = readFileSync(join(root, foundPath), "utf-8");
|
|
250
|
-
const parsed =
|
|
277
|
+
const parsed = parse(raw);
|
|
251
278
|
const issues = [];
|
|
252
279
|
if (!Array.isArray(parsed.customRules) || !parsed.customRules.includes(CUSTOM_RULES_ENTRY)) issues.push({
|
|
253
280
|
file: foundPath,
|
|
@@ -758,7 +785,7 @@ const rootCommand = Command.make("savvy-changesets").pipe(Command.withSubcommand
|
|
|
758
785
|
]));
|
|
759
786
|
const cli = Command.run(rootCommand, {
|
|
760
787
|
name: "savvy-changesets",
|
|
761
|
-
version: "0.
|
|
788
|
+
version: "0.4.0"
|
|
762
789
|
});
|
|
763
790
|
function runCli() {
|
|
764
791
|
const main = Effect.suspend(()=>cli(process.argv)).pipe(Effect.provide(NodeContext.layer));
|
package/esm/index.d.ts
CHANGED
|
@@ -256,9 +256,9 @@ export declare interface Changeset extends Schema.Schema.Type<typeof ChangesetSc
|
|
|
256
256
|
/**
|
|
257
257
|
* Static class for linting changeset files.
|
|
258
258
|
*
|
|
259
|
-
* Runs the
|
|
260
|
-
* content-structure) against changeset markdown
|
|
261
|
-
* diagnostic messages.
|
|
259
|
+
* Runs the four remark-lint rules (heading-hierarchy, required-sections,
|
|
260
|
+
* content-structure, uncategorized-content) against changeset markdown
|
|
261
|
+
* and returns structured diagnostic messages.
|
|
262
262
|
*
|
|
263
263
|
* @example
|
|
264
264
|
* ```typescript
|
package/esm/index.js
CHANGED
|
@@ -48,13 +48,13 @@ const SectionCategorySchema = Schema.Struct({
|
|
|
48
48
|
description: Schema.String
|
|
49
49
|
});
|
|
50
50
|
const CommitHashSchema = Schema.String.pipe(Schema.pattern(/^[a-f0-9]{7,}$/, {
|
|
51
|
-
message: ()=>
|
|
51
|
+
message: ()=>'Commit hash must be 7 or more lowercase hexadecimal characters (0-9, a-f). Example: "a1b2c3d" or a full 40-character SHA like "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2". Uppercase letters are not allowed'
|
|
52
52
|
}));
|
|
53
53
|
const VersionTypeSchema = Schema.Literal("major", "minor", "patch", "none");
|
|
54
54
|
const ChangesetSummarySchema = Schema.String.pipe(Schema.minLength(1, {
|
|
55
|
-
message: ()=>
|
|
55
|
+
message: ()=>'Changeset summary cannot be empty. Provide a 1-1000 character description of the change (e.g., "Fix authentication timeout in login flow")'
|
|
56
56
|
}), Schema.maxLength(1000, {
|
|
57
|
-
message: ()=>"Changeset summary
|
|
57
|
+
message: ()=>"Changeset summary exceeds the 1000 character limit. Shorten the summary to at most 1000 characters — use the changeset body for additional details"
|
|
58
58
|
}));
|
|
59
59
|
const ChangesetSchema = Schema.Struct({
|
|
60
60
|
summary: ChangesetSummarySchema,
|
package/esm/markdownlint.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - `changeset-heading-hierarchy` (CSH001): Enforce h2 start, no h1, no depth skips
|
|
9
9
|
* - `changeset-required-sections` (CSH002): Validate section headings match known categories
|
|
10
10
|
* - `changeset-content-structure` (CSH003): Content quality validation
|
|
11
|
+
* - `changeset-uncategorized-content` (CSH004): Reject content before first h2 heading
|
|
11
12
|
*
|
|
12
13
|
* @packageDocumentation
|
|
13
14
|
*/
|
|
@@ -58,4 +59,12 @@ export declare const RequiredSectionsRule: Rule;
|
|
|
58
59
|
declare const SilkChangesetsRules: Rule[];
|
|
59
60
|
export default SilkChangesetsRules;
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* markdownlint rule: changeset-uncategorized-content (CSH004)
|
|
64
|
+
*
|
|
65
|
+
* Detects content that appears before the first h2 heading in a changeset file.
|
|
66
|
+
* All content must be placed under a categorized section (## heading).
|
|
67
|
+
*/
|
|
68
|
+
export declare const UncategorizedContentRule: Rule;
|
|
69
|
+
|
|
61
70
|
export { }
|
package/esm/markdownlint.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RULE_DOCS } from "./260.js";
|
|
1
2
|
import { isValidHeading, allHeadings } from "./60.js";
|
|
2
3
|
function getHeadingLevel(heading) {
|
|
3
4
|
const sequence = heading.children.find((c)=>"atxHeadingSequence" === c.type);
|
|
@@ -33,7 +34,7 @@ const ContentStructureRule = {
|
|
|
33
34
|
const nextIdx = i + 1 < h2Indices.length ? h2Indices[i + 1] : tokens.length;
|
|
34
35
|
if (!hasContentBetween(tokens, currentIdx, nextIdx)) onError({
|
|
35
36
|
lineNumber: tokens[currentIdx].startLine,
|
|
36
|
-
detail:
|
|
37
|
+
detail: `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}`
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
40
|
for (const token of tokens){
|
|
@@ -42,7 +43,7 @@ const ContentStructureRule = {
|
|
|
42
43
|
const hasInfo = openingFence?.children.some((c)=>"codeFencedFenceInfo" === c.type) ?? false;
|
|
43
44
|
if (!hasInfo) onError({
|
|
44
45
|
lineNumber: token.startLine,
|
|
45
|
-
detail:
|
|
46
|
+
detail: `Code block is missing a language identifier. Add a language after the opening fence (e.g., \`\`\`ts, \`\`\`json, \`\`\`bash). See: ${RULE_DOCS.CSH003}`
|
|
46
47
|
});
|
|
47
48
|
}
|
|
48
49
|
for (const token of tokens){
|
|
@@ -60,7 +61,7 @@ const ContentStructureRule = {
|
|
|
60
61
|
}
|
|
61
62
|
if (!hasContent) onError({
|
|
62
63
|
lineNumber: children[i].startLine,
|
|
63
|
-
detail:
|
|
64
|
+
detail: `Empty list item. Each list item must contain descriptive text (e.g., "- Fixed login timeout issue"). See: ${RULE_DOCS.CSH003}`
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
67
|
}
|
|
@@ -84,13 +85,13 @@ const HeadingHierarchyRule = {
|
|
|
84
85
|
if (1 === depth) {
|
|
85
86
|
onError({
|
|
86
87
|
lineNumber: token.startLine,
|
|
87
|
-
detail:
|
|
88
|
+
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}`
|
|
88
89
|
});
|
|
89
90
|
continue;
|
|
90
91
|
}
|
|
91
92
|
if (prevDepth > 0 && depth > prevDepth + 1) onError({
|
|
92
93
|
lineNumber: token.startLine,
|
|
93
|
-
detail: `Heading level skipped: expected h${prevDepth + 1} or lower, found h${depth}`
|
|
94
|
+
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}`
|
|
94
95
|
});
|
|
95
96
|
prevDepth = depth;
|
|
96
97
|
}
|
|
@@ -113,16 +114,40 @@ const RequiredSectionsRule = {
|
|
|
113
114
|
const text = getHeadingText(token);
|
|
114
115
|
if (!isValidHeading(text)) onError({
|
|
115
116
|
lineNumber: token.startLine,
|
|
116
|
-
detail: `Unknown section "${text}". Valid
|
|
117
|
+
detail: `Unknown section "${text}". Valid h2 headings are: ${allHeadings().join(", ")}. Heading comparison is case-insensitive. See: ${RULE_DOCS.CSH002}`
|
|
117
118
|
});
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
};
|
|
122
|
+
const UncategorizedContentRule = {
|
|
123
|
+
names: [
|
|
124
|
+
"changeset-uncategorized-content",
|
|
125
|
+
"CSH004"
|
|
126
|
+
],
|
|
127
|
+
description: "All content must be placed under a category heading (## heading)",
|
|
128
|
+
tags: [
|
|
129
|
+
"changeset"
|
|
130
|
+
],
|
|
131
|
+
parser: "micromark",
|
|
132
|
+
function: function(params, onError) {
|
|
133
|
+
const tokens = params.parsers.micromark.tokens;
|
|
134
|
+
for (const token of tokens){
|
|
135
|
+
if ("atxHeading" === token.type && 2 === getHeadingLevel(token)) break;
|
|
136
|
+
if ("lineEnding" !== token.type && "lineEndingBlank" !== token.type && "htmlFlow" !== token.type) {
|
|
137
|
+
if ("atxHeading" !== token.type) onError({
|
|
138
|
+
lineNumber: token.startLine,
|
|
139
|
+
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}`
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
121
145
|
const SilkChangesetsRules = [
|
|
122
146
|
HeadingHierarchyRule,
|
|
123
147
|
RequiredSectionsRule,
|
|
124
|
-
ContentStructureRule
|
|
148
|
+
ContentStructureRule,
|
|
149
|
+
UncategorizedContentRule
|
|
125
150
|
];
|
|
126
151
|
const markdownlint = SilkChangesetsRules;
|
|
127
152
|
export default markdownlint;
|
|
128
|
-
export { ContentStructureRule, HeadingHierarchyRule, RequiredSectionsRule };
|
|
153
|
+
export { ContentStructureRule, HeadingHierarchyRule, RequiredSectionsRule, UncategorizedContentRule };
|
package/esm/remark.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ export declare const RequiredSectionsRule: Plugin_2<Root, unknown>;
|
|
|
71
71
|
*
|
|
72
72
|
* @public
|
|
73
73
|
*/
|
|
74
|
-
export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>];
|
|
74
|
+
export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>];
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
77
|
* Ordered array of all transform plugins in the correct execution order.
|
|
@@ -89,4 +89,6 @@ export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plu
|
|
|
89
89
|
*/
|
|
90
90
|
export declare const SilkChangesetTransformPreset: readonly [Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>];
|
|
91
91
|
|
|
92
|
+
export declare const UncategorizedContentRule: Plugin_2<Root, unknown>;
|
|
93
|
+
|
|
92
94
|
export { }
|
package/esm/remark.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { NormalizeFormatPlugin, DeduplicateItemsPlugin, MergeSectionsPlugin, ContentStructureRule, IssueLinkRefsPlugin, ReorderSectionsPlugin, HeadingHierarchyRule, ContributorFootnotesPlugin, RequiredSectionsRule } from "./
|
|
1
|
+
import { UncategorizedContentRule, NormalizeFormatPlugin, DeduplicateItemsPlugin, MergeSectionsPlugin, ContentStructureRule, IssueLinkRefsPlugin, ReorderSectionsPlugin, HeadingHierarchyRule, ContributorFootnotesPlugin, RequiredSectionsRule } from "./622.js";
|
|
2
2
|
const SilkChangesetPreset = [
|
|
3
3
|
HeadingHierarchyRule,
|
|
4
4
|
RequiredSectionsRule,
|
|
5
|
-
ContentStructureRule
|
|
5
|
+
ContentStructureRule,
|
|
6
|
+
UncategorizedContentRule
|
|
6
7
|
];
|
|
7
8
|
const SilkChangesetTransformPreset = [
|
|
8
9
|
MergeSectionsPlugin,
|
|
@@ -12,5 +13,5 @@ const SilkChangesetTransformPreset = [
|
|
|
12
13
|
IssueLinkRefsPlugin,
|
|
13
14
|
NormalizeFormatPlugin
|
|
14
15
|
];
|
|
15
|
-
export { ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, HeadingHierarchyRule, IssueLinkRefsPlugin, MergeSectionsPlugin, NormalizeFormatPlugin, ReorderSectionsPlugin, RequiredSectionsRule } from "./
|
|
16
|
+
export { ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, HeadingHierarchyRule, IssueLinkRefsPlugin, MergeSectionsPlugin, NormalizeFormatPlugin, ReorderSectionsPlugin, RequiredSectionsRule, UncategorizedContentRule } from "./622.js";
|
|
16
17
|
export { SilkChangesetPreset, SilkChangesetTransformPreset };
|
package/package.json
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
2
|
+
"name": "@savvy-web/changesets",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Custom changelog formatter and markdown processing pipeline for the Silk Suite. Provides structured changeset sections, remark-based validation and transformation, and an Effect CLI.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"changesets",
|
|
8
|
+
"changelog",
|
|
9
|
+
"release-notes",
|
|
10
|
+
"remark",
|
|
11
|
+
"markdown",
|
|
12
|
+
"effect",
|
|
13
|
+
"silk-suite"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/savvy-web/changesets#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/savvy-web/changesets/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/savvy-web/changesets.git"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": {
|
|
25
|
+
"name": "C. Spencer Beggs",
|
|
26
|
+
"email": "spencer@savvyweb.systems",
|
|
27
|
+
"url": "https://savvyweb.systems"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./esm/index.d.ts",
|
|
33
|
+
"import": "./esm/index.js",
|
|
34
|
+
"require": "./cjs/index.cjs"
|
|
35
|
+
},
|
|
36
|
+
"./changelog": {
|
|
37
|
+
"types": "./esm/changelog.d.ts",
|
|
38
|
+
"import": "./esm/changelog.js",
|
|
39
|
+
"require": "./cjs/changelog.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./markdownlint": {
|
|
42
|
+
"types": "./esm/markdownlint.d.ts",
|
|
43
|
+
"import": "./esm/markdownlint.js",
|
|
44
|
+
"require": "./cjs/markdownlint.cjs"
|
|
45
|
+
},
|
|
46
|
+
"./remark": {
|
|
47
|
+
"types": "./esm/remark.d.ts",
|
|
48
|
+
"import": "./esm/remark.js",
|
|
49
|
+
"require": "./cjs/remark.cjs"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"bin": {
|
|
53
|
+
"savvy-changesets": "./esm/bin/savvy-changesets.js"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@changesets/get-github-info": "^0.8.0",
|
|
57
|
+
"@effect/cli": "^0.73.2",
|
|
58
|
+
"@effect/platform": "^0.94.5",
|
|
59
|
+
"@effect/platform-node": "^0.104.1",
|
|
60
|
+
"effect": "^3.19.19",
|
|
61
|
+
"jsonc-parser": "^3.3.1",
|
|
62
|
+
"mdast-util-heading-range": "^4.0.0",
|
|
63
|
+
"mdast-util-to-string": "^4.0.0",
|
|
64
|
+
"remark-gfm": "^4.0.1",
|
|
65
|
+
"remark-parse": "^11.0.0",
|
|
66
|
+
"remark-stringify": "^11.0.0",
|
|
67
|
+
"tinyglobby": "^0.2.15",
|
|
68
|
+
"unified": "^11.0.5",
|
|
69
|
+
"unified-lint-rule": "^3.0.1",
|
|
70
|
+
"unist-util-visit": "^5.1.0",
|
|
71
|
+
"workspace-tools": "^0.41.0"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"@changesets/cli": "^2.30.0"
|
|
75
|
+
},
|
|
76
|
+
"peerDependenciesMeta": {
|
|
77
|
+
"@changesets/cli": {
|
|
78
|
+
"optional": false
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"engines": {
|
|
82
|
+
"node": ">=24.0.0"
|
|
83
|
+
},
|
|
84
|
+
"scripts": {
|
|
85
|
+
"postinstall": "savvy-changesets init --check"
|
|
86
|
+
},
|
|
87
|
+
"files": [
|
|
88
|
+
"!changesets.api.json",
|
|
89
|
+
"!tsconfig.json",
|
|
90
|
+
"!tsdoc.json",
|
|
91
|
+
"LICENSE",
|
|
92
|
+
"README.md",
|
|
93
|
+
"cjs",
|
|
94
|
+
"esm",
|
|
95
|
+
"package.json",
|
|
96
|
+
"tsdoc-metadata.json"
|
|
97
|
+
]
|
|
98
|
+
}
|