@outfitter/tooling 0.3.3 → 0.3.5
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/.markdownlint-cli2.jsonc +55 -55
- package/README.md +33 -24
- package/dist/bun-version-compat.d.ts +2 -0
- package/dist/bun-version-compat.js +10 -0
- package/dist/cli/check-boundary-invocations.d.ts +34 -0
- package/dist/cli/check-boundary-invocations.js +14 -0
- package/dist/cli/check-bunup-registry.d.ts +36 -0
- package/dist/cli/check-bunup-registry.js +12 -0
- package/dist/cli/check-changeset.d.ts +82 -0
- package/dist/cli/check-changeset.js +24 -0
- package/dist/cli/check-clean-tree.d.ts +36 -0
- package/dist/cli/check-clean-tree.js +14 -0
- package/dist/cli/check-exports.d.ts +3 -0
- package/dist/cli/check-exports.js +17 -0
- package/dist/cli/check-home-paths.d.ts +31 -0
- package/dist/cli/check-home-paths.js +12 -0
- package/dist/cli/check-markdown-links.d.ts +42 -0
- package/dist/cli/check-markdown-links.js +13 -0
- package/dist/cli/check-readme-imports.d.ts +61 -0
- package/dist/{shared/chunk-7tdgbqb0.js → cli/check-readme-imports.js} +7 -6
- package/dist/cli/check-tsdoc.d.ts +5 -0
- package/dist/cli/check-tsdoc.js +42 -0
- package/dist/cli/check.d.ts +19 -0
- package/dist/cli/check.js +10 -0
- package/dist/cli/fix.d.ts +19 -0
- package/dist/cli/fix.js +10 -0
- package/dist/cli/index.js +61 -1218
- package/dist/cli/init.d.ts +31 -0
- package/dist/cli/init.js +12 -0
- package/dist/cli/internal/exports-analysis.d.ts +2 -0
- package/dist/cli/internal/exports-analysis.js +10 -0
- package/dist/cli/internal/exports-fs.d.ts +17 -0
- package/dist/cli/internal/exports-fs.js +9 -0
- package/dist/cli/internal/pre-push-checks.d.ts +2 -0
- package/dist/cli/internal/pre-push-checks.js +37 -0
- package/dist/cli/internal/tsdoc-analysis.d.ts +3 -0
- package/dist/cli/internal/tsdoc-analysis.js +26 -0
- package/dist/cli/internal/tsdoc-formatting.d.ts +3 -0
- package/dist/cli/internal/tsdoc-formatting.js +10 -0
- package/dist/cli/internal/tsdoc-types.d.ts +2 -0
- package/dist/cli/internal/tsdoc-types.js +16 -0
- package/dist/cli/pre-push.d.ts +7 -0
- package/dist/cli/pre-push.js +29 -0
- package/dist/cli/upgrade-bun.d.ts +8 -0
- package/dist/cli/upgrade-bun.js +9 -0
- package/dist/index.d.ts +9 -186
- package/dist/index.js +4 -42
- package/dist/registry/build.d.ts +4 -0
- package/dist/registry/build.js +279 -0
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.js +1 -0
- package/dist/registry/schema.d.ts +2 -0
- package/dist/registry/schema.js +28 -0
- package/dist/shared/@outfitter/tooling-0zjz8eg9.js +106 -0
- package/dist/shared/@outfitter/tooling-1hez6j9d.js +21 -0
- package/dist/shared/@outfitter/tooling-2vv5y3s4.js +145 -0
- package/dist/shared/{chunk-cmde0fwx.js → @outfitter/tooling-5xxctk9b.js} +12 -138
- package/dist/shared/@outfitter/tooling-5ynz680q.js +59 -0
- package/dist/shared/@outfitter/tooling-7437rmy6.js +39 -0
- package/dist/shared/@outfitter/tooling-8qcwr06t.d.ts +74 -0
- package/dist/shared/@outfitter/tooling-9ram55dd.js +69 -0
- package/dist/shared/@outfitter/tooling-9vs606gq.d.ts +3 -0
- package/dist/shared/@outfitter/tooling-a4bfx4be.js +21 -0
- package/dist/shared/@outfitter/tooling-a59br34g.js +32 -0
- package/dist/shared/@outfitter/tooling-a6q3zh7t.js +86 -0
- package/dist/shared/@outfitter/tooling-amrbp7cm.js +102 -0
- package/dist/shared/@outfitter/tooling-ayps7c4x.js +58 -0
- package/dist/shared/@outfitter/tooling-c8q6mj8z.js +228 -0
- package/dist/shared/@outfitter/tooling-cb0b8wsx.d.ts +57 -0
- package/dist/shared/@outfitter/tooling-ctmgnap5.js +19 -0
- package/dist/shared/@outfitter/tooling-f8q38e9z.d.ts +16 -0
- package/dist/shared/@outfitter/tooling-gcdvsqqp.js +73 -0
- package/dist/shared/@outfitter/tooling-h5dnevjw.js +139 -0
- package/dist/shared/@outfitter/tooling-j8d1h2zd.d.ts +10 -0
- package/dist/shared/@outfitter/tooling-ja1zg5yc.js +214 -0
- package/dist/shared/@outfitter/tooling-jnrs9rqd.js +4 -0
- package/dist/shared/@outfitter/tooling-mkynjra9.js +23 -0
- package/dist/shared/@outfitter/tooling-mq2xvz96.js +285 -0
- package/dist/shared/@outfitter/tooling-pq47jv6t.js +213 -0
- package/dist/shared/@outfitter/tooling-sjm8nebx.d.ts +109 -0
- package/dist/shared/@outfitter/tooling-stgnc2zx.d.ts +85 -0
- package/dist/shared/@outfitter/tooling-tj9p41vj.d.ts +55 -0
- package/dist/shared/@outfitter/tooling-vjmhvpjq.d.ts +29 -0
- package/dist/shared/@outfitter/tooling-wwm97f47.js +81 -0
- package/dist/shared/@outfitter/tooling-y43b117h.d.ts +13 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +8 -0
- package/lefthook.yml +5 -1
- package/package.json +140 -131
- package/registry/registry.json +19 -12
- package/tsconfig.preset.bun.json +5 -5
- package/tsconfig.preset.json +33 -33
- package/biome.json +0 -81
- package/dist/shared/chunk-3s189drz.js +0 -4
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CheckTsDocOptions } from "./tooling-tj9p41vj.js";
|
|
2
|
+
/**
|
|
3
|
+
* Run check-tsdoc across workspace packages.
|
|
4
|
+
*
|
|
5
|
+
* Discovers packages with `src/index.ts` entry points, analyzes TSDoc
|
|
6
|
+
* coverage on exported declarations, and reports per-package statistics.
|
|
7
|
+
* Calls `process.exit()` on completion.
|
|
8
|
+
*/
|
|
9
|
+
declare function runCheckTsdoc(options?: CheckTsDocOptions): Promise<void>;
|
|
10
|
+
export { runCheckTsdoc };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tooling/src/cli/check-markdown-links.ts
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { dirname, resolve } from "path";
|
|
5
|
+
var MD_LINK_RE = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
6
|
+
var SKIP_PROTOCOLS = /^(https?:\/\/|mailto:|data:|tel:)/;
|
|
7
|
+
var FENCE_RE = /^(\s*)((`{3,})|~{3,})(.*)$/;
|
|
8
|
+
function stripInlineCodeSpans(line) {
|
|
9
|
+
let result = "";
|
|
10
|
+
let index = 0;
|
|
11
|
+
while (index < line.length) {
|
|
12
|
+
if (line[index] !== "`") {
|
|
13
|
+
result += line[index];
|
|
14
|
+
index += 1;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
let tickCount = 1;
|
|
18
|
+
while (line[index + tickCount] === "`") {
|
|
19
|
+
tickCount += 1;
|
|
20
|
+
}
|
|
21
|
+
const opener = line.slice(index, index + tickCount);
|
|
22
|
+
index += tickCount;
|
|
23
|
+
let closingIndex = -1;
|
|
24
|
+
let cursor = index;
|
|
25
|
+
while (cursor < line.length) {
|
|
26
|
+
if (line[cursor] !== "`") {
|
|
27
|
+
cursor += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
let runLength = 1;
|
|
31
|
+
while (line[cursor + runLength] === "`") {
|
|
32
|
+
runLength += 1;
|
|
33
|
+
}
|
|
34
|
+
if (runLength === tickCount) {
|
|
35
|
+
closingIndex = cursor;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
cursor += runLength;
|
|
39
|
+
}
|
|
40
|
+
if (closingIndex === -1) {
|
|
41
|
+
result += opener;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
index = closingIndex + tickCount;
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
function extractMarkdownLinks(content) {
|
|
49
|
+
const lines = content.split(`
|
|
50
|
+
`);
|
|
51
|
+
const links = [];
|
|
52
|
+
let inCodeFence = false;
|
|
53
|
+
let fenceChar = "";
|
|
54
|
+
let fenceCount = 0;
|
|
55
|
+
for (let i = 0;i < lines.length; i++) {
|
|
56
|
+
const line = lines[i];
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
const fenceMatch = FENCE_RE.exec(trimmed);
|
|
59
|
+
if (fenceMatch) {
|
|
60
|
+
const marker = fenceMatch[2] ?? "";
|
|
61
|
+
const char = marker[0] ?? "`";
|
|
62
|
+
const count = marker.length;
|
|
63
|
+
if (!inCodeFence) {
|
|
64
|
+
inCodeFence = true;
|
|
65
|
+
fenceChar = char;
|
|
66
|
+
fenceCount = count;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (char === fenceChar && count >= fenceCount && !(fenceMatch[4] ?? "").trim()) {
|
|
70
|
+
inCodeFence = false;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (inCodeFence)
|
|
75
|
+
continue;
|
|
76
|
+
const stripped = stripInlineCodeSpans(line);
|
|
77
|
+
for (const match of stripped.matchAll(MD_LINK_RE)) {
|
|
78
|
+
let target = match[2];
|
|
79
|
+
if (SKIP_PROTOCOLS.test(target))
|
|
80
|
+
continue;
|
|
81
|
+
if (target.startsWith("#"))
|
|
82
|
+
continue;
|
|
83
|
+
const hashIndex = target.indexOf("#");
|
|
84
|
+
if (hashIndex !== -1) {
|
|
85
|
+
target = target.substring(0, hashIndex);
|
|
86
|
+
}
|
|
87
|
+
if (target === "")
|
|
88
|
+
continue;
|
|
89
|
+
links.push({ target, line: i + 1 });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return links;
|
|
93
|
+
}
|
|
94
|
+
async function validateLinks(rootDir, files) {
|
|
95
|
+
const broken = [];
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
const absolutePath = resolve(rootDir, file);
|
|
98
|
+
const content = await Bun.file(absolutePath).text();
|
|
99
|
+
const links = extractMarkdownLinks(content);
|
|
100
|
+
const fileDir = dirname(absolutePath);
|
|
101
|
+
for (const link of links) {
|
|
102
|
+
if (link.target.startsWith("docs/packages/") || link.target.startsWith("./docs/packages/") || link.target.startsWith("../docs/packages/")) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const resolvedTarget = resolve(fileDir, link.target);
|
|
106
|
+
const relativeFromRoot = resolvedTarget.substring(rootDir.length + 1);
|
|
107
|
+
if (relativeFromRoot.startsWith("docs/packages/")) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (!existsSync(resolvedTarget)) {
|
|
111
|
+
broken.push({
|
|
112
|
+
source: file,
|
|
113
|
+
target: link.target,
|
|
114
|
+
line: link.line
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return broken;
|
|
120
|
+
}
|
|
121
|
+
var DEFAULT_PATTERNS = [
|
|
122
|
+
"docs/**/*.md",
|
|
123
|
+
"packages/*/README.md",
|
|
124
|
+
"AGENTS.md",
|
|
125
|
+
"CLAUDE.md",
|
|
126
|
+
"README.md",
|
|
127
|
+
".claude/CLAUDE.md"
|
|
128
|
+
];
|
|
129
|
+
async function discoverFiles(rootDir, patterns) {
|
|
130
|
+
const files = new Set;
|
|
131
|
+
for (const pattern of patterns) {
|
|
132
|
+
const glob = new Bun.Glob(pattern);
|
|
133
|
+
for await (const match of glob.scan({ cwd: rootDir, absolute: false })) {
|
|
134
|
+
files.add(match);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return [...files].toSorted();
|
|
138
|
+
}
|
|
139
|
+
var COLORS = {
|
|
140
|
+
reset: "\x1B[0m",
|
|
141
|
+
red: "\x1B[31m",
|
|
142
|
+
green: "\x1B[32m",
|
|
143
|
+
yellow: "\x1B[33m",
|
|
144
|
+
dim: "\x1B[2m",
|
|
145
|
+
cyan: "\x1B[36m"
|
|
146
|
+
};
|
|
147
|
+
async function runCheckMarkdownLinks(rootDir, patterns) {
|
|
148
|
+
const effectivePatterns = patterns ?? DEFAULT_PATTERNS;
|
|
149
|
+
const files = await discoverFiles(rootDir, effectivePatterns);
|
|
150
|
+
if (files.length === 0) {
|
|
151
|
+
process.stdout.write(`No markdown files found.
|
|
152
|
+
`);
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
const broken = await validateLinks(rootDir, files);
|
|
156
|
+
if (broken.length === 0) {
|
|
157
|
+
process.stdout.write(`${COLORS.green}All links valid across ${files.length} markdown file(s).${COLORS.reset}
|
|
158
|
+
`);
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
const bySource = new Map;
|
|
162
|
+
for (const link of broken) {
|
|
163
|
+
const existing = bySource.get(link.source);
|
|
164
|
+
if (existing) {
|
|
165
|
+
existing.push(link);
|
|
166
|
+
} else {
|
|
167
|
+
bySource.set(link.source, [link]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
process.stderr.write(`${COLORS.red}Found ${broken.length} broken link(s):${COLORS.reset}
|
|
171
|
+
|
|
172
|
+
`);
|
|
173
|
+
for (const [source, links] of bySource) {
|
|
174
|
+
process.stderr.write(` ${COLORS.yellow}${source}${COLORS.reset}
|
|
175
|
+
`);
|
|
176
|
+
for (const link of links) {
|
|
177
|
+
process.stderr.write(` ${COLORS.cyan}${link.line}${COLORS.reset} ${COLORS.dim}${link.target}${COLORS.reset}
|
|
178
|
+
`);
|
|
179
|
+
}
|
|
180
|
+
process.stderr.write(`
|
|
181
|
+
`);
|
|
182
|
+
}
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
async function main() {
|
|
186
|
+
const args = process.argv.slice(2);
|
|
187
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
188
|
+
process.stdout.write(`Usage: check-markdown-links [dirs/patterns...]
|
|
189
|
+
|
|
190
|
+
Validates that relative links in markdown files resolve to existing files.
|
|
191
|
+
|
|
192
|
+
Arguments:
|
|
193
|
+
dirs/patterns Glob patterns to scan (defaults: docs/, packages/*/README.md, etc.)
|
|
194
|
+
|
|
195
|
+
Options:
|
|
196
|
+
--help Show this help message
|
|
197
|
+
|
|
198
|
+
Exit codes:
|
|
199
|
+
0 All links valid
|
|
200
|
+
1 Broken links found
|
|
201
|
+
`);
|
|
202
|
+
process.exitCode = 0;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const cwd = process.cwd();
|
|
206
|
+
const patterns = args.length > 0 ? args.filter((a) => !a.startsWith("--")) : undefined;
|
|
207
|
+
const exitCode = await runCheckMarkdownLinks(cwd, patterns);
|
|
208
|
+
process.exitCode = exitCode;
|
|
209
|
+
}
|
|
210
|
+
if (import.meta.main) {
|
|
211
|
+
main();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { extractMarkdownLinks, validateLinks, runCheckMarkdownLinks };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tooling/src/bun-version-compat.ts
|
|
3
|
+
function parseSemver(version) {
|
|
4
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
5
|
+
if (!match?.[1] || !match[2] || !match[3]) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
major: Number.parseInt(match[1], 10),
|
|
10
|
+
minor: Number.parseInt(match[2], 10),
|
|
11
|
+
patch: Number.parseInt(match[3], 10)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function isTypesBunVersionCompatible(bunVersion, bunTypesVersion) {
|
|
15
|
+
const parsedBun = parseSemver(bunVersion);
|
|
16
|
+
const parsedTypes = parseSemver(bunTypesVersion);
|
|
17
|
+
if (!parsedBun || !parsedTypes) {
|
|
18
|
+
return bunVersion === bunTypesVersion;
|
|
19
|
+
}
|
|
20
|
+
return parsedBun.major === parsedTypes.major && parsedBun.minor === parsedTypes.minor && parsedTypes.patch <= parsedBun.patch;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { parseSemver, isTypesBunVersionCompatible };
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tooling/src/cli/check-changeset.ts
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
function toWorkspacePackageName(packageName) {
|
|
6
|
+
return packageName.startsWith("@outfitter/") ? packageName : `@outfitter/${packageName}`;
|
|
7
|
+
}
|
|
8
|
+
function getReleasableChangedPackages(changedPackages, ignoredPackages) {
|
|
9
|
+
const ignored = new Set(ignoredPackages.map(toWorkspacePackageName));
|
|
10
|
+
return changedPackages.filter((packageName) => !ignored.has(toWorkspacePackageName(packageName)));
|
|
11
|
+
}
|
|
12
|
+
function getChangedPackagePaths(files) {
|
|
13
|
+
const packageNames = new Set;
|
|
14
|
+
const pattern = /^packages\/([^/]+)\/src\/(.+)$/;
|
|
15
|
+
for (const file of files) {
|
|
16
|
+
const match = pattern.exec(file);
|
|
17
|
+
if (match?.[1] && match[2] && isReleaseRelevantSourcePath(match[2])) {
|
|
18
|
+
packageNames.add(match[1]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return [...packageNames].toSorted();
|
|
22
|
+
}
|
|
23
|
+
function getChangedChangesetFiles(files) {
|
|
24
|
+
const pattern = /^\.changeset\/([^/]+\.md)$/;
|
|
25
|
+
const results = [];
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
const match = pattern.exec(file);
|
|
28
|
+
if (match?.[1] && match[1] !== "README.md") {
|
|
29
|
+
results.push(match[1]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return results.toSorted();
|
|
33
|
+
}
|
|
34
|
+
function checkChangesetRequired(releasablePackages, changesetFiles, coveredPackages = []) {
|
|
35
|
+
if (releasablePackages.length === 0) {
|
|
36
|
+
return { ok: true, missingFor: [] };
|
|
37
|
+
}
|
|
38
|
+
if (changesetFiles.length === 0) {
|
|
39
|
+
return { ok: false, missingFor: releasablePackages };
|
|
40
|
+
}
|
|
41
|
+
const covered = new Set(coveredPackages);
|
|
42
|
+
const missingFor = releasablePackages.filter((packageName) => !covered.has(toWorkspacePackageName(packageName)));
|
|
43
|
+
return missingFor.length === 0 ? { ok: true, missingFor: [] } : { ok: false, missingFor };
|
|
44
|
+
}
|
|
45
|
+
function parseIgnoredPackagesFromChangesetConfig(jsonContent) {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(jsonContent);
|
|
48
|
+
if (!Array.isArray(parsed.ignore)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
return parsed.ignore.filter((entry) => typeof entry === "string");
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function parseChangesetFrontmatterPackageNames(markdownContent) {
|
|
57
|
+
const frontmatterMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdownContent);
|
|
58
|
+
if (!frontmatterMatch?.[1]) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const packages = new Set;
|
|
62
|
+
for (const line of frontmatterMatch[1].split(/\r?\n/)) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
const match = /^(["']?)(@[^"':\s]+\/[^"':\s]+)\1\s*:/.exec(trimmed);
|
|
65
|
+
if (match?.[2]) {
|
|
66
|
+
packages.add(match[2]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return [...packages].toSorted();
|
|
70
|
+
}
|
|
71
|
+
function isReleaseRelevantSourcePath(relativePath) {
|
|
72
|
+
if (/(^|\/)__tests__\//.test(relativePath)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (/(^|\/)__snapshots__\//.test(relativePath)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (/\.(test|spec)\.[^/]+$/.test(relativePath)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
function getDefaultGitDiffRange() {
|
|
84
|
+
return {
|
|
85
|
+
base: "origin/main",
|
|
86
|
+
head: "HEAD",
|
|
87
|
+
label: "origin/main...HEAD",
|
|
88
|
+
source: "default"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function resolveGitDiffRange(input) {
|
|
92
|
+
if (input.eventName !== "pull_request" || !input.eventPath) {
|
|
93
|
+
return getDefaultGitDiffRange();
|
|
94
|
+
}
|
|
95
|
+
const readEventFile = input.readEventFile ?? ((path) => readFileSync(path, "utf-8"));
|
|
96
|
+
try {
|
|
97
|
+
const payload = JSON.parse(readEventFile(input.eventPath));
|
|
98
|
+
const baseRef = payload.pull_request?.base?.ref;
|
|
99
|
+
const baseSha = payload.pull_request?.base?.sha;
|
|
100
|
+
const headRef = payload.pull_request?.head?.ref;
|
|
101
|
+
const headSha = payload.pull_request?.head?.sha;
|
|
102
|
+
if (!baseSha || !headSha) {
|
|
103
|
+
return getDefaultGitDiffRange();
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
base: baseSha,
|
|
107
|
+
head: headSha,
|
|
108
|
+
label: `${baseRef ?? "base"} (${baseSha})...${headRef ?? "head"} (${headSha})`,
|
|
109
|
+
source: "pull_request"
|
|
110
|
+
};
|
|
111
|
+
} catch {
|
|
112
|
+
return getDefaultGitDiffRange();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function analyzeChangesetReferences(referencedPackages, ignored) {
|
|
116
|
+
const coveredPackages = referencedPackages.filter((packageName) => !ignored.has(packageName));
|
|
117
|
+
const ignoredReferences = referencedPackages.filter((packageName) => ignored.has(packageName));
|
|
118
|
+
return {
|
|
119
|
+
coveredPackages: coveredPackages.toSorted(),
|
|
120
|
+
ignoredReferences: ignoredReferences.toSorted()
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function findIgnoredPackageReferences(input) {
|
|
124
|
+
if (input.ignoredPackages.length === 0 || input.changesetFiles.length === 0) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
const ignored = new Set(input.ignoredPackages);
|
|
128
|
+
const results = [];
|
|
129
|
+
for (const file of input.changesetFiles) {
|
|
130
|
+
const content = input.readChangesetFile(file);
|
|
131
|
+
const referencedPackages = parseChangesetFrontmatterPackageNames(content);
|
|
132
|
+
const invalidReferences = analyzeChangesetReferences(referencedPackages, ignored).ignoredReferences;
|
|
133
|
+
if (invalidReferences.length > 0) {
|
|
134
|
+
results.push({ file, packages: invalidReferences.toSorted() });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return results.toSorted((a, b) => a.file.localeCompare(b.file));
|
|
138
|
+
}
|
|
139
|
+
function loadIgnoredPackages(cwd) {
|
|
140
|
+
const configPath = join(cwd, ".changeset", "config.json");
|
|
141
|
+
if (!existsSync(configPath)) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
return parseIgnoredPackagesFromChangesetConfig(readFileSync(configPath, "utf-8"));
|
|
146
|
+
} catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function analyzeChangedChangesets(cwd, changesetFiles, ignoredPackages) {
|
|
151
|
+
const covered = new Set;
|
|
152
|
+
const ignored = new Set(ignoredPackages);
|
|
153
|
+
const ignoredReferences = [];
|
|
154
|
+
for (const filename of changesetFiles) {
|
|
155
|
+
try {
|
|
156
|
+
const content = readFileSync(join(cwd, ".changeset", filename), "utf-8");
|
|
157
|
+
const referencedPackages = parseChangesetFrontmatterPackageNames(content);
|
|
158
|
+
const analysis = analyzeChangesetReferences(referencedPackages, ignored);
|
|
159
|
+
for (const packageName of analysis.coveredPackages) {
|
|
160
|
+
covered.add(packageName);
|
|
161
|
+
}
|
|
162
|
+
if (analysis.ignoredReferences.length > 0) {
|
|
163
|
+
ignoredReferences.push({
|
|
164
|
+
file: filename,
|
|
165
|
+
packages: analysis.ignoredReferences
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
coveredPackages: [...covered].toSorted(),
|
|
174
|
+
ignoredReferences: ignoredReferences.toSorted((a, b) => a.file.localeCompare(b.file))
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
var COLORS = {
|
|
178
|
+
reset: "\x1B[0m",
|
|
179
|
+
red: "\x1B[31m",
|
|
180
|
+
green: "\x1B[32m",
|
|
181
|
+
yellow: "\x1B[33m",
|
|
182
|
+
blue: "\x1B[34m",
|
|
183
|
+
dim: "\x1B[2m"
|
|
184
|
+
};
|
|
185
|
+
async function runCheckChangeset(options = {}) {
|
|
186
|
+
if (options.skip || process.env["NO_CHANGESET"] === "1") {
|
|
187
|
+
process.stdout.write(`${COLORS.dim}check-changeset skipped (NO_CHANGESET=1)${COLORS.reset}
|
|
188
|
+
`);
|
|
189
|
+
process.exitCode = 0;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (process.env["GITHUB_EVENT_NAME"] === "push") {
|
|
193
|
+
process.stdout.write(`${COLORS.dim}check-changeset skipped (push event)${COLORS.reset}
|
|
194
|
+
`);
|
|
195
|
+
process.exitCode = 0;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const cwd = process.cwd();
|
|
199
|
+
const diffRange = resolveGitDiffRange({
|
|
200
|
+
eventName: process.env["GITHUB_EVENT_NAME"],
|
|
201
|
+
eventPath: process.env["GITHUB_EVENT_PATH"],
|
|
202
|
+
readEventFile: (path) => readFileSync(path, "utf-8")
|
|
203
|
+
});
|
|
204
|
+
let changedFiles;
|
|
205
|
+
try {
|
|
206
|
+
const proc = Bun.spawnSync(["git", "diff", "--name-only", `${diffRange.base}...${diffRange.head}`], { cwd });
|
|
207
|
+
if (proc.exitCode !== 0) {
|
|
208
|
+
process.exitCode = 0;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
changedFiles = proc.stdout.toString().trim().split(`
|
|
212
|
+
`).filter((line) => line.length > 0);
|
|
213
|
+
} catch {
|
|
214
|
+
process.exitCode = 0;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (process.env["GITHUB_EVENT_NAME"]) {
|
|
218
|
+
process.stdout.write(`${COLORS.dim}check-changeset diff basis: ${diffRange.label}${COLORS.reset}
|
|
219
|
+
`);
|
|
220
|
+
}
|
|
221
|
+
const changedPackages = getChangedPackagePaths(changedFiles);
|
|
222
|
+
if (changedPackages.length === 0) {
|
|
223
|
+
process.stdout.write(`${COLORS.green}No package source changes detected.${COLORS.reset}
|
|
224
|
+
`);
|
|
225
|
+
process.exitCode = 0;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const changesetFiles = getChangedChangesetFiles(changedFiles);
|
|
229
|
+
const ignoredPackages = loadIgnoredPackages(cwd);
|
|
230
|
+
const releasablePackages = getReleasableChangedPackages(changedPackages, ignoredPackages);
|
|
231
|
+
const changesetAnalysis = analyzeChangedChangesets(cwd, changesetFiles, ignoredPackages);
|
|
232
|
+
const check = checkChangesetRequired(releasablePackages, changesetFiles, changesetAnalysis.coveredPackages);
|
|
233
|
+
const ignoredReferences = changesetAnalysis.ignoredReferences;
|
|
234
|
+
let hasErrors = false;
|
|
235
|
+
if (!check.ok) {
|
|
236
|
+
process.stderr.write(`${COLORS.red}Changeset coverage missing.${COLORS.reset} ` + `Every changed releasable package must be mentioned in at least one current PR changeset.
|
|
237
|
+
|
|
238
|
+
`);
|
|
239
|
+
process.stderr.write(`Packages missing changeset coverage:
|
|
240
|
+
|
|
241
|
+
`);
|
|
242
|
+
for (const pkg of check.missingFor) {
|
|
243
|
+
process.stderr.write(` ${COLORS.yellow}${toWorkspacePackageName(pkg)}${COLORS.reset}
|
|
244
|
+
`);
|
|
245
|
+
}
|
|
246
|
+
process.stderr.write(`
|
|
247
|
+
Run ${COLORS.blue}bun run changeset${COLORS.reset} for a custom changelog entry, ` + `or add ${COLORS.blue}release:none${COLORS.reset} to skip.
|
|
248
|
+
`);
|
|
249
|
+
hasErrors = true;
|
|
250
|
+
}
|
|
251
|
+
if (ignoredReferences.length > 0) {
|
|
252
|
+
process.stderr.write(`${COLORS.red}Invalid changeset package reference(s).${COLORS.reset}
|
|
253
|
+
|
|
254
|
+
`);
|
|
255
|
+
process.stderr.write(`Changesets must not reference packages listed in .changeset/config.json ignore:
|
|
256
|
+
|
|
257
|
+
`);
|
|
258
|
+
for (const reference of ignoredReferences) {
|
|
259
|
+
process.stderr.write(` ${COLORS.yellow}${reference.file}${COLORS.reset}
|
|
260
|
+
`);
|
|
261
|
+
for (const pkg of reference.packages) {
|
|
262
|
+
process.stderr.write(` - ${pkg}
|
|
263
|
+
`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
process.stderr.write(`
|
|
267
|
+
Update the affected changeset files to remove ignored packages before merging.
|
|
268
|
+
`);
|
|
269
|
+
hasErrors = true;
|
|
270
|
+
}
|
|
271
|
+
if (hasErrors) {
|
|
272
|
+
process.exitCode = 1;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (releasablePackages.length === 0) {
|
|
276
|
+
process.stdout.write(`${COLORS.green}Only ignored package source changes detected.${COLORS.reset}
|
|
277
|
+
`);
|
|
278
|
+
} else {
|
|
279
|
+
process.stdout.write(`${COLORS.green}Changesets cover ${releasablePackages.length} changed package(s).${COLORS.reset}
|
|
280
|
+
`);
|
|
281
|
+
}
|
|
282
|
+
process.exitCode = 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export { getReleasableChangedPackages, getChangedPackagePaths, getChangedChangesetFiles, checkChangesetRequired, parseIgnoredPackagesFromChangesetConfig, parseChangesetFrontmatterPackageNames, resolveGitDiffRange, findIgnoredPackageReferences, runCheckChangeset };
|