@savvy-web/silk-effects 0.6.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -17
- package/_virtual/_rolldown/runtime.js +18 -0
- package/changesets/api/categories.js +247 -0
- package/changesets/api/changelog.js +134 -0
- package/changesets/api/dependency-table.js +163 -0
- package/changesets/api/linter.js +168 -0
- package/changesets/api/transformer.js +140 -0
- package/changesets/categories/index.js +299 -0
- package/changesets/categories/types.js +66 -0
- package/changesets/changelog/formatting.js +119 -0
- package/changesets/changelog/getDependencyReleaseLine.js +114 -0
- package/changesets/changelog/getReleaseLine.js +122 -0
- package/changesets/changelog/index.js +99 -0
- package/changesets/constants.js +43 -0
- package/changesets/errors.js +305 -0
- package/changesets/index.js +146 -0
- package/changesets/markdownlint/index.js +29 -0
- package/changesets/markdownlint/rules/content-structure.js +98 -0
- package/changesets/markdownlint/rules/dependency-table-format.js +170 -0
- package/changesets/markdownlint/rules/heading-hierarchy.js +61 -0
- package/changesets/markdownlint/rules/required-sections.js +54 -0
- package/changesets/markdownlint/rules/uncategorized-content.js +54 -0
- package/changesets/markdownlint/rules/utils.js +30 -0
- package/changesets/remark/plugins/aggregate-dependency-tables.js +47 -0
- package/changesets/remark/plugins/contributor-footnotes.js +123 -0
- package/changesets/remark/plugins/deduplicate-items.js +30 -0
- package/changesets/remark/plugins/issue-link-refs.js +58 -0
- package/changesets/remark/plugins/merge-sections.js +43 -0
- package/changesets/remark/plugins/normalize-format.js +47 -0
- package/changesets/remark/plugins/reorder-sections.js +34 -0
- package/changesets/remark/presets.js +119 -0
- package/changesets/remark/rules/content-structure.js +22 -0
- package/changesets/remark/rules/dependency-table-format.js +40 -0
- package/changesets/remark/rules/heading-hierarchy.js +19 -0
- package/changesets/remark/rules/required-sections.js +17 -0
- package/changesets/remark/rules/uncategorized-content.js +31 -0
- package/changesets/schemas/changeset.js +146 -0
- package/changesets/schemas/dependency-table.js +189 -0
- package/changesets/schemas/git.js +69 -0
- package/changesets/schemas/github.js +175 -0
- package/changesets/schemas/options.js +182 -0
- package/changesets/schemas/package-scope.js +128 -0
- package/changesets/schemas/primitives.js +72 -0
- package/changesets/schemas/version-files.js +151 -0
- package/changesets/services/branch-analyzer.js +278 -0
- package/changesets/services/changelog.js +50 -0
- package/changesets/services/config-inspector.js +390 -0
- package/changesets/services/github.js +178 -0
- package/changesets/services/markdown.js +106 -0
- package/changesets/services/workspace-snapshot.js +182 -0
- package/changesets/utils/commit-parser.js +80 -0
- package/changesets/utils/dep-diff.js +77 -0
- package/changesets/utils/dependency-table.js +347 -0
- package/changesets/utils/issue-refs.js +101 -0
- package/changesets/utils/jsonpath.js +175 -0
- package/changesets/utils/logger.js +50 -0
- package/changesets/utils/markdown-link.js +57 -0
- package/changesets/utils/publishability.js +39 -0
- package/changesets/utils/remark-pipeline.js +79 -0
- package/changesets/utils/section-parser.js +94 -0
- package/changesets/utils/strip-frontmatter.js +46 -0
- package/changesets/utils/version-blocks.js +108 -0
- package/changesets/utils/version-files.js +336 -0
- package/changesets/utils/worktree-snapshot.js +142 -0
- package/changesets/vendor/github-info.js +55 -0
- package/commitlint/config/factory.js +69 -0
- package/commitlint/config/plugins.js +227 -0
- package/commitlint/config/rules.js +155 -0
- package/commitlint/config/schema.js +46 -0
- package/commitlint/detection/dco.js +53 -0
- package/commitlint/detection/scopes.js +45 -0
- package/commitlint/formatter/format.js +85 -0
- package/commitlint/formatter/messages.js +79 -0
- package/commitlint/hook/diagnostics/branch.js +36 -0
- package/commitlint/hook/diagnostics/cache.js +37 -0
- package/commitlint/hook/diagnostics/commitlint-config.js +36 -0
- package/commitlint/hook/diagnostics/open-issues.js +56 -0
- package/commitlint/hook/diagnostics/package-manager.js +51 -0
- package/commitlint/hook/diagnostics/signing.js +107 -0
- package/commitlint/hook/envelope.js +46 -0
- package/commitlint/hook/output.js +45 -0
- package/commitlint/hook/parse-bash-command.js +105 -0
- package/commitlint/hook/rules/closes-trailer.js +31 -0
- package/commitlint/hook/rules/forbidden-content.js +32 -0
- package/commitlint/hook/rules/plan-leakage.js +36 -0
- package/commitlint/hook/rules/signing-flag-conflict.js +25 -0
- package/commitlint/hook/rules/soft-wrap.js +37 -0
- package/commitlint/hook/rules/types.js +14 -0
- package/commitlint/hook/rules/verbosity.js +31 -0
- package/commitlint/hook/silence-logger.js +39 -0
- package/commitlint/index.js +146 -0
- package/commitlint/prompt/config.js +91 -0
- package/commitlint/prompt/emojis.js +74 -0
- package/commitlint/prompt/prompter.js +135 -0
- package/commitlint/static.js +73 -0
- package/errors/BiomeSyncError.js +21 -0
- package/errors/ChangesetConfigError.js +20 -0
- package/errors/ConfigNotFoundError.js +21 -0
- package/errors/SectionParseError.js +16 -0
- package/errors/SectionValidationError.js +16 -0
- package/errors/SectionWriteError.js +16 -0
- package/errors/TagFormatError.js +20 -0
- package/errors/ToolNotFoundError.js +11 -0
- package/errors/ToolResolutionError.js +11 -0
- package/errors/ToolVersionMismatchError.js +11 -0
- package/errors/VersioningDetectionError.js +20 -0
- package/errors/WorkspaceAnalysisError.js +21 -0
- package/index.d.ts +9743 -8380
- package/index.js +36 -6657
- package/lint/Handler.js +39 -0
- package/lint/cli/sections.js +65 -0
- package/lint/cli/templates/markdownlint.gen.js +183 -0
- package/lint/config/Preset.js +152 -0
- package/lint/config/createConfig.js +89 -0
- package/lint/handlers/Biome.js +179 -0
- package/lint/handlers/Markdown.js +139 -0
- package/lint/handlers/PackageJson.js +130 -0
- package/lint/handlers/PnpmWorkspace.js +141 -0
- package/lint/handlers/ShellScripts.js +58 -0
- package/lint/handlers/TypeScript.js +134 -0
- package/lint/handlers/Yaml.js +167 -0
- package/lint/index.js +52 -0
- package/lint/utils/Command.js +285 -0
- package/lint/utils/Filter.js +100 -0
- package/lint/utils/Workspace.js +86 -0
- package/package.json +52 -63
- package/schemas/CommentStyle.js +16 -0
- package/schemas/ResolvedTool.js +63 -0
- package/schemas/SavvySections.js +113 -0
- package/schemas/SectionBlock.js +70 -0
- package/schemas/SectionDefinition.js +121 -0
- package/schemas/SectionResults.js +12 -0
- package/schemas/TagStrategySchemas.js +18 -0
- package/schemas/ToolDefinition.js +39 -0
- package/schemas/ToolResults.js +14 -0
- package/schemas/VersioningSchemas.js +95 -0
- package/schemas/WorkspaceAnalysisSchemas.js +190 -0
- package/services/BiomeSchemaSync.js +133 -0
- package/services/ChangesetConfig.js +78 -0
- package/services/ChangesetConfigReader.js +106 -0
- package/services/ConfigDiscovery.js +71 -0
- package/services/ManagedSection.js +288 -0
- package/services/SilkPublishability.js +193 -0
- package/services/SilkWorkspaceAnalyzer.js +213 -0
- package/services/TagStrategy.js +54 -0
- package/services/ToolDiscovery.js +229 -0
- package/services/VersioningStrategy.js +67 -0
- package/tsdoc-metadata.json +11 -11
- package/turbo/digest.js +127 -0
- package/turbo/errors.js +48 -0
- package/turbo/index.js +32 -0
- package/turbo/schemas/DryRun.js +57 -0
- package/turbo/schemas/results.js +61 -0
- package/turbo/services/TurboInspector.js +100 -0
- 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 };
|