@savvy-web/silk-effects 0.6.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +48 -17
  2. package/_virtual/_rolldown/runtime.js +18 -0
  3. package/changesets/api/categories.js +247 -0
  4. package/changesets/api/changelog.js +134 -0
  5. package/changesets/api/dependency-table.js +163 -0
  6. package/changesets/api/linter.js +168 -0
  7. package/changesets/api/transformer.js +140 -0
  8. package/changesets/categories/index.js +299 -0
  9. package/changesets/categories/types.js +66 -0
  10. package/changesets/changelog/formatting.js +119 -0
  11. package/changesets/changelog/getDependencyReleaseLine.js +114 -0
  12. package/changesets/changelog/getReleaseLine.js +122 -0
  13. package/changesets/changelog/index.js +99 -0
  14. package/changesets/constants.js +43 -0
  15. package/changesets/errors.js +305 -0
  16. package/changesets/index.js +146 -0
  17. package/changesets/markdownlint/index.js +29 -0
  18. package/changesets/markdownlint/rules/content-structure.js +98 -0
  19. package/changesets/markdownlint/rules/dependency-table-format.js +170 -0
  20. package/changesets/markdownlint/rules/heading-hierarchy.js +61 -0
  21. package/changesets/markdownlint/rules/required-sections.js +54 -0
  22. package/changesets/markdownlint/rules/uncategorized-content.js +54 -0
  23. package/changesets/markdownlint/rules/utils.js +30 -0
  24. package/changesets/remark/plugins/aggregate-dependency-tables.js +47 -0
  25. package/changesets/remark/plugins/contributor-footnotes.js +123 -0
  26. package/changesets/remark/plugins/deduplicate-items.js +30 -0
  27. package/changesets/remark/plugins/issue-link-refs.js +58 -0
  28. package/changesets/remark/plugins/merge-sections.js +43 -0
  29. package/changesets/remark/plugins/normalize-format.js +47 -0
  30. package/changesets/remark/plugins/reorder-sections.js +34 -0
  31. package/changesets/remark/presets.js +119 -0
  32. package/changesets/remark/rules/content-structure.js +22 -0
  33. package/changesets/remark/rules/dependency-table-format.js +40 -0
  34. package/changesets/remark/rules/heading-hierarchy.js +19 -0
  35. package/changesets/remark/rules/required-sections.js +17 -0
  36. package/changesets/remark/rules/uncategorized-content.js +31 -0
  37. package/changesets/schemas/changeset.js +146 -0
  38. package/changesets/schemas/dependency-table.js +189 -0
  39. package/changesets/schemas/git.js +69 -0
  40. package/changesets/schemas/github.js +175 -0
  41. package/changesets/schemas/options.js +182 -0
  42. package/changesets/schemas/package-scope.js +128 -0
  43. package/changesets/schemas/primitives.js +72 -0
  44. package/changesets/schemas/version-files.js +151 -0
  45. package/changesets/services/branch-analyzer.js +278 -0
  46. package/changesets/services/changelog.js +50 -0
  47. package/changesets/services/config-inspector.js +390 -0
  48. package/changesets/services/github.js +178 -0
  49. package/changesets/services/markdown.js +106 -0
  50. package/changesets/services/workspace-snapshot.js +182 -0
  51. package/changesets/utils/commit-parser.js +80 -0
  52. package/changesets/utils/dep-diff.js +77 -0
  53. package/changesets/utils/dependency-table.js +347 -0
  54. package/changesets/utils/issue-refs.js +101 -0
  55. package/changesets/utils/jsonpath.js +175 -0
  56. package/changesets/utils/logger.js +50 -0
  57. package/changesets/utils/markdown-link.js +57 -0
  58. package/changesets/utils/publishability.js +39 -0
  59. package/changesets/utils/remark-pipeline.js +79 -0
  60. package/changesets/utils/section-parser.js +94 -0
  61. package/changesets/utils/strip-frontmatter.js +46 -0
  62. package/changesets/utils/version-blocks.js +108 -0
  63. package/changesets/utils/version-files.js +336 -0
  64. package/changesets/utils/worktree-snapshot.js +142 -0
  65. package/changesets/vendor/github-info.js +55 -0
  66. package/commitlint/config/factory.js +69 -0
  67. package/commitlint/config/plugins.js +227 -0
  68. package/commitlint/config/rules.js +155 -0
  69. package/commitlint/config/schema.js +46 -0
  70. package/commitlint/detection/dco.js +53 -0
  71. package/commitlint/detection/scopes.js +45 -0
  72. package/commitlint/formatter/format.js +85 -0
  73. package/commitlint/formatter/messages.js +79 -0
  74. package/commitlint/hook/diagnostics/branch.js +36 -0
  75. package/commitlint/hook/diagnostics/cache.js +37 -0
  76. package/commitlint/hook/diagnostics/commitlint-config.js +36 -0
  77. package/commitlint/hook/diagnostics/open-issues.js +56 -0
  78. package/commitlint/hook/diagnostics/package-manager.js +51 -0
  79. package/commitlint/hook/diagnostics/signing.js +107 -0
  80. package/commitlint/hook/envelope.js +46 -0
  81. package/commitlint/hook/output.js +45 -0
  82. package/commitlint/hook/parse-bash-command.js +105 -0
  83. package/commitlint/hook/rules/closes-trailer.js +31 -0
  84. package/commitlint/hook/rules/forbidden-content.js +32 -0
  85. package/commitlint/hook/rules/plan-leakage.js +36 -0
  86. package/commitlint/hook/rules/signing-flag-conflict.js +25 -0
  87. package/commitlint/hook/rules/soft-wrap.js +37 -0
  88. package/commitlint/hook/rules/types.js +14 -0
  89. package/commitlint/hook/rules/verbosity.js +31 -0
  90. package/commitlint/hook/silence-logger.js +39 -0
  91. package/commitlint/index.js +146 -0
  92. package/commitlint/prompt/config.js +91 -0
  93. package/commitlint/prompt/emojis.js +74 -0
  94. package/commitlint/prompt/prompter.js +135 -0
  95. package/commitlint/static.js +73 -0
  96. package/errors/BiomeSyncError.js +21 -0
  97. package/errors/ChangesetConfigError.js +20 -0
  98. package/errors/ConfigNotFoundError.js +21 -0
  99. package/errors/SectionParseError.js +16 -0
  100. package/errors/SectionValidationError.js +16 -0
  101. package/errors/SectionWriteError.js +16 -0
  102. package/errors/TagFormatError.js +20 -0
  103. package/errors/ToolNotFoundError.js +11 -0
  104. package/errors/ToolResolutionError.js +11 -0
  105. package/errors/ToolVersionMismatchError.js +11 -0
  106. package/errors/VersioningDetectionError.js +20 -0
  107. package/errors/WorkspaceAnalysisError.js +21 -0
  108. package/index.d.ts +9743 -8380
  109. package/index.js +36 -6657
  110. package/lint/Handler.js +39 -0
  111. package/lint/cli/sections.js +65 -0
  112. package/lint/cli/templates/markdownlint.gen.js +183 -0
  113. package/lint/config/Preset.js +152 -0
  114. package/lint/config/createConfig.js +89 -0
  115. package/lint/handlers/Biome.js +179 -0
  116. package/lint/handlers/Markdown.js +139 -0
  117. package/lint/handlers/PackageJson.js +130 -0
  118. package/lint/handlers/PnpmWorkspace.js +141 -0
  119. package/lint/handlers/ShellScripts.js +58 -0
  120. package/lint/handlers/TypeScript.js +134 -0
  121. package/lint/handlers/Yaml.js +167 -0
  122. package/lint/index.js +52 -0
  123. package/lint/utils/Command.js +285 -0
  124. package/lint/utils/Filter.js +100 -0
  125. package/lint/utils/Workspace.js +86 -0
  126. package/package.json +52 -63
  127. package/schemas/CommentStyle.js +16 -0
  128. package/schemas/ResolvedTool.js +63 -0
  129. package/schemas/SavvySections.js +113 -0
  130. package/schemas/SectionBlock.js +70 -0
  131. package/schemas/SectionDefinition.js +121 -0
  132. package/schemas/SectionResults.js +12 -0
  133. package/schemas/TagStrategySchemas.js +18 -0
  134. package/schemas/ToolDefinition.js +39 -0
  135. package/schemas/ToolResults.js +14 -0
  136. package/schemas/VersioningSchemas.js +95 -0
  137. package/schemas/WorkspaceAnalysisSchemas.js +190 -0
  138. package/services/BiomeSchemaSync.js +133 -0
  139. package/services/ChangesetConfig.js +78 -0
  140. package/services/ChangesetConfigReader.js +106 -0
  141. package/services/ConfigDiscovery.js +71 -0
  142. package/services/ManagedSection.js +288 -0
  143. package/services/SilkPublishability.js +193 -0
  144. package/services/SilkWorkspaceAnalyzer.js +213 -0
  145. package/services/TagStrategy.js +54 -0
  146. package/services/ToolDiscovery.js +229 -0
  147. package/services/VersioningStrategy.js +67 -0
  148. package/tsdoc-metadata.json +11 -11
  149. package/turbo/digest.js +127 -0
  150. package/turbo/errors.js +48 -0
  151. package/turbo/index.js +32 -0
  152. package/turbo/schemas/DryRun.js +57 -0
  153. package/turbo/schemas/results.js +61 -0
  154. package/turbo/services/TurboInspector.js +100 -0
  155. package/utils/ToolCommand.js +40 -0
