@savvy-web/silk-effects 7.0.1 → 7.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/changesets/api/linter.js +1 -1
- package/changesets/api/transformer.js +10 -7
- package/changesets/changelog/getReleaseLine.js +114 -20
- package/changesets/changelog/vanilla.js +47 -0
- package/changesets/constants.js +7 -2
- package/changesets/index.js +7 -4
- package/changesets/markdownlint/rules/content-structure.js +2 -2
- package/changesets/markdownlint/rules/dependency-table-format.js +12 -11
- package/changesets/markdownlint/rules/heading-hierarchy.js +2 -2
- package/changesets/markdownlint/rules/required-sections.js +2 -2
- package/changesets/markdownlint/rules/uncategorized-content.js +3 -3
- package/changesets/markdownlint/rules/utils.js +17 -6
- package/changesets/remark/plugins/aggregate-dependency-tables.js +53 -1
- package/changesets/remark/plugins/contributor-footnotes.js +276 -64
- package/changesets/remark/plugins/reorder-sections.js +18 -3
- package/changesets/remark/presets.js +1 -1
- package/changesets/remark/rules/dependency-table-format.js +7 -9
- package/changesets/schemas/dependency-table.js +11 -3
- package/changesets/schemas/options.js +8 -0
- package/changesets/services/config-inspector.js +51 -4
- package/changesets/services/deps-regen.js +106 -10
- package/changesets/services/release-planner.js +29 -8
- package/changesets/utils/dep-diff.js +34 -5
- package/changesets/utils/dependency-section.js +34 -0
- package/changesets/utils/dependency-table.js +14 -9
- package/changesets/utils/markdown-emit.js +86 -0
- package/changesets/utils/remark-pipeline.js +14 -85
- package/changesets/utils/section-parser.js +4 -2
- package/index.d.ts +158 -24
- package/index.js +1 -1
- package/lint/index.js +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -109,7 +109,7 @@ const tags = strategy.tagsFor([{ name: "@savvy-web/silk-effects", version: "1.0.
|
|
|
109
109
|
|
|
110
110
|
#### ChangesetLinter
|
|
111
111
|
|
|
112
|
-
Validate a changeset file against the Silk section rules. `ChangesetLinter.validateContent(content, filePath?)` and `ChangesetLinter.validateFile(filePath)` are static and synchronous, returning `LintMessage[]` — no Effect, no layers. Rules cover the valid section headings, structural constraints, and the dependency-table format
|
|
112
|
+
Validate a changeset file against the Silk section rules. `ChangesetLinter.validateContent(content, filePath?)` and `ChangesetLinter.validateFile(filePath)` are static and synchronous, returning `LintMessage[]` — no Effect, no layers. Rules cover the valid section headings, structural constraints, and the dependency-table format: a `## Dependencies` section must contain a dependency table somewhere between its heading and the next — prose may sit before or after the table — and a section with no table at all is reported at the heading. Each rule is documented under [`docs/rules/`](./docs/rules/CSH001.md) (CSH001 through CSH005).
|
|
113
113
|
|
|
114
114
|
```typescript
|
|
115
115
|
import { Changesets } from "@savvy-web/silk-effects";
|
|
@@ -294,6 +294,8 @@ const preview = yield* planner.preview(root, {
|
|
|
294
294
|
|
|
295
295
|
`Changesets.ReleasePlanner.layer` requires `ConfigInspector` and `FileSystem`.
|
|
296
296
|
|
|
297
|
+
The `Changesets` namespace also re-exports the stock [`@changesets/changelog-git`](https://www.npmjs.com/package/@changesets/changelog-git) renderer as `Changesets.vanillaChangelogFunctions`, for consumers who want the vanilla changelog output without depending on that package directly.
|
|
298
|
+
|
|
297
299
|
---
|
|
298
300
|
|
|
299
301
|
### FileSystem + process layer required
|
|
@@ -408,6 +410,7 @@ const report = await Effect.runPromise(
|
|
|
408
410
|
- [Changeset config](./docs/04-changeset-config.md) — reading and decoding `.changeset/config.json`
|
|
409
411
|
- [Config discovery](./docs/05-config-discovery.md) — priority-based config file search
|
|
410
412
|
- [Biome sync](./docs/06-biome-sync.md) — keeping Biome `$schema` URLs current
|
|
413
|
+
- Changeset lint rules — [CSH001](./docs/rules/CSH001.md) through [CSH005](./docs/rules/CSH005.md), the section rules `ChangesetLinter` enforces
|
|
411
414
|
|
|
412
415
|
## License
|
|
413
416
|
|
package/changesets/api/linter.js
CHANGED
|
@@ -6,10 +6,10 @@ import { UncategorizedContentRule } from "../remark/rules/uncategorized-content.
|
|
|
6
6
|
import { stripFrontmatter } from "../utils/strip-frontmatter.js";
|
|
7
7
|
import remarkGfm from "remark-gfm";
|
|
8
8
|
import remarkParse from "remark-parse";
|
|
9
|
-
import remarkStringify from "remark-stringify";
|
|
10
9
|
import { unified } from "unified";
|
|
11
10
|
import { readFileSync, readdirSync } from "node:fs";
|
|
12
11
|
import { join } from "node:path";
|
|
12
|
+
import remarkStringify from "remark-stringify";
|
|
13
13
|
|
|
14
14
|
//#region src/changesets/api/linter.ts
|
|
15
15
|
/**
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { emitMarkdown } from "../utils/markdown-emit.js";
|
|
2
|
+
import { ContributorFootnotesPlugin } from "../remark/plugins/contributor-footnotes.js";
|
|
1
3
|
import { MaintenanceNotePlugin } from "../remark/plugins/maintenance-note.js";
|
|
2
4
|
import { SilkChangesetTransformPreset } from "../remark/presets.js";
|
|
3
5
|
import remarkGfm from "remark-gfm";
|
|
4
6
|
import remarkParse from "remark-parse";
|
|
5
|
-
import remarkStringify from "remark-stringify";
|
|
6
7
|
import { unified } from "unified";
|
|
7
8
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
8
9
|
|
|
@@ -92,8 +93,9 @@ var ChangelogTransformer = class ChangelogTransformer {
|
|
|
92
93
|
* @remarks
|
|
93
94
|
* The input is parsed with `remark-parse` and `remark-gfm` (for table
|
|
94
95
|
* support), processed through every plugin in {@link SilkChangesetTransformPreset}
|
|
95
|
-
* in order, and
|
|
96
|
-
*
|
|
96
|
+
* in order, and emitted back to markdown through the canonical
|
|
97
|
+
* `@effected/markdown` stringifier. The operation is synchronous and
|
|
98
|
+
* idempotent.
|
|
97
99
|
*
|
|
98
100
|
* @param content - Raw CHANGELOG markdown string (may contain multiple
|
|
99
101
|
* version blocks, GFM tables, footnotes, and reference links)
|
|
@@ -103,11 +105,12 @@ var ChangelogTransformer = class ChangelogTransformer {
|
|
|
103
105
|
*/
|
|
104
106
|
static transformContent(content, options) {
|
|
105
107
|
const processor = unified().use(remarkParse).use(remarkGfm);
|
|
106
|
-
for (const plugin of SilkChangesetTransformPreset) processor.use(
|
|
108
|
+
for (const plugin of SilkChangesetTransformPreset) if (plugin === ContributorFootnotesPlugin && options?.thanks !== void 0) processor.use(ContributorFootnotesPlugin, { thanks: options.thanks });
|
|
109
|
+
else processor.use(plugin);
|
|
107
110
|
if (options?.maintenance) processor.use(MaintenanceNotePlugin, options.maintenance);
|
|
108
|
-
processor.
|
|
109
|
-
const
|
|
110
|
-
return
|
|
111
|
+
const parsed = processor.parse(content);
|
|
112
|
+
const transformed = processor.runSync(parsed);
|
|
113
|
+
return emitMarkdown(transformed);
|
|
111
114
|
}
|
|
112
115
|
/**
|
|
113
116
|
* Transform a CHANGELOG file in-place.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveCommitType } from "../categories/index.js";
|
|
2
2
|
import { GitHubService } from "../services/github.js";
|
|
3
|
+
import { parseMarkdown, stringifyMarkdown } from "../utils/remark-pipeline.js";
|
|
3
4
|
import { GitHubInfoSchema } from "../schemas/github.js";
|
|
4
5
|
import { parseCommitMessage } from "../utils/commit-parser.js";
|
|
5
6
|
import { parseIssueReferences } from "../utils/issue-refs.js";
|
|
@@ -41,6 +42,105 @@ import { Effect, Schema } from "effect";
|
|
|
41
42
|
* @internal
|
|
42
43
|
*/
|
|
43
44
|
/**
|
|
45
|
+
* Stringify a single MDAST block node back to markdown, trimmed.
|
|
46
|
+
*
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
function stringifyNode(node) {
|
|
50
|
+
return stringifyMarkdown({
|
|
51
|
+
type: "root",
|
|
52
|
+
children: [node]
|
|
53
|
+
}).trimEnd();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Render one top-level section content node to markdown.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* The emit decision is made on the node type, never on string prefixes:
|
|
60
|
+
*
|
|
61
|
+
* - **paragraph** — rendered as a `- ` bullet, with continuation lines
|
|
62
|
+
* indented two spaces so multi-line prose stays inside the list item
|
|
63
|
+
* (the historical behavior for simple prose content).
|
|
64
|
+
* - **heading** — promoted one level so a changeset `###` sub-heading emits
|
|
65
|
+
* as `####` and can never collide with the changelog's depth-3 category
|
|
66
|
+
* headings (which the version-block utilities and merge/reorder/dedup
|
|
67
|
+
* plugins treat as section boundaries). Depths are clamped to the 4–6
|
|
68
|
+
* range: promoting below 4 could fabricate a version or category heading.
|
|
69
|
+
* - **everything else** (list, table, code fence, blockquote, ...) — passes
|
|
70
|
+
* through verbatim as a block: never bullet-prefixed, never
|
|
71
|
+
* continuation-indented.
|
|
72
|
+
*
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
function renderSectionNode(node) {
|
|
76
|
+
if (node.type === "paragraph") {
|
|
77
|
+
const [first, ...rest] = stringifyNode(node).split("\n");
|
|
78
|
+
return [`- ${first}`, ...rest.map((line) => line.length > 0 ? ` ${line}` : line)].join("\n");
|
|
79
|
+
}
|
|
80
|
+
if (node.type === "heading") {
|
|
81
|
+
const heading = node;
|
|
82
|
+
return stringifyNode({
|
|
83
|
+
...heading,
|
|
84
|
+
depth: Math.min(Math.max(heading.depth + 1, 4), 6)
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return stringifyNode(node);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Parse an attribution suffix (e.g. `" [#42](url) Thanks [\@user](url)!"`)
|
|
91
|
+
* into phrasing nodes ready to append to a paragraph, prefixed with a
|
|
92
|
+
* separating space.
|
|
93
|
+
*
|
|
94
|
+
* @internal
|
|
95
|
+
*/
|
|
96
|
+
function attributionPhrasing(attribution) {
|
|
97
|
+
const trimmed = attribution.trim();
|
|
98
|
+
if (trimmed === "") return void 0;
|
|
99
|
+
const first = parseMarkdown(trimmed).children[0];
|
|
100
|
+
if (first?.type !== "paragraph") return void 0;
|
|
101
|
+
return [{
|
|
102
|
+
type: "text",
|
|
103
|
+
value: " "
|
|
104
|
+
}, ...first.children];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Find the paragraph of a list's FINAL RENDERED bullet: the last paragraph
|
|
108
|
+
* of the last item, recursing into a trailing nested list first — for
|
|
109
|
+
* `- Parent` / ` - Child`, the child is the bullet a reader sees last, so
|
|
110
|
+
* the attribution must land there, not on the parent line above it.
|
|
111
|
+
*
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
function lastRenderedBulletParagraph(list) {
|
|
115
|
+
const lastItem = list.children[list.children.length - 1];
|
|
116
|
+
if (!lastItem) return void 0;
|
|
117
|
+
for (let i = lastItem.children.length - 1; i >= 0; i--) {
|
|
118
|
+
const child = lastItem.children[i];
|
|
119
|
+
if (child.type === "list") {
|
|
120
|
+
const nested = lastRenderedBulletParagraph(child);
|
|
121
|
+
if (nested) return nested;
|
|
122
|
+
} else if (child.type === "paragraph") return child;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Find the paragraph the attribution should be appended to: the LAST
|
|
127
|
+
* top-level paragraph, or the deepest trailing bullet's paragraph of the
|
|
128
|
+
* LAST top-level list (see {@link lastRenderedBulletParagraph}), across all
|
|
129
|
+
* section content nodes. Tables, headings, code fences and blockquotes are
|
|
130
|
+
* never targets — attribution must not land inside them.
|
|
131
|
+
*
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
function findAttributionTarget(sections) {
|
|
135
|
+
let target;
|
|
136
|
+
for (const section of sections) for (const node of section.contentNodes) if (node.type === "paragraph") target = node;
|
|
137
|
+
else if (node.type === "list") {
|
|
138
|
+
const lastParagraph = lastRenderedBulletParagraph(node);
|
|
139
|
+
if (lastParagraph) target = lastParagraph;
|
|
140
|
+
}
|
|
141
|
+
return target;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
44
144
|
* Format a single changeset into a markdown release line.
|
|
45
145
|
*
|
|
46
146
|
* This is the core Effect program that implements the `getReleaseLine` contract
|
|
@@ -86,30 +186,24 @@ function getReleaseLine(changeset, versionType, options) {
|
|
|
86
186
|
const commitMsg = parseCommitMessage(firstLine);
|
|
87
187
|
const bodyText = changeset.summary.split("\n").slice(1).join("\n");
|
|
88
188
|
const issueRefs = parseIssueReferences(bodyText);
|
|
89
|
-
const
|
|
189
|
+
const includeThanks = options.thanks !== false;
|
|
190
|
+
const attribution = commitInfo ? formatPRAndUserAttribution(commitInfo.pull ?? void 0, includeThanks ? commitInfo.user ?? void 0 : void 0, commitInfo.links) : "";
|
|
90
191
|
if (parsed.sections.length > 0) {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
192
|
+
const phrasing = attributionPhrasing(attribution);
|
|
193
|
+
let standaloneAttribution;
|
|
194
|
+
if (phrasing) {
|
|
195
|
+
const target = findAttributionTarget(parsed.sections);
|
|
196
|
+
if (target) target.children.push(...phrasing);
|
|
197
|
+
else standaloneAttribution = attribution.trim();
|
|
95
198
|
}
|
|
199
|
+
const blocks = [];
|
|
200
|
+
if (parsed.preamble) blocks.push(parsed.preamble);
|
|
96
201
|
for (const section of parsed.sections) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (section.content) {
|
|
100
|
-
const contentLines = section.content.split("\n");
|
|
101
|
-
const firstContentLine = contentLines[0];
|
|
102
|
-
if (firstContentLine.startsWith("- ") || firstContentLine.startsWith("* ")) {
|
|
103
|
-
lines.push(firstContentLine);
|
|
104
|
-
lines.push(...contentLines.slice(1));
|
|
105
|
-
} else {
|
|
106
|
-
lines.push(`- ${firstContentLine}`);
|
|
107
|
-
lines.push(...contentLines.slice(1).map((line) => line.length > 0 ? ` ${line}` : line));
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
lines.push("");
|
|
202
|
+
blocks.push(`### ${section.category.heading}`);
|
|
203
|
+
for (const node of section.contentNodes) blocks.push(renderSectionNode(node));
|
|
111
204
|
}
|
|
112
|
-
|
|
205
|
+
if (standaloneAttribution) blocks.push(standaloneAttribution);
|
|
206
|
+
return blocks.join("\n\n");
|
|
113
207
|
}
|
|
114
208
|
const commitType = commitMsg.type ?? versionType;
|
|
115
209
|
const entryInput = {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import changelogGit from "@changesets/changelog-git";
|
|
2
|
+
|
|
3
|
+
//#region src/changesets/changelog/vanilla.ts
|
|
4
|
+
/**
|
|
5
|
+
* The vanilla changesets changelog renderer — `\@changesets/changelog-git`,
|
|
6
|
+
* re-exported.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Some consumers (notably `silk-release-action`) need the STOCK changesets
|
|
10
|
+
* rendering — plain summary lines with commit hashes, no sections, no
|
|
11
|
+
* attribution, no dependency tables — as a fallback or comparison path
|
|
12
|
+
* alongside the silk formatter. Re-exporting the upstream implementation
|
|
13
|
+
* here means those consumers depend only on this package: they get exact
|
|
14
|
+
* parity with `\@changesets/changelog-git` (this is a re-export, never a
|
|
15
|
+
* reimplementation) without declaring the dependency themselves.
|
|
16
|
+
*
|
|
17
|
+
* @see {@link changelogFunctions} in `./index.ts` for the silk formatter
|
|
18
|
+
* this is the vanilla alternative to
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The stock `\@changesets/changelog-git` changelog implementation.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* This is the exact upstream default export (identity-equal, pinned by
|
|
25
|
+
* test), typed against this package's re-exported {@link ChangelogFunctions}
|
|
26
|
+
* interface. Use it wherever the plain changesets rendering is wanted
|
|
27
|
+
* instead of the silk section-aware formatter — e.g. in
|
|
28
|
+
* `.changeset/config.json` via a changelog module that forwards it, or
|
|
29
|
+
* programmatically from a release pipeline.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { Changesets } from "@savvy-web/silk-effects";
|
|
34
|
+
*
|
|
35
|
+
* const line = await Changesets.vanillaChangelogFunctions.getReleaseLine(
|
|
36
|
+
* { id: "x", summary: "Fix a thing", releases: [{ name: "pkg", type: "patch" }] },
|
|
37
|
+
* "patch",
|
|
38
|
+
* null,
|
|
39
|
+
* );
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
const vanillaChangelogFunctions = changelogGit;
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { vanillaChangelogFunctions };
|
package/changesets/constants.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
//#region src/changesets/constants.ts
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Base URL for rule documentation on GitHub.
|
|
4
|
+
*
|
|
5
|
+
* The former home (savvy-web/changesets) is archived; the docs live in the
|
|
6
|
+
* systems monorepo alongside the rule sources (savvy-web/systems#456).
|
|
7
|
+
*/
|
|
8
|
+
const DOCS_BASE = "https://github.com/savvy-web/systems/blob/main/packages/silk-effects/docs/rules";
|
|
4
9
|
/**
|
|
5
10
|
* Documentation URLs for each changeset lint rule.
|
|
6
11
|
*
|
package/changesets/index.js
CHANGED
|
@@ -18,9 +18,9 @@ import { HeadingHierarchyRule } from "./remark/rules/heading-hierarchy.js";
|
|
|
18
18
|
import { RequiredSectionsRule } from "./remark/rules/required-sections.js";
|
|
19
19
|
import { UncategorizedContentRule } from "./remark/rules/uncategorized-content.js";
|
|
20
20
|
import { ChangesetLinter } from "./api/linter.js";
|
|
21
|
+
import { ContributorFootnotesPlugin } from "./remark/plugins/contributor-footnotes.js";
|
|
21
22
|
import { MaintenanceNotePlugin } from "./remark/plugins/maintenance-note.js";
|
|
22
23
|
import { AggregateDependencyTablesPlugin } from "./remark/plugins/aggregate-dependency-tables.js";
|
|
23
|
-
import { ContributorFootnotesPlugin } from "./remark/plugins/contributor-footnotes.js";
|
|
24
24
|
import { DeduplicateItemsPlugin } from "./remark/plugins/deduplicate-items.js";
|
|
25
25
|
import { IssueLinkRefsPlugin } from "./remark/plugins/issue-link-refs.js";
|
|
26
26
|
import { MergeSectionsPlugin } from "./remark/plugins/merge-sections.js";
|
|
@@ -28,13 +28,14 @@ import { NormalizeFormatPlugin } from "./remark/plugins/normalize-format.js";
|
|
|
28
28
|
import { ReorderSectionsPlugin } from "./remark/plugins/reorder-sections.js";
|
|
29
29
|
import { SilkChangesetPreset, SilkChangesetTransformPreset } from "./remark/presets.js";
|
|
30
30
|
import { ChangelogTransformer } from "./api/transformer.js";
|
|
31
|
+
import { vanillaChangelogFunctions } from "./changelog/vanilla.js";
|
|
31
32
|
import { ClassificationReasonSchema, ClassificationSchema, ConfigInspector, InspectedConfigSchema, ResolvedPackageScopeSchema, ResolvedVersionFileSchema, makeConfigInspectorTest } from "./services/config-inspector.js";
|
|
32
33
|
import { BranchAnalysisSchema, BranchAnalyzer, BranchFileEntrySchema, FileStatusSchema, makeBranchAnalyzerTest } from "./services/branch-analyzer.js";
|
|
33
34
|
import { ChangelogService } from "./services/changelog.js";
|
|
34
35
|
import { computeWorkspaceDependencyDiffs } from "./utils/dep-diff.js";
|
|
35
36
|
import { gitMergeBase } from "./utils/git.js";
|
|
36
37
|
import { listPublishablePackageNames } from "./utils/publishability.js";
|
|
37
|
-
import { DepsRegen, DepsRegenDefault, isPureDependencyChangeset, makeDepsRegenDefault } from "./services/deps-regen.js";
|
|
38
|
+
import { DepsRegen, DepsRegenDefault, isPureDependencyChangeset, makeDepsRegenDefault, parseChangesetPackages } from "./services/deps-regen.js";
|
|
38
39
|
import { MaintenanceReasonSchema, MaintenanceTriggerSchema, deriveMaintenanceReason } from "./services/maintenance-reason.js";
|
|
39
40
|
import { VersionFiles } from "./utils/version-files.js";
|
|
40
41
|
import { ReleasePlanner, makeReleasePlannerTest } from "./services/release-planner.js";
|
|
@@ -150,8 +151,10 @@ var changesets_exports = /* @__PURE__ */ __exportAll({
|
|
|
150
151
|
makeDepsRegenDefault: () => makeDepsRegenDefault,
|
|
151
152
|
makeGitHubTest: () => makeGitHubTest,
|
|
152
153
|
makeReleasePlannerTest: () => makeReleasePlannerTest,
|
|
153
|
-
|
|
154
|
+
parseChangesetPackages: () => parseChangesetPackages,
|
|
155
|
+
serializeDependencyTableToMarkdown: () => serializeDependencyTableToMarkdown,
|
|
156
|
+
vanillaChangelogFunctions: () => vanillaChangelogFunctions
|
|
154
157
|
});
|
|
155
158
|
|
|
156
159
|
//#endregion
|
|
157
|
-
export { AggregateDependencyTablesPlugin, AppliedReleaseEntrySchema, AppliedReleaseSchema, BranchAnalysisSchema, BranchAnalyzer, BranchFileEntrySchema, BumpTypeSchema, Categories, Changelog, ChangelogService, ChangelogTransformer, ChangesetIOError, ChangesetLinter, ChangesetOptionsSchema, ChangesetPreviewSchema, ChangesetSchema, ChangesetSummarySchema, ChangesetValidationError, ClassificationReasonSchema, ClassificationSchema, CommitHashSchema, ConfigInspector, ConfigurationError, ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, DependencyActionSchema, DependencyTable, DependencyTableFormatRule, DependencyTableRowSchema, DependencyTableSchema, DependencyTableTypeSchema, DependencyTypeSchema, DependencyUpdateSchema, DepsRegen, DepsRegenDefault, FileStatusSchema, GitError, GitHubApiError, GitHubInfoSchema, GitHubService, GlobSchema, HeadingHierarchyRule, InspectedConfigSchema, IssueLinkRefsPlugin, IssueNumberSchema, JsonPathSchema, LegacyVersionFileConfigSchema, LegacyVersionFilesSchema, MaintenanceNotePlugin, MaintenanceReasonSchema, MaintenanceTriggerSchema, MarkdownParseError, ContentStructureRule$1 as MarkdownlintContentStructureRule, DependencyTableFormatRule$1 as MarkdownlintDependencyTableFormatRule, HeadingHierarchyRule$1 as MarkdownlintHeadingHierarchyRule, RequiredSectionsRule$1 as MarkdownlintRequiredSectionsRule, UncategorizedContentRule$1 as MarkdownlintUncategorizedContentRule, MergeSectionsPlugin, NonEmptyString, NormalizeFormatPlugin, PackageScopeSchema, PackagesRecordSchema, PendingChangesetSchema, PositiveInteger, PreviewReleaseSchema, ReleasePlanError, ReleasePlanner, ReorderSectionsPlugin, RepoSchema, RequiredSectionsRule, ResolvedPackageScopeSchema, ResolvedVersionFileSchema, SectionCategorySchema, SilkChangesetPreset, SilkChangesetTransformPreset, SilkChangesetsRules, UncategorizedContentRule, UrlOrMarkdownLinkSchema, UsernameSchema, VERSION_RE, VersionFileConfigSchema, VersionFileError, VersionFileUpdateRecordSchema, VersionFiles, VersionFilesSchema, VersionOrEmptySchema, VersionTypeSchema, changelogFunctions, changesets_exports, computeWorkspaceDependencyDiffs, deriveMaintenanceReason, gitMergeBase, isPureDependencyChangeset, listPublishablePackageNames, makeBranchAnalyzerTest, makeConfigInspectorTest, makeDepsRegenDefault, makeGitHubTest, makeReleasePlannerTest, serializeDependencyTableToMarkdown };
|
|
160
|
+
export { AggregateDependencyTablesPlugin, AppliedReleaseEntrySchema, AppliedReleaseSchema, BranchAnalysisSchema, BranchAnalyzer, BranchFileEntrySchema, BumpTypeSchema, Categories, Changelog, ChangelogService, ChangelogTransformer, ChangesetIOError, ChangesetLinter, ChangesetOptionsSchema, ChangesetPreviewSchema, ChangesetSchema, ChangesetSummarySchema, ChangesetValidationError, ClassificationReasonSchema, ClassificationSchema, CommitHashSchema, ConfigInspector, ConfigurationError, ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, DependencyActionSchema, DependencyTable, DependencyTableFormatRule, DependencyTableRowSchema, DependencyTableSchema, DependencyTableTypeSchema, DependencyTypeSchema, DependencyUpdateSchema, DepsRegen, DepsRegenDefault, FileStatusSchema, GitError, GitHubApiError, GitHubInfoSchema, GitHubService, GlobSchema, HeadingHierarchyRule, InspectedConfigSchema, IssueLinkRefsPlugin, IssueNumberSchema, JsonPathSchema, LegacyVersionFileConfigSchema, LegacyVersionFilesSchema, MaintenanceNotePlugin, MaintenanceReasonSchema, MaintenanceTriggerSchema, MarkdownParseError, ContentStructureRule$1 as MarkdownlintContentStructureRule, DependencyTableFormatRule$1 as MarkdownlintDependencyTableFormatRule, HeadingHierarchyRule$1 as MarkdownlintHeadingHierarchyRule, RequiredSectionsRule$1 as MarkdownlintRequiredSectionsRule, UncategorizedContentRule$1 as MarkdownlintUncategorizedContentRule, MergeSectionsPlugin, NonEmptyString, NormalizeFormatPlugin, PackageScopeSchema, PackagesRecordSchema, PendingChangesetSchema, PositiveInteger, PreviewReleaseSchema, ReleasePlanError, ReleasePlanner, ReorderSectionsPlugin, RepoSchema, RequiredSectionsRule, ResolvedPackageScopeSchema, ResolvedVersionFileSchema, SectionCategorySchema, SilkChangesetPreset, SilkChangesetTransformPreset, SilkChangesetsRules, UncategorizedContentRule, UrlOrMarkdownLinkSchema, UsernameSchema, VERSION_RE, VersionFileConfigSchema, VersionFileError, VersionFileUpdateRecordSchema, VersionFiles, VersionFilesSchema, VersionOrEmptySchema, VersionTypeSchema, changelogFunctions, changesets_exports, computeWorkspaceDependencyDiffs, deriveMaintenanceReason, gitMergeBase, isPureDependencyChangeset, listPublishablePackageNames, makeBranchAnalyzerTest, makeConfigInspectorTest, makeDepsRegenDefault, makeGitHubTest, makeReleasePlannerTest, parseChangesetPackages, serializeDependencyTableToMarkdown, vanillaChangelogFunctions };
|
|
@@ -44,7 +44,7 @@ function hasContentBetween(tokens, currentIdx, nextIdx) {
|
|
|
44
44
|
* }
|
|
45
45
|
* ```
|
|
46
46
|
*
|
|
47
|
-
* @see {@link https://github.com/savvy-web/
|
|
47
|
+
* @see {@link https://github.com/savvy-web/systems/blob/main/packages/silk-effects/docs/rules/CSH003.md | CSH003 rule documentation}
|
|
48
48
|
* @see `src/remark/rules/content-structure.ts` for the corresponding remark-lint rule
|
|
49
49
|
*
|
|
50
50
|
* @public
|
|
@@ -57,7 +57,7 @@ const ContentStructureRule = {
|
|
|
57
57
|
function: function CSH003(params, onError) {
|
|
58
58
|
const tokens = params.parsers.micromark.tokens;
|
|
59
59
|
const h2Indices = [];
|
|
60
|
-
for (let i = 0; i < tokens.length; i++) if (tokens[i].type === "atxHeading" && getHeadingLevel(tokens[i]) === 2) h2Indices.push(i);
|
|
60
|
+
for (let i = 0; i < tokens.length; i++) if ((tokens[i].type === "atxHeading" || tokens[i].type === "setextHeading") && getHeadingLevel(tokens[i]) === 2) h2Indices.push(i);
|
|
61
61
|
for (let i = 0; i < h2Indices.length; i++) {
|
|
62
62
|
const currentIdx = h2Indices[i];
|
|
63
63
|
if (!hasContentBetween(tokens, currentIdx, i + 1 < h2Indices.length ? h2Indices[i + 1] : tokens.length)) onError({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { VERSION_RE } from "../../schemas/dependency-table.js";
|
|
2
2
|
import { RULE_DOCS } from "../../constants.js";
|
|
3
|
+
import { scanDependencySection } from "../../utils/dependency-section.js";
|
|
3
4
|
import { getHeadingLevel, getHeadingText, unescapeMarkdown } from "./utils.js";
|
|
4
5
|
|
|
5
6
|
//#region src/changesets/markdownlint/rules/dependency-table-format.ts
|
|
@@ -10,7 +11,9 @@ const VALID_TYPES = /* @__PURE__ */ new Set([
|
|
|
10
11
|
"peerDependency",
|
|
11
12
|
"optionalDependency",
|
|
12
13
|
"workspace",
|
|
13
|
-
"config"
|
|
14
|
+
"config",
|
|
15
|
+
"runtime",
|
|
16
|
+
"packageManager"
|
|
14
17
|
]);
|
|
15
18
|
const VALID_ACTIONS = /* @__PURE__ */ new Set([
|
|
16
19
|
"added",
|
|
@@ -64,19 +67,17 @@ const DependencyTableFormatRule = {
|
|
|
64
67
|
const tokens = params.parsers.micromark.tokens;
|
|
65
68
|
for (let i = 0; i < tokens.length; i++) {
|
|
66
69
|
const token = tokens[i];
|
|
67
|
-
if (token.type !== "atxHeading") continue;
|
|
70
|
+
if (token.type !== "atxHeading" && token.type !== "setextHeading") continue;
|
|
68
71
|
if (getHeadingLevel(token) !== 2) continue;
|
|
69
72
|
if (getHeadingText(token).toLowerCase() !== "dependencies") continue;
|
|
70
73
|
const headingLine = token.startLine;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
if (tableToken === null) {
|
|
74
|
+
const { table } = scanDependencySection(tokens, i + 1, {
|
|
75
|
+
isSkippable: (t) => t.type === "lineEnding" || t.type === "lineEndingBlank",
|
|
76
|
+
isHeading: (t) => t.type === "atxHeading" || t.type === "setextHeading",
|
|
77
|
+
isTable: (t) => t.type === "table"
|
|
78
|
+
});
|
|
79
|
+
const tableToken = table;
|
|
80
|
+
if (tableToken === void 0) {
|
|
80
81
|
onError({
|
|
81
82
|
lineNumber: headingLine,
|
|
82
83
|
detail: `Dependencies section must contain a table, not a list or paragraph. See: ${RULE_DOCS.CSH005}`
|
|
@@ -26,7 +26,7 @@ import { getHeadingLevel } from "./utils.js";
|
|
|
26
26
|
* }
|
|
27
27
|
* ```
|
|
28
28
|
*
|
|
29
|
-
* @see {@link https://github.com/savvy-web/
|
|
29
|
+
* @see {@link https://github.com/savvy-web/systems/blob/main/packages/silk-effects/docs/rules/CSH001.md | CSH001 rule documentation}
|
|
30
30
|
* @see `src/remark/rules/heading-hierarchy.ts` for the corresponding remark-lint rule
|
|
31
31
|
*
|
|
32
32
|
* @public
|
|
@@ -39,7 +39,7 @@ const HeadingHierarchyRule = {
|
|
|
39
39
|
function: function CSH001(params, onError) {
|
|
40
40
|
let prevDepth = 0;
|
|
41
41
|
for (const token of params.parsers.micromark.tokens) {
|
|
42
|
-
if (token.type !== "atxHeading") continue;
|
|
42
|
+
if (token.type !== "atxHeading" && token.type !== "setextHeading") continue;
|
|
43
43
|
const depth = getHeadingLevel(token);
|
|
44
44
|
if (depth === 1) {
|
|
45
45
|
onError({
|
|
@@ -27,7 +27,7 @@ import { getHeadingLevel, getHeadingText } from "./utils.js";
|
|
|
27
27
|
* }
|
|
28
28
|
* ```
|
|
29
29
|
*
|
|
30
|
-
* @see {@link https://github.com/savvy-web/
|
|
30
|
+
* @see {@link https://github.com/savvy-web/systems/blob/main/packages/silk-effects/docs/rules/CSH002.md | CSH002 rule documentation}
|
|
31
31
|
* @see `src/remark/rules/required-sections.ts` for the corresponding remark-lint rule
|
|
32
32
|
*
|
|
33
33
|
* @public
|
|
@@ -39,7 +39,7 @@ const RequiredSectionsRule = {
|
|
|
39
39
|
parser: "micromark",
|
|
40
40
|
function: function CSH002(params, onError) {
|
|
41
41
|
for (const token of params.parsers.micromark.tokens) {
|
|
42
|
-
if (token.type !== "atxHeading") continue;
|
|
42
|
+
if (token.type !== "atxHeading" && token.type !== "setextHeading") continue;
|
|
43
43
|
if (getHeadingLevel(token) !== 2) continue;
|
|
44
44
|
const text = getHeadingText(token);
|
|
45
45
|
if (!isValidHeading(text)) onError({
|
|
@@ -27,7 +27,7 @@ import { getHeadingLevel } from "./utils.js";
|
|
|
27
27
|
* }
|
|
28
28
|
* ```
|
|
29
29
|
*
|
|
30
|
-
* @see {@link https://github.com/savvy-web/
|
|
30
|
+
* @see {@link https://github.com/savvy-web/systems/blob/main/packages/silk-effects/docs/rules/CSH004.md | CSH004 rule documentation}
|
|
31
31
|
* @see `src/remark/rules/uncategorized-content.ts` for the corresponding remark-lint rule
|
|
32
32
|
*
|
|
33
33
|
* @public
|
|
@@ -40,9 +40,9 @@ const UncategorizedContentRule = {
|
|
|
40
40
|
function: function CSH004(params, onError) {
|
|
41
41
|
const tokens = params.parsers.micromark.tokens;
|
|
42
42
|
for (const token of tokens) {
|
|
43
|
-
if (token.type === "atxHeading" && getHeadingLevel(token) === 2) break;
|
|
43
|
+
if ((token.type === "atxHeading" || token.type === "setextHeading") && getHeadingLevel(token) === 2) break;
|
|
44
44
|
if (token.type === "lineEnding" || token.type === "lineEndingBlank" || token.type === "htmlFlow") continue;
|
|
45
|
-
if (token.type !== "atxHeading") onError({
|
|
45
|
+
if (token.type !== "atxHeading" && token.type !== "setextHeading") onError({
|
|
46
46
|
lineNumber: token.startLine,
|
|
47
47
|
detail: `Content must be placed under a category heading (## heading). Move this content under an appropriate section like "## Features" or "## Bug Fixes". If it doesn't fit an existing category, use "## Other". See: ${RULE_DOCS.CSH004}`
|
|
48
48
|
});
|
|
@@ -2,14 +2,25 @@ import { RULE_DOCS } from "../../constants.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/changesets/markdownlint/rules/utils.ts
|
|
4
4
|
/**
|
|
5
|
-
* Get the heading level (1-6) from an `atxHeading` token.
|
|
5
|
+
* Get the heading level (1-6) from an `atxHeading` or `setextHeading` token.
|
|
6
6
|
*
|
|
7
|
-
* @
|
|
8
|
-
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* remark-parse normalizes setext headings to plain depth-1/2 heading nodes,
|
|
9
|
+
* so the remark siblings of these rules see them as ordinary headings. The
|
|
10
|
+
* micromark extractors must agree, or the two engines disagree about the
|
|
11
|
+
* same file (the issue #367 class of drift).
|
|
12
|
+
*
|
|
13
|
+
* @param heading - The `atxHeading` or `setextHeading` micromark token
|
|
14
|
+
* @returns The heading depth (`#` count, or 1/2 for `=`/`-` setext
|
|
15
|
+
* underlines), or 0 if no sequence found
|
|
9
16
|
*
|
|
10
17
|
* @internal
|
|
11
18
|
*/
|
|
12
19
|
function getHeadingLevel(heading) {
|
|
20
|
+
if (heading.type === "setextHeading") {
|
|
21
|
+
const line = heading.children.find((c) => c.type === "setextHeadingLine");
|
|
22
|
+
return line ? line.text.startsWith("=") ? 1 : 2 : 0;
|
|
23
|
+
}
|
|
13
24
|
const sequence = heading.children.find((c) => c.type === "atxHeadingSequence");
|
|
14
25
|
return sequence ? sequence.text.length : 0;
|
|
15
26
|
}
|
|
@@ -39,20 +50,20 @@ function unescapeMarkdown(raw) {
|
|
|
39
50
|
return raw.replace(BACKSLASH_ESCAPE_RE, "$1");
|
|
40
51
|
}
|
|
41
52
|
/**
|
|
42
|
-
* Get the plain text content of an `atxHeading` token.
|
|
53
|
+
* Get the plain text content of an `atxHeading` or `setextHeading` token.
|
|
43
54
|
*
|
|
44
55
|
* @remarks
|
|
45
56
|
* Escape-resolved, so a heading compares equal to the same heading as the
|
|
46
57
|
* remark rules see it. `getHeadingLevel` needs no equivalent — it counts
|
|
47
58
|
* sequence characters rather than reading text.
|
|
48
59
|
*
|
|
49
|
-
* @param heading - The `atxHeading` micromark token
|
|
60
|
+
* @param heading - The `atxHeading` or `setextHeading` micromark token
|
|
50
61
|
* @returns The heading text, or empty string if no text token found
|
|
51
62
|
*
|
|
52
63
|
* @internal
|
|
53
64
|
*/
|
|
54
65
|
function getHeadingText(heading) {
|
|
55
|
-
const textToken = heading.children.find((c) => c.type === "atxHeadingText");
|
|
66
|
+
const textToken = heading.children.find((c) => c.type === "atxHeadingText" || c.type === "setextHeadingText");
|
|
56
67
|
return textToken ? unescapeMarkdown(textToken.text) : "";
|
|
57
68
|
}
|
|
58
69
|
|
|
@@ -2,6 +2,52 @@ import { collapseDependencyRows, parseDependencyTable, serializeDependencyTable,
|
|
|
2
2
|
import { getBlockSections, getHeadingText, getVersionBlocks } from "../../utils/version-blocks.js";
|
|
3
3
|
|
|
4
4
|
//#region src/changesets/remark/plugins/aggregate-dependency-tables.ts
|
|
5
|
+
/**
|
|
6
|
+
* Unwrap dependency tables that an earlier (broken) formatter nested inside
|
|
7
|
+
* list items.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* Historical CHANGELOG blocks (e.g. silk\@3.10.0) carry an authored
|
|
11
|
+
* dependency table bullet-wrapped into a list item, which the aggregation
|
|
12
|
+
* pass otherwise cannot recognize. Each list item's tables that parse as
|
|
13
|
+
* dependency tables contribute their rows; ONLY those tables leave the
|
|
14
|
+
* list. The non-table items (e.g. an explanatory bullet alongside the
|
|
15
|
+
* wrapped table) keep their bullet structure: they survive as a List node
|
|
16
|
+
* in the legacy content rather than being flattened to bare paragraphs.
|
|
17
|
+
* When no table in the list parses, the caller keeps the whole list as
|
|
18
|
+
* legacy content untouched.
|
|
19
|
+
*
|
|
20
|
+
* @param list - A list node found inside a `### Dependencies` section
|
|
21
|
+
* @returns Parsed rows plus the remaining non-table content (a pruned copy
|
|
22
|
+
* of the list, when any items survive)
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
function extractTablesFromList(list) {
|
|
27
|
+
const rows = [];
|
|
28
|
+
const keptItems = [];
|
|
29
|
+
for (const item of list.children) {
|
|
30
|
+
const keptChildren = [];
|
|
31
|
+
for (const child of item.children) {
|
|
32
|
+
if (child.type === "table") try {
|
|
33
|
+
rows.push(...parseDependencyTable(child));
|
|
34
|
+
continue;
|
|
35
|
+
} catch {}
|
|
36
|
+
keptChildren.push(child);
|
|
37
|
+
}
|
|
38
|
+
if (keptChildren.length > 0) keptItems.push({
|
|
39
|
+
...item,
|
|
40
|
+
children: keptChildren
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
rows,
|
|
45
|
+
rest: keptItems.length > 0 ? [{
|
|
46
|
+
...list,
|
|
47
|
+
children: keptItems
|
|
48
|
+
}] : []
|
|
49
|
+
};
|
|
50
|
+
}
|
|
5
51
|
const AggregateDependencyTablesPlugin = () => {
|
|
6
52
|
return (tree) => {
|
|
7
53
|
const blocks = getVersionBlocks(tree);
|
|
@@ -16,7 +62,13 @@ const AggregateDependencyTablesPlugin = () => {
|
|
|
16
62
|
} catch {
|
|
17
63
|
legacyContent.push(node);
|
|
18
64
|
}
|
|
19
|
-
else
|
|
65
|
+
else if (node.type === "list") {
|
|
66
|
+
const extracted = extractTablesFromList(node);
|
|
67
|
+
if (extracted.rows.length > 0) {
|
|
68
|
+
allRows.push(...extracted.rows);
|
|
69
|
+
legacyContent.push(...extracted.rest);
|
|
70
|
+
} else legacyContent.push(node);
|
|
71
|
+
} else legacyContent.push(node);
|
|
20
72
|
const collapsed = sortDependencyRows(collapseDependencyRows(allRows));
|
|
21
73
|
const indicesToRemove = [];
|
|
22
74
|
for (const section of depSections) {
|