@outfitter/tooling 0.3.0 → 0.3.4
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 +21 -21
- package/dist/bun-version-compat.d.ts +2 -0
- package/dist/bun-version-compat.js +10 -0
- package/dist/cli/check-boundary-invocations.js +2 -2
- package/dist/cli/check-bunup-registry.js +2 -2
- package/dist/cli/check-changeset.js +2 -2
- package/dist/cli/check-clean-tree.js +2 -2
- package/dist/cli/check-exports.js +2 -2
- 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 +2 -3
- package/dist/cli/check-readme-imports.js +4 -4
- package/dist/cli/check-tsdoc.js +2 -2
- package/dist/cli/check.js +2 -2
- package/dist/cli/fix.js +2 -2
- package/dist/cli/index.js +49 -1221
- package/dist/cli/init.js +2 -2
- package/dist/cli/pre-push.d.ts +13 -1
- package/dist/cli/pre-push.js +5 -3
- package/dist/cli/upgrade-bun.js +3 -2
- package/dist/index.d.ts +6 -186
- package/dist/index.js +4 -42
- package/dist/registry/build.d.ts +1 -3
- package/dist/registry/build.js +187 -58
- package/dist/registry/index.js +1 -13
- package/dist/registry/schema.js +22 -6
- package/dist/shared/@outfitter/{tooling-9errkcvk.js → tooling-1hez6j9d.js} +1 -1
- package/dist/shared/@outfitter/{tooling-cj5vsa9k.js → tooling-6cxfdx0q.js} +21 -18
- package/dist/shared/@outfitter/{tooling-qk5xgmxr.js → tooling-875svjnz.js} +5 -4
- package/dist/shared/@outfitter/{tooling-mxwc1n8w.js → tooling-9ram55dd.js} +4 -3
- package/dist/shared/@outfitter/{tooling-0x5q15ec.js → tooling-a4bfx4be.js} +1 -1
- package/dist/shared/@outfitter/{tooling-r9976n43.js → tooling-amrbp7cm.js} +6 -4
- package/dist/shared/@outfitter/{tooling-2n2dpsaa.js → tooling-d363b88r.js} +38 -12
- package/dist/shared/@outfitter/{tooling-1y8w5ahg.js → tooling-gcdvsqqp.js} +7 -4
- package/dist/shared/@outfitter/{tooling-enjcenja.js → tooling-h04te11c.js} +6 -4
- package/dist/shared/@outfitter/tooling-ja1zg5yc.js +214 -0
- package/dist/shared/@outfitter/tooling-mkynjra9.js +23 -0
- package/dist/shared/@outfitter/{tooling-9yzd08v1.js → tooling-pq47jv6t.js} +72 -5
- package/dist/shared/@outfitter/tooling-vjmhvpjq.d.ts +29 -0
- package/dist/shared/@outfitter/{tooling-t17gnh9b.js → tooling-wwm97f47.js} +8 -5
- package/dist/version.js +1 -1
- package/package.json +134 -130
- package/registry/registry.json +18 -11
- package/tsconfig.preset.bun.json +5 -5
- package/tsconfig.preset.json +33 -33
- package/biome.json +0 -81
- package/dist/shared/@outfitter/tooling-kcvs6mys.js +0 -1
- package/dist/shared/@outfitter/tooling-wv09k6hr.js +0 -23
- package/dist/shared/chunk-3s189drz.js +0 -4
- package/dist/shared/chunk-7tdgbqb0.js +0 -197
- package/dist/shared/chunk-cmde0fwx.js +0 -421
- /package/dist/shared/@outfitter/{tooling-dvwh9qve.js → tooling-jnrs9rqd.js} +0 -0
|
@@ -11,7 +11,7 @@ function getChangedPackagePaths(files) {
|
|
|
11
11
|
packageNames.add(match[1]);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
return [...packageNames].
|
|
14
|
+
return [...packageNames].toSorted();
|
|
15
15
|
}
|
|
16
16
|
function getChangedChangesetFiles(files) {
|
|
17
17
|
const pattern = /^\.changeset\/([^/]+\.md)$/;
|
|
@@ -22,7 +22,7 @@ function getChangedChangesetFiles(files) {
|
|
|
22
22
|
results.push(match[1]);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
return results.
|
|
25
|
+
return results.toSorted();
|
|
26
26
|
}
|
|
27
27
|
function checkChangesetRequired(changedPackages, changesetFiles) {
|
|
28
28
|
if (changedPackages.length === 0) {
|
|
@@ -57,7 +57,7 @@ function parseChangesetFrontmatterPackageNames(markdownContent) {
|
|
|
57
57
|
packages.add(match[2]);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
return [...packages].
|
|
60
|
+
return [...packages].toSorted();
|
|
61
61
|
}
|
|
62
62
|
function findIgnoredPackageReferences(input) {
|
|
63
63
|
if (input.ignoredPackages.length === 0 || input.changesetFiles.length === 0) {
|
|
@@ -70,10 +70,10 @@ function findIgnoredPackageReferences(input) {
|
|
|
70
70
|
const referencedPackages = parseChangesetFrontmatterPackageNames(content);
|
|
71
71
|
const invalidReferences = referencedPackages.filter((pkg) => ignored.has(pkg));
|
|
72
72
|
if (invalidReferences.length > 0) {
|
|
73
|
-
results.push({ file, packages: invalidReferences.
|
|
73
|
+
results.push({ file, packages: invalidReferences.toSorted() });
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
return results.
|
|
76
|
+
return results.toSorted((a, b) => a.file.localeCompare(b.file));
|
|
77
77
|
}
|
|
78
78
|
function loadIgnoredPackages(cwd) {
|
|
79
79
|
const configPath = join(cwd, ".changeset", "config.json");
|
|
@@ -112,38 +112,41 @@ async function runCheckChangeset(options = {}) {
|
|
|
112
112
|
if (options.skip || process.env["NO_CHANGESET"] === "1") {
|
|
113
113
|
process.stdout.write(`${COLORS.dim}check-changeset skipped (NO_CHANGESET=1)${COLORS.reset}
|
|
114
114
|
`);
|
|
115
|
-
process.
|
|
115
|
+
process.exitCode = 0;
|
|
116
|
+
return;
|
|
116
117
|
}
|
|
117
118
|
if (process.env["GITHUB_EVENT_NAME"] === "push") {
|
|
118
119
|
process.stdout.write(`${COLORS.dim}check-changeset skipped (push event)${COLORS.reset}
|
|
119
120
|
`);
|
|
120
|
-
process.
|
|
121
|
+
process.exitCode = 0;
|
|
122
|
+
return;
|
|
121
123
|
}
|
|
122
124
|
const cwd = process.cwd();
|
|
123
125
|
let changedFiles;
|
|
124
126
|
try {
|
|
125
127
|
const proc = Bun.spawnSync(["git", "diff", "--name-only", "origin/main...HEAD"], { cwd });
|
|
126
128
|
if (proc.exitCode !== 0) {
|
|
127
|
-
process.
|
|
129
|
+
process.exitCode = 0;
|
|
130
|
+
return;
|
|
128
131
|
}
|
|
129
132
|
changedFiles = proc.stdout.toString().trim().split(`
|
|
130
133
|
`).filter((line) => line.length > 0);
|
|
131
134
|
} catch {
|
|
132
|
-
process.
|
|
135
|
+
process.exitCode = 0;
|
|
136
|
+
return;
|
|
133
137
|
}
|
|
134
138
|
const changedPackages = getChangedPackagePaths(changedFiles);
|
|
135
139
|
if (changedPackages.length === 0) {
|
|
136
140
|
process.stdout.write(`${COLORS.green}No package source changes detected.${COLORS.reset}
|
|
137
141
|
`);
|
|
138
|
-
process.
|
|
142
|
+
process.exitCode = 0;
|
|
143
|
+
return;
|
|
139
144
|
}
|
|
140
145
|
const changesetFiles = getChangedChangesetFiles(changedFiles);
|
|
141
146
|
const check = checkChangesetRequired(changedPackages, changesetFiles);
|
|
142
147
|
if (!check.ok) {
|
|
143
|
-
process.stderr.write(`${COLORS.
|
|
144
|
-
|
|
145
|
-
`);
|
|
146
|
-
process.stderr.write(`The following packages have source changes but no changeset:
|
|
148
|
+
process.stderr.write(`${COLORS.yellow}No changeset found.${COLORS.reset} ` + "Consider adding one with `bun run changeset` for a custom changelog entry.\n\n");
|
|
149
|
+
process.stderr.write(`Packages with source changes:
|
|
147
150
|
|
|
148
151
|
`);
|
|
149
152
|
for (const pkg of check.missingFor) {
|
|
@@ -151,9 +154,8 @@ async function runCheckChangeset(options = {}) {
|
|
|
151
154
|
`);
|
|
152
155
|
}
|
|
153
156
|
process.stderr.write(`
|
|
154
|
-
Run ${COLORS.blue}bun run changeset${COLORS.reset}
|
|
157
|
+
Run ${COLORS.blue}bun run changeset${COLORS.reset} for a custom changelog entry, ` + `or add ${COLORS.blue}release:none${COLORS.reset} to skip.
|
|
155
158
|
`);
|
|
156
|
-
process.exit(1);
|
|
157
159
|
}
|
|
158
160
|
const ignoredReferences = getIgnoredReferencesForChangedChangesets(cwd, changesetFiles);
|
|
159
161
|
if (ignoredReferences.length > 0) {
|
|
@@ -174,11 +176,12 @@ Run ${COLORS.blue}bun run changeset${COLORS.reset} to add a changeset, ` + `or a
|
|
|
174
176
|
process.stderr.write(`
|
|
175
177
|
Update the affected changeset files to remove ignored packages before merging.
|
|
176
178
|
`);
|
|
177
|
-
process.
|
|
179
|
+
process.exitCode = 1;
|
|
180
|
+
return;
|
|
178
181
|
}
|
|
179
182
|
process.stdout.write(`${COLORS.green}Changeset found for ${changedPackages.length} changed package(s).${COLORS.reset}
|
|
180
183
|
`);
|
|
181
|
-
process.
|
|
184
|
+
process.exitCode = 0;
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
export { getChangedPackagePaths, getChangedChangesetFiles, checkChangesetRequired, parseIgnoredPackagesFromChangesetConfig, parseChangesetFrontmatterPackageNames, findIgnoredPackageReferences, runCheckChangeset };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
__require
|
|
4
|
-
} from "./tooling-
|
|
4
|
+
} from "./tooling-jnrs9rqd.js";
|
|
5
5
|
|
|
6
6
|
// packages/tooling/src/cli/check-tsdoc.ts
|
|
7
7
|
import { resolve } from "path";
|
|
@@ -225,7 +225,7 @@ function discoverPackages(cwd) {
|
|
|
225
225
|
seenEntryPoints.add(entryPoint);
|
|
226
226
|
} catch {}
|
|
227
227
|
}
|
|
228
|
-
return packages.
|
|
228
|
+
return packages.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
229
229
|
}
|
|
230
230
|
function collectReExportedSourceFiles(sourceFile, program, pkgPath) {
|
|
231
231
|
const result = [];
|
|
@@ -388,7 +388,8 @@ async function runCheckTsdoc(options = {}) {
|
|
|
388
388
|
` + `Searched: packages/*/src/index.ts, apps/*/src/index.ts, src/index.ts
|
|
389
389
|
` + `Use --package <path> to specify a package path explicitly.
|
|
390
390
|
`);
|
|
391
|
-
process.
|
|
391
|
+
process.exitCode = 1;
|
|
392
|
+
return;
|
|
392
393
|
}
|
|
393
394
|
if (resolveJsonMode(options)) {
|
|
394
395
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
@@ -399,7 +400,7 @@ async function runCheckTsdoc(options = {}) {
|
|
|
399
400
|
minCoverage: options.minCoverage
|
|
400
401
|
});
|
|
401
402
|
}
|
|
402
|
-
process.
|
|
403
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
403
404
|
}
|
|
404
405
|
|
|
405
406
|
export { coverageLevelSchema, declarationCoverageSchema, coverageSummarySchema, packageCoverageSchema, tsDocCheckResultSchema, isExportedDeclaration, getDeclarationName, getDeclarationKind, classifyDeclaration, analyzeSourceFile, calculateCoverage, resolveJsonMode, analyzeCheckTsdoc, printCheckTsdocHuman, runCheckTsdoc };
|
|
@@ -33,7 +33,7 @@ function buildUltraciteCommand(options) {
|
|
|
33
33
|
"ultracite",
|
|
34
34
|
"init",
|
|
35
35
|
"--linter",
|
|
36
|
-
"
|
|
36
|
+
"oxlint",
|
|
37
37
|
"--pm",
|
|
38
38
|
"bun",
|
|
39
39
|
"--quiet"
|
|
@@ -49,7 +49,8 @@ async function runInit(cwd = process.cwd()) {
|
|
|
49
49
|
if (!await pkgFile.exists()) {
|
|
50
50
|
process.stderr.write(`No package.json found in current directory
|
|
51
51
|
`);
|
|
52
|
-
process.
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
return;
|
|
53
54
|
}
|
|
54
55
|
const pkg = await pkgFile.json();
|
|
55
56
|
const frameworkFlags = detectFrameworks(pkg);
|
|
@@ -62,7 +63,7 @@ async function runInit(cwd = process.cwd()) {
|
|
|
62
63
|
stdio: ["inherit", "inherit", "inherit"]
|
|
63
64
|
});
|
|
64
65
|
const exitCode = await proc.exited;
|
|
65
|
-
process.
|
|
66
|
+
process.exitCode = exitCode;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export { detectFrameworks, buildUltraciteCommand, runInit };
|
|
@@ -26,7 +26,7 @@ function findBoundaryViolations(entries) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
return violations.
|
|
29
|
+
return violations.toSorted((a, b) => {
|
|
30
30
|
const fileCompare = a.file.localeCompare(b.file);
|
|
31
31
|
if (fileCompare !== 0) {
|
|
32
32
|
return fileCompare;
|
|
@@ -78,13 +78,15 @@ async function runCheckBoundaryInvocations() {
|
|
|
78
78
|
const message = error instanceof Error ? error.message : "unknown read failure";
|
|
79
79
|
process.stderr.write(`Boundary invocation check failed before evaluation: ${message}
|
|
80
80
|
`);
|
|
81
|
-
process.
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
return;
|
|
82
83
|
}
|
|
83
84
|
const violations = findBoundaryViolations(entries);
|
|
84
85
|
if (violations.length === 0) {
|
|
85
86
|
process.stdout.write(`No boundary invocation violations detected in root/apps scripts.
|
|
86
87
|
`);
|
|
87
|
-
process.
|
|
88
|
+
process.exitCode = 0;
|
|
89
|
+
return;
|
|
88
90
|
}
|
|
89
91
|
process.stderr.write(`Boundary invocation violations detected:
|
|
90
92
|
|
|
@@ -94,7 +96,7 @@ async function runCheckBoundaryInvocations() {
|
|
|
94
96
|
`);
|
|
95
97
|
}
|
|
96
98
|
process.stderr.write("\nUse canonical command surfaces (e.g. `outfitter repo ...` or package bins) instead of executing packages/*/src directly.\n");
|
|
97
|
-
process.
|
|
99
|
+
process.exitCode = 1;
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
export { detectBoundaryViolation, findBoundaryViolations, readScriptEntries, runCheckBoundaryInvocations };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
analyzeSourceFile,
|
|
4
4
|
calculateCoverage
|
|
5
|
-
} from "./tooling-
|
|
5
|
+
} from "./tooling-875svjnz.js";
|
|
6
6
|
|
|
7
7
|
// packages/tooling/src/cli/pre-push.ts
|
|
8
8
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -255,31 +255,55 @@ function runScript(scriptName) {
|
|
|
255
255
|
});
|
|
256
256
|
return result.exitCode === 0;
|
|
257
257
|
}
|
|
258
|
+
function checkBunVersion(projectRoot = process.cwd()) {
|
|
259
|
+
const versionFile = join(projectRoot, ".bun-version");
|
|
260
|
+
if (!existsSync(versionFile)) {
|
|
261
|
+
return { matches: true };
|
|
262
|
+
}
|
|
263
|
+
const expected = readFileSync(versionFile, "utf-8").trim();
|
|
264
|
+
const actual = Bun.version;
|
|
265
|
+
if (expected === actual) {
|
|
266
|
+
return { matches: true };
|
|
267
|
+
}
|
|
268
|
+
return { matches: false, expected, actual };
|
|
269
|
+
}
|
|
258
270
|
async function runPrePush(options = {}) {
|
|
259
271
|
log(`${COLORS.blue}Pre-push verify${COLORS.reset} (TDD-aware)`);
|
|
260
272
|
log("");
|
|
273
|
+
if (options.force) {
|
|
274
|
+
log(`${COLORS.yellow}Force flag set${COLORS.reset} - skipping strict verification`);
|
|
275
|
+
process.exitCode = 0;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const versionCheck = checkBunVersion();
|
|
279
|
+
if (!versionCheck.matches) {
|
|
280
|
+
log(`${COLORS.red}Bun version mismatch${COLORS.reset}: running ${versionCheck.actual}, pinned ${versionCheck.expected}`);
|
|
281
|
+
log("Fix: bunx @outfitter/tooling upgrade-bun");
|
|
282
|
+
log("");
|
|
283
|
+
process.exitCode = 1;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
261
286
|
const branch = getCurrentBranch();
|
|
262
287
|
if (isReleaseBranch(branch)) {
|
|
263
288
|
log(`${COLORS.yellow}Release branch detected${COLORS.reset}: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
264
289
|
log(`${COLORS.yellow}Skipping strict verification${COLORS.reset} for automated changeset release push`);
|
|
265
|
-
process.
|
|
290
|
+
process.exitCode = 0;
|
|
291
|
+
return;
|
|
266
292
|
}
|
|
267
293
|
if (isRedPhaseBranch(branch)) {
|
|
268
294
|
if (maybeSkipForRedPhase("branch", branch)) {
|
|
269
|
-
process.
|
|
295
|
+
process.exitCode = 0;
|
|
296
|
+
return;
|
|
270
297
|
}
|
|
271
298
|
}
|
|
272
299
|
if (isScaffoldBranch(branch)) {
|
|
273
300
|
if (hasRedPhaseBranchInContext(branch)) {
|
|
274
301
|
if (maybeSkipForRedPhase("context", branch)) {
|
|
275
|
-
process.
|
|
302
|
+
process.exitCode = 0;
|
|
303
|
+
return;
|
|
276
304
|
}
|
|
277
305
|
}
|
|
278
306
|
}
|
|
279
|
-
if (options.force) {
|
|
280
|
-
log(`${COLORS.yellow}Force flag set${COLORS.reset} - skipping strict verification`);
|
|
281
|
-
process.exit(0);
|
|
282
|
-
}
|
|
283
307
|
const plan = createVerificationPlan(readPackageScripts());
|
|
284
308
|
if (!plan.ok) {
|
|
285
309
|
log(`${COLORS.red}Strict pre-push verification is not configured${COLORS.reset}`);
|
|
@@ -288,7 +312,8 @@ async function runPrePush(options = {}) {
|
|
|
288
312
|
log("Add one of:");
|
|
289
313
|
log(" - verify:ci");
|
|
290
314
|
log(" - typecheck + (check or lint) + build + test");
|
|
291
|
-
process.
|
|
315
|
+
process.exitCode = 1;
|
|
316
|
+
return;
|
|
292
317
|
}
|
|
293
318
|
log(`Running strict verification for branch: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
294
319
|
if (plan.source === "verify:ci") {
|
|
@@ -307,7 +332,8 @@ async function runPrePush(options = {}) {
|
|
|
307
332
|
log(" - feature-tests");
|
|
308
333
|
log(" - feature/tests");
|
|
309
334
|
log(" - feature_tests");
|
|
310
|
-
process.
|
|
335
|
+
process.exitCode = 1;
|
|
336
|
+
return;
|
|
311
337
|
}
|
|
312
338
|
const changedFiles = getChangedFilesForPush();
|
|
313
339
|
if (hasPackageSourceChanges(changedFiles)) {
|
|
@@ -317,7 +343,7 @@ async function runPrePush(options = {}) {
|
|
|
317
343
|
}
|
|
318
344
|
log("");
|
|
319
345
|
log(`${COLORS.green}Strict verification passed${COLORS.reset}`);
|
|
320
|
-
process.
|
|
346
|
+
process.exitCode = 0;
|
|
321
347
|
}
|
|
322
348
|
|
|
323
|
-
export { isRedPhaseBranch, isScaffoldBranch, isReleaseBranch, isTestOnlyPath, areFilesTestOnly, canBypassRedPhaseByChangedFiles, hasPackageSourceChanges, createVerificationPlan, runPrePush };
|
|
349
|
+
export { isRedPhaseBranch, isScaffoldBranch, isReleaseBranch, isTestOnlyPath, areFilesTestOnly, canBypassRedPhaseByChangedFiles, hasPackageSourceChanges, createVerificationPlan, checkBunVersion, runPrePush };
|
|
@@ -23,14 +23,16 @@ async function runCheckCleanTree(options = {}) {
|
|
|
23
23
|
if (diffResult.exitCode !== 0) {
|
|
24
24
|
process.stderr.write(`Failed to run git diff
|
|
25
25
|
`);
|
|
26
|
-
process.
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
return;
|
|
27
28
|
}
|
|
28
29
|
const modified = parseGitDiff(diffResult.stdout.toString());
|
|
29
30
|
const lsResult = Bun.spawnSync(["git", "ls-files", "--others", "--exclude-standard", "--", ...pathArgs], { stderr: "pipe" });
|
|
30
31
|
if (lsResult.exitCode !== 0) {
|
|
31
32
|
process.stderr.write(`Failed to run git ls-files
|
|
32
33
|
`);
|
|
33
|
-
process.
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
34
36
|
}
|
|
35
37
|
const untracked = parseUntrackedFiles(lsResult.stdout.toString());
|
|
36
38
|
const clean = modified.length === 0 && untracked.length === 0;
|
|
@@ -38,7 +40,8 @@ async function runCheckCleanTree(options = {}) {
|
|
|
38
40
|
if (status.clean) {
|
|
39
41
|
process.stdout.write(`${COLORS.green}Working tree is clean.${COLORS.reset}
|
|
40
42
|
`);
|
|
41
|
-
process.
|
|
43
|
+
process.exitCode = 0;
|
|
44
|
+
return;
|
|
42
45
|
}
|
|
43
46
|
process.stderr.write(`${COLORS.red}Working tree is dirty after verification:${COLORS.reset}
|
|
44
47
|
|
|
@@ -64,7 +67,7 @@ This likely means a build step produced uncommitted changes.
|
|
|
64
67
|
`);
|
|
65
68
|
process.stderr.write(`Commit these changes or add them to .gitignore.
|
|
66
69
|
`);
|
|
67
|
-
process.
|
|
70
|
+
process.exitCode = 1;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
export { parseGitDiff, parseUntrackedFiles, isCleanTree, runCheckCleanTree };
|
|
@@ -85,7 +85,7 @@ function discoverEntries(packageRoot) {
|
|
|
85
85
|
}
|
|
86
86
|
entries.push(match);
|
|
87
87
|
}
|
|
88
|
-
return entries.
|
|
88
|
+
return entries.toSorted();
|
|
89
89
|
}
|
|
90
90
|
function addConfigFileExports(expected, pkg) {
|
|
91
91
|
const CONFIG_RE = /\.(json|jsonc|yml|yaml|toml)$/;
|
|
@@ -154,13 +154,15 @@ async function runCheckExports(options = {}) {
|
|
|
154
154
|
if (!Array.isArray(rawConfig)) {
|
|
155
155
|
process.stderr.write(`bunup.config.ts must export a workspace array
|
|
156
156
|
`);
|
|
157
|
-
process.
|
|
157
|
+
process.exitCode = 1;
|
|
158
|
+
return;
|
|
158
159
|
}
|
|
159
160
|
workspaces = rawConfig;
|
|
160
161
|
} catch {
|
|
161
162
|
process.stderr.write(`Could not load bunup.config.ts from ${cwd}
|
|
162
163
|
`);
|
|
163
|
-
process.
|
|
164
|
+
process.exitCode = 1;
|
|
165
|
+
return;
|
|
164
166
|
}
|
|
165
167
|
const results = [];
|
|
166
168
|
for (const workspace of workspaces) {
|
|
@@ -223,7 +225,7 @@ async function runCheckExports(options = {}) {
|
|
|
223
225
|
`);
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
|
-
process.
|
|
228
|
+
process.exitCode = checkResult.ok ? 0 : 1;
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
export { entryToSubpath, compareExports, resolveJsonMode, runCheckExports };
|
|
@@ -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 };
|