@@ -0,0 +1,101 @@
1
+ //#region src/changesets/utils/issue-refs.ts
2
+ /**
3
+ * GitHub issue reference parsing from commit messages.
4
+ *
5
+ * @remarks
6
+ * Extracts categorized issue references (`Closes`, `Fixes`, `Refs`) from
7
+ * commit message bodies. Supports multiple formats including with/without
8
+ * colons, singular/plural keywords, and comma-separated issue lists.
9
+ * Input is truncated to 10,000 characters to prevent ReDoS on adversarial input.
10
+ *
11
+ * @see {@link Changelog} for the public API that consumes issue references
12
+ *
13
+ * @internal
14
+ */
15
+ /**
16
+ * Matches "closes #123" or "close: #456, #789" patterns (case-insensitive).
17
+ *
18
+ * Capture group `[1]` contains the comma-separated issue number list.
19
+ *
20
+ * @internal
21
+ */
22
+ const CLOSES_ISSUE_PATTERN = /closes?:?\s*#?(\d+(?:, *#?\d+)*)/i;
23
+ /**
24
+ * Matches "fixes #123" or "fix: #456, #789" patterns (case-insensitive).
25
+ *
26
+ * Capture group `[1]` contains the comma-separated issue number list.
27
+ *
28
+ * @internal
29
+ */
30
+ const FIXES_ISSUE_PATTERN = /fix(?:es)?:?\s*#?(\d+(?:, *#?\d+)*)/i;
31
+ /**
32
+ * Matches "refs #123" or "ref: #456, #789" patterns (case-insensitive).
33
+ *
34
+ * Capture group `[1]` contains the comma-separated issue number list.
35
+ *
36
+ * @internal
37
+ */
38
+ const REFS_ISSUE_PATTERN = /refs?:?\s*#?(\d+(?:, *#?\d+)*)/i;
39
+ /**
40
+ * Pattern for splitting comma-separated issue numbers.
41
+ *
42
+ * @internal
43
+ */
44
+ const ISSUE_NUMBER_SPLIT_PATTERN = /, */;
45
+ /**
46
+ * Extract issue numbers from a regex match against a commit message.
47
+ *
48
+ * @remarks
49
+ * Truncates the input to 10,000 characters before matching to prevent
50
+ * ReDoS on adversarial input. Strips `#` prefixes from matched numbers.
51
+ *
52
+ * @param pattern - The regex pattern to match (must have a capture group for issue numbers)
53
+ * @param message - The commit message body to search
54
+ * @returns Array of issue number strings (without `#` prefix)
55
+ *
56
+ * @internal
57
+ */
58
+ function extractIssueNumbers(pattern, message) {
59
+ const safeMessage = message.slice(0, 1e4);
60
+ const match = pattern.exec(safeMessage);
61
+ if (!match?.[1]) return [];
62
+ return match[1].split(ISSUE_NUMBER_SPLIT_PATTERN).map((num) => num.replace("#", "").trim());
63
+ }
64
+ /**
65
+ * Parse a commit message for GitHub issue references.
66
+ *
67
+ * Recognizes `Closes`, `Fixes`, and `Refs` keywords with various formats:
68
+ * - `Closes #123`
69
+ * - `Fixes: #456, #789`
70
+ * - `Refs #101`
71
+ *
72
+ * @remarks
73
+ * Each keyword category is matched independently, so a single commit
74
+ * message can contain references in all three categories. Keywords are
75
+ * case-insensitive and accept both singular and plural forms.
76
+ *
77
+ * @param commitMessage - The commit message body to parse
78
+ * @returns Categorized issue references with numbers as strings (no `#` prefix)
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * import { parseIssueReferences } from "../utils/issue-refs.js";
83
+ *
84
+ * const refs = parseIssueReferences("feat: add API\n\nCloses #42\nFixes: #17, #23");
85
+ * // refs.closes === ["42"]
86
+ * // refs.fixes === ["17", "23"]
87
+ * // refs.refs === []
88
+ * ```
89
+ *
90
+ * @internal
91
+ */
92
+ function parseIssueReferences(commitMessage) {
93
+ return {
94
+ closes: extractIssueNumbers(CLOSES_ISSUE_PATTERN, commitMessage),
95
+ fixes: extractIssueNumbers(FIXES_ISSUE_PATTERN, commitMessage),
96
+ refs: extractIssueNumbers(REFS_ISSUE_PATTERN, commitMessage)
97
+ };
98
+ }
99
+
100
+ //#endregion
101
+ export { parseIssueReferences };
@@ -0,0 +1,175 @@
1
+ //#region src/changesets/utils/jsonpath.ts
2
+ /**
3
+ * Tokenize a JSONPath string into segments.
4
+ *
5
+ * @remarks
6
+ * Strips the leading `$.` prefix, then splits on `.` boundaries while
7
+ * preserving bracket expressions (`[*]`, `[0]`). Each token is converted
8
+ * to the corresponding {@link Segment} discriminated union variant.
9
+ *
10
+ * @param path - JSONPath string (e.g., `"$.foo[*].bar"`)
11
+ * @returns Array of path segments
12
+ * @throws If the path does not start with `$.`
13
+ * @throws If the path cannot be tokenized after the `$.` prefix
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { parseJsonPath } from "../utils/jsonpath.js";
18
+ *
19
+ * const segments = parseJsonPath("$.packages[*].version");
20
+ * // [
21
+ * // { type: "property", key: "packages" },
22
+ * // { type: "wildcard" },
23
+ * // { type: "property", key: "version" },
24
+ * // ]
25
+ * ```
26
+ *
27
+ * @internal
28
+ */
29
+ function parseJsonPath(path) {
30
+ if (!path.startsWith("$.")) throw new Error(`Invalid JSONPath: must start with "$." — got "${path}"`);
31
+ const segments = [];
32
+ const raw = path.slice(2);
33
+ if (raw === "") return segments;
34
+ const tokens = raw.match(/[^.[\]]+|\[\*\]|\[\d+\]/g);
35
+ if (!tokens) throw new Error(`Invalid JSONPath: could not parse "${path}"`);
36
+ for (const token of tokens) if (token === "[*]") segments.push({ type: "wildcard" });
37
+ else if (token.startsWith("[") && token.endsWith("]")) segments.push({
38
+ type: "index",
39
+ index: Number.parseInt(token.slice(1, -1), 10)
40
+ });
41
+ else segments.push({
42
+ type: "property",
43
+ key: token
44
+ });
45
+ return segments;
46
+ }
47
+ /**
48
+ * Collect all values matching a JSONPath expression.
49
+ *
50
+ * @remarks
51
+ * Uses a breadth-first expansion: starting with the root object,
52
+ * each segment fans out the current set of matched nodes. Wildcards
53
+ * expand arrays into their individual elements. Non-object/non-array
54
+ * nodes at any intermediate step are silently skipped.
55
+ *
56
+ * @param obj - The object to query
57
+ * @param path - JSONPath string (e.g., `"$.version"`)
58
+ * @returns Array of all matched values (empty if no matches)
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * import { jsonPathGet } from "../utils/jsonpath.js";
63
+ *
64
+ * const obj = { packages: [{ version: "1.0.0" }, { version: "2.0.0" }] };
65
+ * const versions = jsonPathGet(obj, "$.packages[*].version");
66
+ * // ["1.0.0", "2.0.0"]
67
+ * ```
68
+ *
69
+ * @internal
70
+ */
71
+ function jsonPathGet(obj, path) {
72
+ const segments = parseJsonPath(path);
73
+ let current = [obj];
74
+ for (const segment of segments) {
75
+ const next = [];
76
+ for (const node of current) {
77
+ if (node === null || node === void 0 || typeof node !== "object") continue;
78
+ switch (segment.type) {
79
+ case "property": {
80
+ const value = node[segment.key];
81
+ if (value !== void 0) next.push(value);
82
+ break;
83
+ }
84
+ case "index":
85
+ if (Array.isArray(node) && segment.index < node.length) next.push(node[segment.index]);
86
+ break;
87
+ case "wildcard":
88
+ if (Array.isArray(node)) next.push(...node);
89
+ break;
90
+ }
91
+ }
92
+ current = next;
93
+ }
94
+ return current;
95
+ }
96
+ /**
97
+ * Mutate all matching locations in an object in-place.
98
+ *
99
+ * @remarks
100
+ * Walks to the parent(s) of the final segment, then sets the value
101
+ * at each matching location. Only updates existing keys/indices;
102
+ * does not create new properties or extend arrays. Returns the count
103
+ * of locations actually updated.
104
+ *
105
+ * @param obj - The object to modify in-place
106
+ * @param path - JSONPath string (e.g., `"$.packages[*].version"`)
107
+ * @param value - The value to set at each matching location
108
+ * @returns The number of locations updated (0 if no matches or empty path)
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * import { jsonPathSet } from "../utils/jsonpath.js";
113
+ *
114
+ * const obj = { version: "1.0.0" };
115
+ * const count = jsonPathSet(obj, "$.version", "2.0.0");
116
+ * // count === 1, obj.version === "2.0.0"
117
+ * ```
118
+ *
119
+ * @internal
120
+ */
121
+ function jsonPathSet(obj, path, value) {
122
+ const segments = parseJsonPath(path);
123
+ if (segments.length === 0) return 0;
124
+ const lastSegment = segments[segments.length - 1];
125
+ const parentSegments = segments.slice(0, -1);
126
+ let parents = [obj];
127
+ for (const segment of parentSegments) {
128
+ const next = [];
129
+ for (const node of parents) {
130
+ if (node === null || node === void 0 || typeof node !== "object") continue;
131
+ switch (segment.type) {
132
+ case "property": {
133
+ const child = node[segment.key];
134
+ if (child !== void 0) next.push(child);
135
+ break;
136
+ }
137
+ case "index":
138
+ if (Array.isArray(node) && segment.index < node.length) next.push(node[segment.index]);
139
+ break;
140
+ case "wildcard":
141
+ if (Array.isArray(node)) next.push(...node);
142
+ break;
143
+ }
144
+ }
145
+ parents = next;
146
+ }
147
+ let count = 0;
148
+ for (const parent of parents) {
149
+ if (parent === null || parent === void 0 || typeof parent !== "object") continue;
150
+ switch (lastSegment.type) {
151
+ case "property":
152
+ if (lastSegment.key in parent) {
153
+ parent[lastSegment.key] = value;
154
+ count++;
155
+ }
156
+ break;
157
+ case "index":
158
+ if (Array.isArray(parent) && lastSegment.index < parent.length) {
159
+ parent[lastSegment.index] = value;
160
+ count++;
161
+ }
162
+ break;
163
+ case "wildcard":
164
+ if (Array.isArray(parent)) for (let i = 0; i < parent.length; i++) {
165
+ parent[i] = value;
166
+ count++;
167
+ }
168
+ break;
169
+ }
170
+ }
171
+ return count;
172
+ }
173
+
174
+ //#endregion
175
+ export { jsonPathGet, jsonPathSet };
@@ -0,0 +1,50 @@
1
+ //#region src/changesets/utils/logger.ts
2
+ /**
3
+ * Simple logging utility for changelog generation.
4
+ *
5
+ * @remarks
6
+ * Provides environment-aware warning output:
7
+ * - **GitHub Actions**: emits `::warning::` annotations that surface in
8
+ * the Actions UI
9
+ * - **Local development**: falls back to `console.warn`
10
+ * - **Test environment**: suppressed entirely when `process.env.VITEST`
11
+ * is set, preventing noisy test output
12
+ *
13
+ * @internal
14
+ */
15
+ /**
16
+ * Log a warning message.
17
+ *
18
+ * @remarks
19
+ * Detection order:
20
+ * 1. If `process.env.VITEST` is set, the message is silently discarded.
21
+ * 2. If `process.env.GITHUB_ACTIONS === "true"`, the message is emitted
22
+ * as a `::warning::` annotation.
23
+ * 3. Otherwise, `console.warn` is used.
24
+ *
25
+ * @param message - The warning message
26
+ * @param args - Additional arguments (concatenated with a space in CI mode)
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * import { logWarning } from "../utils/logger.js";
31
+ *
32
+ * logWarning("Duplicate dependency entry", "effect@3.19.0");
33
+ * // In CI: "::warning::Duplicate dependency entry effect@3.19.0"
34
+ * // Locally: console.warn("Duplicate dependency entry", "effect@3.19.0")
35
+ * ```
36
+ *
37
+ * @internal
38
+ */
39
+ function logWarning(message, ...args) {
40
+ /* v8 ignore start — environment-specific */
41
+ if (typeof process !== "undefined" && process.env.VITEST) return;
42
+ if (typeof process !== "undefined" && process.env.GITHUB_ACTIONS === "true") {
43
+ const text = args.length > 0 ? `${message} ${args.join(" ")}` : message;
44
+ console.warn(`::warning::${text}`);
45
+ } else console.warn(message, ...args);
46
+ /* v8 ignore stop */
47
+ }
48
+
49
+ //#endregion
50
+ export { logWarning };
@@ -0,0 +1,57 @@
1
+ //#region src/changesets/utils/markdown-link.ts
2
+ /**
3
+ * Markdown link extraction utilities.
4
+ *
5
+ * @remarks
6
+ * Handles the inconsistency where `\@changesets/get-github-info` sometimes
7
+ * returns markdown-formatted links (e.g., `[#17](https://...)`) instead
8
+ * of plain URLs. Provides a regex pattern and extraction function to
9
+ * normalize these values.
10
+ *
11
+ * @see {@link Changelog} for the public API that uses link extraction
12
+ * during changelog generation
13
+ *
14
+ * @internal
15
+ */
16
+ /**
17
+ * Pattern matching markdown link format: `[text](url)`.
18
+ *
19
+ * Capture groups:
20
+ * - `[1]` — link text (e.g., `#17`)
21
+ * - `[2]` — URL (e.g., `https://github.com/owner/repo/pull/17`)
22
+ *
23
+ * @internal
24
+ */
25
+ const MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/;
26
+ /**
27
+ * Extract a plain URL from a markdown-formatted link.
28
+ *
29
+ * @remarks
30
+ * The `\@changesets/get-github-info` package sometimes returns markdown links
31
+ * like `[#17](https://github.com/owner/repo/pull/17)` instead of plain URLs.
32
+ * This helper extracts the URL portion, or returns the input unchanged if
33
+ * it is already a plain URL.
34
+ *
35
+ * @param linkOrUrl - A markdown link or plain URL string
36
+ * @returns The extracted plain URL
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { extractUrlFromMarkdown } from "../utils/markdown-link.js";
41
+ *
42
+ * extractUrlFromMarkdown("[#17](https://github.com/o/r/pull/17)");
43
+ * // "https://github.com/o/r/pull/17"
44
+ *
45
+ * extractUrlFromMarkdown("https://github.com/o/r/pull/17");
46
+ * // "https://github.com/o/r/pull/17" (returned unchanged)
47
+ * ```
48
+ *
49
+ * @internal
50
+ */
51
+ function extractUrlFromMarkdown(linkOrUrl) {
52
+ const match = MARKDOWN_LINK_PATTERN.exec(linkOrUrl);
53
+ return match ? match[2] : linkOrUrl;
54
+ }
55
+
56
+ //#endregion
57
+ export { MARKDOWN_LINK_PATTERN, extractUrlFromMarkdown };
@@ -0,0 +1,39 @@
1
+ import { Effect } from "effect";
2
+ import { PublishabilityDetector } from "workspaces-effect";
3
+
4
+ //#region src/changesets/utils/publishability.ts
5
+ /**
6
+ * Publishability helpers for the changeset CLI commands.
7
+ *
8
+ * @remarks
9
+ * Provides `listPublishablePackageNames`, a convenience wrapper around
10
+ * {@link PublishabilityDetector} that returns a `Set<string>` of
11
+ * publishable package names. Used by the `deps detect` and `deps regen`
12
+ * commands to filter out workspace packages whose dependency changes
13
+ * would never reach a release.
14
+ *
15
+ * @packageDocumentation
16
+ */
17
+ /**
18
+ * Compute the set of currently-publishable workspace package names.
19
+ *
20
+ * @remarks
21
+ * Uses the currently-active {@link PublishabilityDetector} — wire the
22
+ * {@link SilkPublishabilityDetectorLive} layer to get silk semantics.
23
+ *
24
+ * @param packages - The workspace packages to evaluate
25
+ * @returns An Effect yielding a `Set` of publishable package names
26
+ *
27
+ * @public
28
+ */
29
+ function listPublishablePackageNames(packages) {
30
+ return Effect.gen(function* () {
31
+ const detector = yield* PublishabilityDetector;
32
+ const names = /* @__PURE__ */ new Set();
33
+ for (const pkg of packages) if ((yield* detector.detect(pkg, pkg.path)).length > 0) names.add(pkg.name);
34
+ return names;
35
+ });
36
+ }
37
+
38
+ //#endregion
39
+ export { listPublishablePackageNames };
@@ -0,0 +1,79 @@
1
+ import remarkGfm from "remark-gfm";
2
+ import remarkParse from "remark-parse";
3
+ import remarkStringify from "remark-stringify";
4
+ import { unified } from "unified";
5
+
6
+ //#region src/changesets/utils/remark-pipeline.ts
7
+ /**
8
+ * Create a unified processor configured with remark-parse, remark-gfm,
9
+ * and remark-stringify.
10
+ *
11
+ * @remarks
12
+ * Each call creates a fresh processor instance. The plugin chain is:
13
+ * 1. `remark-parse` — markdown to MDAST
14
+ * 2. `remark-gfm` — GitHub Flavored Markdown extensions (tables, etc.)
15
+ * 3. `remark-stringify` — MDAST back to markdown
16
+ *
17
+ * @privateRemarks
18
+ * Return type is intentionally inferred because the unified `Processor`
19
+ * generic signature is complex and parameterized by the plugin chain.
20
+ *
21
+ * @returns A configured unified processor
22
+ *
23
+ * @internal
24
+ */
25
+ function createRemarkProcessor() {
26
+ return unified().use(remarkParse).use(remarkGfm).use(remarkStringify);
27
+ }
28
+ /**
29
+ * Parse a markdown string into an MDAST AST synchronously.
30
+ *
31
+ * @remarks
32
+ * Uses {@link createRemarkProcessor} internally. The returned tree
33
+ * includes GFM extensions (tables, strikethrough, etc.) thanks to
34
+ * the remark-gfm plugin.
35
+ *
36
+ * @param content - Raw markdown string
37
+ * @returns The parsed MDAST root node
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * import { parseMarkdown } from "../utils/remark-pipeline.js";
42
+ *
43
+ * const tree = parseMarkdown("## Version 1.0.0\n\nInitial release.");
44
+ * // tree.type === "root"
45
+ * // tree.children[0].type === "heading"
46
+ * ```
47
+ *
48
+ * @internal
49
+ */
50
+ function parseMarkdown(content) {
51
+ return createRemarkProcessor().parse(content);
52
+ }
53
+ /**
54
+ * Stringify an MDAST AST back to a markdown string synchronously.
55
+ *
56
+ * @remarks
57
+ * Uses {@link createRemarkProcessor} internally. GFM constructs
58
+ * (tables, etc.) are serialized correctly thanks to the remark-gfm plugin.
59
+ *
60
+ * @param tree - The MDAST root node
61
+ * @returns The serialized markdown string
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * import { parseMarkdown, stringifyMarkdown } from "../utils/remark-pipeline.js";
66
+ *
67
+ * const tree = parseMarkdown("# Hello\n\nWorld.");
68
+ * const md = stringifyMarkdown(tree);
69
+ * // "# Hello\n\nWorld.\n"
70
+ * ```
71
+ *
72
+ * @internal
73
+ */
74
+ function stringifyMarkdown(tree) {
75
+ return createRemarkProcessor().stringify(tree);
76
+ }
77
+
78
+ //#endregion
79
+ export { parseMarkdown, stringifyMarkdown };
@@ -0,0 +1,94 @@
1
+ import { fromHeading } from "../categories/index.js";
2
+ import { parseMarkdown, stringifyMarkdown } from "./remark-pipeline.js";
3
+ import { toString } from "mdast-util-to-string";
4
+
5
+ //#region src/changesets/utils/section-parser.ts
6
+ /**
7
+ * Parse a changeset summary into structured sections.
8
+ *
9
+ * @remarks
10
+ * The algorithm works in three steps:
11
+ * 1. Parse the summary markdown into an MDAST tree via {@link parseMarkdown}
12
+ * 2. Scan `tree.children` for h2 (`depth === 2`) headings, recording their
13
+ * indices
14
+ * 3. For each h2, extract the heading text, resolve it to a
15
+ * {@link SectionCategory} via `fromHeading()`, and collect all nodes
16
+ * between this h2 and the next h2 (or end of document) as the section
17
+ * content
18
+ *
19
+ * If the summary contains h2 (`##`) headings, they are mapped to categories
20
+ * via `fromHeading()`. Content between headings becomes the section content.
21
+ * Content before the first h2 becomes the preamble.
22
+ *
23
+ * If no h2 headings are present, the entire content is returned as the preamble
24
+ * with an empty sections array (backward-compatible flat-text mode).
25
+ *
26
+ * Unknown headings (those not recognized by `fromHeading()`) are silently
27
+ * skipped; validation of heading names is the responsibility of Layer 1
28
+ * (remark-lint).
29
+ *
30
+ * @param summary - The changeset summary markdown
31
+ * @returns Parsed sections and optional preamble
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import { parseChangesetSections } from "../utils/section-parser.js";
36
+ *
37
+ * const result = parseChangesetSections("## Features\n\n- New API\n\n## Bug Fixes\n\n- Fixed crash");
38
+ * // result.sections.length === 2
39
+ * // result.sections[0].category === "Features"
40
+ * // result.preamble === undefined
41
+ * ```
42
+ *
43
+ * @internal
44
+ */
45
+ function parseChangesetSections(summary) {
46
+ const tree = parseMarkdown(summary);
47
+ const h2Indices = [];
48
+ for (let i = 0; i < tree.children.length; i++) {
49
+ const node = tree.children[i];
50
+ if (node.type === "heading" && node.depth === 2) h2Indices.push(i);
51
+ }
52
+ if (h2Indices.length === 0) return {
53
+ preamble: summary.trim(),
54
+ sections: []
55
+ };
56
+ const result = { sections: [] };
57
+ if (h2Indices[0] > 0) result.preamble = stringifyAstSlice(tree.children.slice(0, h2Indices[0]));
58
+ for (let i = 0; i < h2Indices.length; i++) {
59
+ const headingIndex = h2Indices[i];
60
+ const headingNode = tree.children[headingIndex];
61
+ const headingText = toString(headingNode);
62
+ const nextIndex = i + 1 < h2Indices.length ? h2Indices[i + 1] : tree.children.length;
63
+ const content = stringifyAstSlice(tree.children.slice(headingIndex + 1, nextIndex));
64
+ const category = fromHeading(headingText);
65
+ if (category) result.sections.push({
66
+ category,
67
+ heading: headingText,
68
+ content
69
+ });
70
+ }
71
+ return result;
72
+ }
73
+ /**
74
+ * Stringify a slice of MDAST nodes back to markdown.
75
+ *
76
+ * @remarks
77
+ * Wraps the nodes in a synthetic root node and delegates to
78
+ * {@link stringifyMarkdown}. Returns an empty string for empty slices.
79
+ *
80
+ * @param nodes - Array of MDAST content nodes
81
+ * @returns Trimmed markdown string, or empty string if no nodes
82
+ *
83
+ * @internal
84
+ */
85
+ function stringifyAstSlice(nodes) {
86
+ if (nodes.length === 0) return "";
87
+ return stringifyMarkdown({
88
+ type: "root",
89
+ children: nodes
90
+ }).trim();
91
+ }
92
+
93
+ //#endregion
94
+ export { parseChangesetSections };
@@ -0,0 +1,46 @@
1
+ //#region src/changesets/utils/strip-frontmatter.ts
2
+ /**
3
+ * Utility for stripping YAML frontmatter from changeset files.
4
+ *
5
+ * @remarks
6
+ * Changeset `.md` files produced by `\@changesets/cli` contain YAML
7
+ * frontmatter (delimited by `---`) specifying which packages are
8
+ * affected and their version bump types. This frontmatter must be
9
+ * removed before remark-lint processing, as remark-parse does not
10
+ * handle YAML frontmatter natively without `remark-frontmatter`.
11
+ *
12
+ * The regex used is non-greedy (`[\s\S]*?`) to match only the first
13
+ * frontmatter block and avoid stripping content after a second `---`
14
+ * delimiter that might appear in the document body.
15
+ *
16
+ * @see {@link ChangesetLinter} for the public API that uses frontmatter
17
+ * stripping during changeset validation
18
+ *
19
+ * @internal
20
+ */
21
+ /**
22
+ * Strip leading YAML frontmatter from a markdown string.
23
+ *
24
+ * Removes the `---\n...\n---\n` block at the start of the content.
25
+ * If no frontmatter is present, the content is returned as-is.
26
+ *
27
+ * @param content - Raw markdown string potentially containing frontmatter
28
+ * @returns The markdown content with frontmatter removed
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * import { stripFrontmatter } from "../utils/strip-frontmatter.js";
33
+ *
34
+ * const raw = "---\n\"pkg\": minor\n---\n\n## Features\n\n- New API";
35
+ * const body = stripFrontmatter(raw);
36
+ * // "\n## Features\n\n- New API"
37
+ * ```
38
+ *
39
+ * @internal
40
+ */
41
+ function stripFrontmatter(content) {
42
+ return content.replace(/^---\n[\s\S]*?\n---\n?/, "");
43
+ }
44
+
45
+ //#endregion
46
+ export { stripFrontmatter };