@outfitter/tooling 0.3.4 → 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/README.md +12 -3
- package/dist/cli/check-changeset.d.ts +28 -12
- package/dist/cli/check-changeset.js +5 -1
- package/dist/cli/check-exports.d.ts +2 -1
- package/dist/cli/check-exports.js +6 -3
- package/dist/cli/check-home-paths.d.ts +31 -0
- package/dist/cli/check-home-paths.js +12 -0
- package/dist/cli/check-readme-imports.d.ts +2 -1
- package/dist/cli/check-tsdoc.d.ts +4 -1
- package/dist/cli/check-tsdoc.js +16 -10
- package/dist/cli/index.js +16 -4
- 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 +2 -55
- package/dist/cli/pre-push.js +6 -4
- package/dist/index.d.ts +4 -1
- package/dist/shared/@outfitter/tooling-0zjz8eg9.js +106 -0
- package/dist/shared/@outfitter/tooling-2vv5y3s4.js +145 -0
- package/dist/shared/@outfitter/{tooling-875svjnz.js → tooling-5xxctk9b.js} +2 -113
- 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-a59br34g.js +32 -0
- package/dist/shared/@outfitter/tooling-a6q3zh7t.js +86 -0
- package/dist/shared/@outfitter/tooling-ayps7c4x.js +58 -0
- package/dist/shared/@outfitter/{tooling-d363b88r.js → tooling-c8q6mj8z.js} +27 -148
- package/dist/shared/@outfitter/{tooling-wesswf21.d.ts → tooling-cb0b8wsx.d.ts} +9 -11
- package/dist/shared/@outfitter/tooling-f8q38e9z.d.ts +16 -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-mq2xvz96.js +285 -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-y43b117h.d.ts +13 -0
- package/lefthook.yml +5 -1
- package/package.json +10 -4
- package/registry/registry.json +5 -5
- package/dist/shared/@outfitter/tooling-6cxfdx0q.js +0 -187
- package/dist/shared/@outfitter/tooling-h04te11c.js +0 -231
- package/dist/shared/@outfitter/tooling-njw4z34x.d.ts +0 -140
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
computeExpectedExports
|
|
4
|
+
} from "./tooling-a6q3zh7t.js";
|
|
5
|
+
import {
|
|
6
|
+
compareExports
|
|
7
|
+
} from "./tooling-ayps7c4x.js";
|
|
8
|
+
|
|
9
|
+
// packages/tooling/src/cli/check-exports.ts
|
|
10
|
+
import { resolve } from "path";
|
|
11
|
+
var COLORS = {
|
|
12
|
+
reset: "\x1B[0m",
|
|
13
|
+
red: "\x1B[31m",
|
|
14
|
+
green: "\x1B[32m",
|
|
15
|
+
yellow: "\x1B[33m",
|
|
16
|
+
blue: "\x1B[34m",
|
|
17
|
+
dim: "\x1B[2m"
|
|
18
|
+
};
|
|
19
|
+
function resolveJsonMode(options = {}) {
|
|
20
|
+
return options.json ?? process.env["OUTFITTER_JSON"] === "1";
|
|
21
|
+
}
|
|
22
|
+
async function runCheckExports(options = {}) {
|
|
23
|
+
const cwd = process.cwd();
|
|
24
|
+
const configPath = resolve(cwd, "bunup.config.ts");
|
|
25
|
+
let workspaces;
|
|
26
|
+
try {
|
|
27
|
+
const configModule = await import(configPath);
|
|
28
|
+
const rawConfig = configModule.default;
|
|
29
|
+
if (!Array.isArray(rawConfig)) {
|
|
30
|
+
process.stderr.write(`bunup.config.ts must export a workspace array
|
|
31
|
+
`);
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
workspaces = rawConfig;
|
|
36
|
+
} catch {
|
|
37
|
+
process.stderr.write(`Could not load bunup.config.ts from ${cwd}
|
|
38
|
+
`);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const results = [];
|
|
43
|
+
for (const workspace of workspaces) {
|
|
44
|
+
const packageRoot = resolve(cwd, workspace.root);
|
|
45
|
+
const pkgPath = resolve(packageRoot, "package.json");
|
|
46
|
+
let pkg;
|
|
47
|
+
try {
|
|
48
|
+
pkg = await Bun.file(pkgPath).json();
|
|
49
|
+
} catch {
|
|
50
|
+
results.push({ name: workspace.name, status: "ok" });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const actual = typeof pkg.exports === "object" && pkg.exports !== null ? pkg.exports : {};
|
|
54
|
+
const expected = computeExpectedExports(packageRoot, workspace, pkg);
|
|
55
|
+
results.push(compareExports({
|
|
56
|
+
name: workspace.name,
|
|
57
|
+
actual,
|
|
58
|
+
expected,
|
|
59
|
+
path: workspace.root
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
const checkResult = {
|
|
63
|
+
ok: results.every((r) => r.status === "ok"),
|
|
64
|
+
packages: results
|
|
65
|
+
};
|
|
66
|
+
if (resolveJsonMode(options)) {
|
|
67
|
+
process.stdout.write(`${JSON.stringify(checkResult, null, 2)}
|
|
68
|
+
`);
|
|
69
|
+
} else {
|
|
70
|
+
const drifted = results.filter((r) => r.status === "drift");
|
|
71
|
+
if (drifted.length === 0) {
|
|
72
|
+
process.stdout.write(`${COLORS.green}All ${results.length} packages have exports in sync.${COLORS.reset}
|
|
73
|
+
`);
|
|
74
|
+
} else {
|
|
75
|
+
process.stderr.write(`${COLORS.red}Export drift detected in ${drifted.length} package(s):${COLORS.reset}
|
|
76
|
+
|
|
77
|
+
`);
|
|
78
|
+
for (const result of drifted) {
|
|
79
|
+
const drift = result.drift;
|
|
80
|
+
if (!drift)
|
|
81
|
+
continue;
|
|
82
|
+
process.stderr.write(` ${COLORS.yellow}${result.name}${COLORS.reset} ${COLORS.dim}(${drift.path})${COLORS.reset}
|
|
83
|
+
`);
|
|
84
|
+
for (const key of drift.added) {
|
|
85
|
+
process.stderr.write(` ${COLORS.green}+ ${key}${COLORS.reset} ${COLORS.dim}(missing from package.json)${COLORS.reset}
|
|
86
|
+
`);
|
|
87
|
+
}
|
|
88
|
+
for (const key of drift.removed) {
|
|
89
|
+
process.stderr.write(` ${COLORS.red}- ${key}${COLORS.reset} ${COLORS.dim}(not in source)${COLORS.reset}
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
92
|
+
for (const entry of drift.changed) {
|
|
93
|
+
process.stderr.write(` ${COLORS.yellow}~ ${entry.key}${COLORS.reset} ${COLORS.dim}(value mismatch)${COLORS.reset}
|
|
94
|
+
`);
|
|
95
|
+
}
|
|
96
|
+
process.stderr.write(`
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
process.stderr.write(`Run ${COLORS.blue}bun run build${COLORS.reset} to regenerate exports.
|
|
100
|
+
`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
process.exitCode = checkResult.ok ? 0 : 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { resolveJsonMode, runCheckExports };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
canBypassRedPhaseByChangedFiles,
|
|
4
|
+
checkBunVersion,
|
|
5
|
+
createVerificationPlan,
|
|
6
|
+
getChangedFilesForPush,
|
|
7
|
+
getCurrentBranch,
|
|
8
|
+
hasPackageSourceChanges,
|
|
9
|
+
hasRedPhaseBranchInContext,
|
|
10
|
+
isRedPhaseBranch,
|
|
11
|
+
isReleaseBranch,
|
|
12
|
+
isScaffoldBranch,
|
|
13
|
+
printTsdocSummary,
|
|
14
|
+
readPackageScripts
|
|
15
|
+
} from "./tooling-c8q6mj8z.js";
|
|
16
|
+
// packages/tooling/src/cli/pre-push.ts
|
|
17
|
+
var COLORS = {
|
|
18
|
+
reset: "\x1B[0m",
|
|
19
|
+
red: "\x1B[31m",
|
|
20
|
+
green: "\x1B[32m",
|
|
21
|
+
yellow: "\x1B[33m",
|
|
22
|
+
blue: "\x1B[34m"
|
|
23
|
+
};
|
|
24
|
+
function log(msg) {
|
|
25
|
+
process.stdout.write(`${msg}
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
function runScript(scriptName) {
|
|
29
|
+
log("");
|
|
30
|
+
log(`Running: ${COLORS.blue}bun run ${scriptName}${COLORS.reset}`);
|
|
31
|
+
const result = Bun.spawnSync(["bun", "run", scriptName], {
|
|
32
|
+
stdio: ["inherit", "inherit", "inherit"]
|
|
33
|
+
});
|
|
34
|
+
return result.exitCode === 0;
|
|
35
|
+
}
|
|
36
|
+
function maybeSkipForRedPhase(reason, branch) {
|
|
37
|
+
const changedFiles = getChangedFilesForPush();
|
|
38
|
+
if (!changedFiles.deterministic) {
|
|
39
|
+
log(`${COLORS.yellow}RED-phase bypass denied${COLORS.reset}: could not determine full push diff range`);
|
|
40
|
+
log("Running strict verification.");
|
|
41
|
+
log("");
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (!canBypassRedPhaseByChangedFiles(changedFiles)) {
|
|
45
|
+
log(`${COLORS.yellow}RED-phase bypass denied${COLORS.reset}: changed files are not test-only`);
|
|
46
|
+
if (changedFiles.files.length > 0) {
|
|
47
|
+
log(`Changed files (${changedFiles.source}): ${changedFiles.files.join(", ")}`);
|
|
48
|
+
} else {
|
|
49
|
+
log(`No changed files detected in ${changedFiles.source} range. Running strict verification.`);
|
|
50
|
+
}
|
|
51
|
+
log("");
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (reason === "branch") {
|
|
55
|
+
log(`${COLORS.yellow}TDD RED phase${COLORS.reset} detected: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
56
|
+
} else {
|
|
57
|
+
log(`${COLORS.yellow}Scaffold branch${COLORS.reset} with RED phase branch in context: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
58
|
+
}
|
|
59
|
+
log(`${COLORS.yellow}Skipping strict verification${COLORS.reset} - changed files are test-only`);
|
|
60
|
+
log(`Diff source: ${changedFiles.source}`);
|
|
61
|
+
log("");
|
|
62
|
+
log("Remember: GREEN phase (implementation) must make these tests pass!");
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
async function runPrePush(options = {}) {
|
|
66
|
+
log(`${COLORS.blue}Pre-push verify${COLORS.reset} (TDD-aware)`);
|
|
67
|
+
log("");
|
|
68
|
+
if (options.force) {
|
|
69
|
+
log(`${COLORS.yellow}Force flag set${COLORS.reset} - skipping strict verification`);
|
|
70
|
+
process.exitCode = 0;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const versionCheck = checkBunVersion();
|
|
74
|
+
if (!versionCheck.matches) {
|
|
75
|
+
log(`${COLORS.red}Bun version mismatch${COLORS.reset}: running ${versionCheck.actual}, pinned ${versionCheck.expected}`);
|
|
76
|
+
log("Fix: bunx @outfitter/tooling upgrade-bun");
|
|
77
|
+
log("");
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const branch = getCurrentBranch();
|
|
82
|
+
if (isReleaseBranch(branch)) {
|
|
83
|
+
log(`${COLORS.yellow}Release branch detected${COLORS.reset}: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
84
|
+
log(`${COLORS.yellow}Skipping strict verification${COLORS.reset} for automated changeset release push`);
|
|
85
|
+
process.exitCode = 0;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (isRedPhaseBranch(branch)) {
|
|
89
|
+
if (maybeSkipForRedPhase("branch", branch)) {
|
|
90
|
+
process.exitCode = 0;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (isScaffoldBranch(branch)) {
|
|
95
|
+
if (hasRedPhaseBranchInContext(branch)) {
|
|
96
|
+
if (maybeSkipForRedPhase("context", branch)) {
|
|
97
|
+
process.exitCode = 0;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const plan = createVerificationPlan(readPackageScripts());
|
|
103
|
+
if (!plan.ok) {
|
|
104
|
+
log(`${COLORS.red}Strict pre-push verification is not configured${COLORS.reset}`);
|
|
105
|
+
log(plan.error);
|
|
106
|
+
log("");
|
|
107
|
+
log("Add one of:");
|
|
108
|
+
log(" - verify:push");
|
|
109
|
+
log(" - verify:ci");
|
|
110
|
+
log(" - typecheck + (check or lint) + build + test");
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
log(`Running strict verification for branch: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
115
|
+
if (plan.source === "verify:push" || plan.source === "verify:ci") {
|
|
116
|
+
log(`Using \`${plan.source}\` script.`);
|
|
117
|
+
} else {
|
|
118
|
+
log(`Using fallback scripts: ${plan.scripts.join(" -> ")}`);
|
|
119
|
+
}
|
|
120
|
+
for (const scriptName of plan.scripts) {
|
|
121
|
+
if (runScript(scriptName)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
log("");
|
|
125
|
+
log(`${COLORS.red}Verification failed${COLORS.reset} on script: ${scriptName}`);
|
|
126
|
+
log("");
|
|
127
|
+
log("If this is intentional TDD RED phase work, name your branch:");
|
|
128
|
+
log(" - feature-tests");
|
|
129
|
+
log(" - feature/tests");
|
|
130
|
+
log(" - feature_tests");
|
|
131
|
+
process.exitCode = 1;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const changedFiles = getChangedFilesForPush();
|
|
135
|
+
if (hasPackageSourceChanges(changedFiles)) {
|
|
136
|
+
try {
|
|
137
|
+
await printTsdocSummary(log);
|
|
138
|
+
} catch {}
|
|
139
|
+
}
|
|
140
|
+
log("");
|
|
141
|
+
log(`${COLORS.green}Strict verification passed${COLORS.reset}`);
|
|
142
|
+
process.exitCode = 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export { runPrePush };
|
|
@@ -3,44 +3,9 @@ import {
|
|
|
3
3
|
__require
|
|
4
4
|
} from "./tooling-jnrs9rqd.js";
|
|
5
5
|
|
|
6
|
-
// packages/tooling/src/cli/
|
|
6
|
+
// packages/tooling/src/cli/internal/tsdoc-analysis.ts
|
|
7
7
|
import { resolve } from "path";
|
|
8
8
|
import ts from "typescript";
|
|
9
|
-
import { z } from "zod";
|
|
10
|
-
var coverageLevelSchema = z.enum([
|
|
11
|
-
"documented",
|
|
12
|
-
"partial",
|
|
13
|
-
"undocumented"
|
|
14
|
-
]);
|
|
15
|
-
var declarationCoverageSchema = z.object({
|
|
16
|
-
name: z.string(),
|
|
17
|
-
kind: z.string(),
|
|
18
|
-
level: coverageLevelSchema,
|
|
19
|
-
file: z.string(),
|
|
20
|
-
line: z.number()
|
|
21
|
-
});
|
|
22
|
-
var coverageSummarySchema = z.object({
|
|
23
|
-
documented: z.number(),
|
|
24
|
-
partial: z.number(),
|
|
25
|
-
undocumented: z.number(),
|
|
26
|
-
total: z.number(),
|
|
27
|
-
percentage: z.number()
|
|
28
|
-
});
|
|
29
|
-
var packageCoverageSchema = z.object({
|
|
30
|
-
name: z.string(),
|
|
31
|
-
path: z.string(),
|
|
32
|
-
declarations: z.array(declarationCoverageSchema),
|
|
33
|
-
documented: z.number(),
|
|
34
|
-
partial: z.number(),
|
|
35
|
-
undocumented: z.number(),
|
|
36
|
-
total: z.number(),
|
|
37
|
-
percentage: z.number()
|
|
38
|
-
});
|
|
39
|
-
var tsDocCheckResultSchema = z.object({
|
|
40
|
-
ok: z.boolean(),
|
|
41
|
-
packages: z.array(packageCoverageSchema),
|
|
42
|
-
summary: coverageSummarySchema
|
|
43
|
-
});
|
|
44
9
|
function isExportedDeclaration(node) {
|
|
45
10
|
if (ts.isExportDeclaration(node))
|
|
46
11
|
return false;
|
|
@@ -159,24 +124,6 @@ function calculateCoverage(declarations) {
|
|
|
159
124
|
const percentage = Math.round(score / total * 100);
|
|
160
125
|
return { documented, partial, undocumented, total, percentage };
|
|
161
126
|
}
|
|
162
|
-
var COLORS = {
|
|
163
|
-
reset: "\x1B[0m",
|
|
164
|
-
red: "\x1B[31m",
|
|
165
|
-
green: "\x1B[32m",
|
|
166
|
-
yellow: "\x1B[33m",
|
|
167
|
-
blue: "\x1B[34m",
|
|
168
|
-
dim: "\x1B[2m",
|
|
169
|
-
bold: "\x1B[1m"
|
|
170
|
-
};
|
|
171
|
-
function resolveJsonMode(options = {}) {
|
|
172
|
-
return options.json ?? process.env["OUTFITTER_JSON"] === "1";
|
|
173
|
-
}
|
|
174
|
-
function bar(percentage, width = 20) {
|
|
175
|
-
const filled = Math.round(percentage / 100 * width);
|
|
176
|
-
const empty = width - filled;
|
|
177
|
-
const color = percentage >= 80 ? COLORS.green : percentage >= 50 ? COLORS.yellow : COLORS.red;
|
|
178
|
-
return `${color}${"\u2588".repeat(filled)}${COLORS.dim}${"\u2591".repeat(empty)}${COLORS.reset}`;
|
|
179
|
-
}
|
|
180
127
|
function discoverPackages(cwd) {
|
|
181
128
|
const packages = [];
|
|
182
129
|
const seenEntryPoints = new Set;
|
|
@@ -344,63 +291,5 @@ function analyzeCheckTsdoc(options = {}) {
|
|
|
344
291
|
summary
|
|
345
292
|
};
|
|
346
293
|
}
|
|
347
|
-
function printCheckTsdocHuman(result, options) {
|
|
348
|
-
process.stdout.write(`
|
|
349
|
-
${COLORS.bold}TSDoc Coverage Report${COLORS.reset}
|
|
350
|
-
|
|
351
|
-
`);
|
|
352
|
-
for (const pkg of result.packages) {
|
|
353
|
-
const color = pkg.percentage >= 80 ? COLORS.green : pkg.percentage >= 50 ? COLORS.yellow : COLORS.red;
|
|
354
|
-
process.stdout.write(` ${color}${pkg.percentage.toString().padStart(3)}%${COLORS.reset} ${bar(pkg.percentage)} ${pkg.name}
|
|
355
|
-
`);
|
|
356
|
-
if (pkg.total > 0) {
|
|
357
|
-
const parts = [];
|
|
358
|
-
if (pkg.documented > 0)
|
|
359
|
-
parts.push(`${COLORS.green}${pkg.documented} documented${COLORS.reset}`);
|
|
360
|
-
if (pkg.partial > 0)
|
|
361
|
-
parts.push(`${COLORS.yellow}${pkg.partial} partial${COLORS.reset}`);
|
|
362
|
-
if (pkg.undocumented > 0)
|
|
363
|
-
parts.push(`${COLORS.red}${pkg.undocumented} undocumented${COLORS.reset}`);
|
|
364
|
-
process.stdout.write(` ${COLORS.dim}${pkg.total} declarations:${COLORS.reset} ${parts.join(", ")}
|
|
365
|
-
`);
|
|
366
|
-
} else {
|
|
367
|
-
process.stdout.write(` ${COLORS.dim}no exported declarations${COLORS.reset}
|
|
368
|
-
`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
const { summary } = result;
|
|
372
|
-
process.stdout.write(`
|
|
373
|
-
${COLORS.bold}Summary:${COLORS.reset} ${summary.percentage}% coverage (${summary.documented} documented, ${summary.partial} partial, ${summary.undocumented} undocumented of ${summary.total} total)
|
|
374
|
-
`);
|
|
375
|
-
const minCoverage = options?.minCoverage ?? 0;
|
|
376
|
-
if (options?.strict && summary.percentage < minCoverage) {
|
|
377
|
-
process.stderr.write(`
|
|
378
|
-
${COLORS.red}Coverage ${summary.percentage}% is below minimum threshold of ${minCoverage}%${COLORS.reset}
|
|
379
|
-
`);
|
|
380
|
-
}
|
|
381
|
-
process.stdout.write(`
|
|
382
|
-
`);
|
|
383
|
-
}
|
|
384
|
-
async function runCheckTsdoc(options = {}) {
|
|
385
|
-
const result = analyzeCheckTsdoc(options);
|
|
386
|
-
if (!result) {
|
|
387
|
-
process.stderr.write(`No packages found with src/index.ts entry points.
|
|
388
|
-
` + `Searched: packages/*/src/index.ts, apps/*/src/index.ts, src/index.ts
|
|
389
|
-
` + `Use --package <path> to specify a package path explicitly.
|
|
390
|
-
`);
|
|
391
|
-
process.exitCode = 1;
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
if (resolveJsonMode(options)) {
|
|
395
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
396
|
-
`);
|
|
397
|
-
} else {
|
|
398
|
-
printCheckTsdocHuman(result, {
|
|
399
|
-
strict: options.strict,
|
|
400
|
-
minCoverage: options.minCoverage
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
process.exitCode = result.ok ? 0 : 1;
|
|
404
|
-
}
|
|
405
294
|
|
|
406
|
-
export {
|
|
295
|
+
export { isExportedDeclaration, getDeclarationName, getDeclarationKind, classifyDeclaration, analyzeSourceFile, calculateCoverage, discoverPackages, collectReExportedSourceFiles, analyzePackage, analyzeCheckTsdoc };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tooling/src/cli/internal/tsdoc-formatting.ts
|
|
3
|
+
var COLORS = {
|
|
4
|
+
reset: "\x1B[0m",
|
|
5
|
+
red: "\x1B[31m",
|
|
6
|
+
green: "\x1B[32m",
|
|
7
|
+
yellow: "\x1B[33m",
|
|
8
|
+
blue: "\x1B[34m",
|
|
9
|
+
dim: "\x1B[2m",
|
|
10
|
+
bold: "\x1B[1m"
|
|
11
|
+
};
|
|
12
|
+
function resolveJsonMode(options = {}) {
|
|
13
|
+
return options.json ?? process.env["OUTFITTER_JSON"] === "1";
|
|
14
|
+
}
|
|
15
|
+
function bar(percentage, width = 20) {
|
|
16
|
+
const filled = Math.round(percentage / 100 * width);
|
|
17
|
+
const empty = width - filled;
|
|
18
|
+
const color = percentage >= 80 ? COLORS.green : percentage >= 50 ? COLORS.yellow : COLORS.red;
|
|
19
|
+
return `${color}${"\u2588".repeat(filled)}${COLORS.dim}${"\u2591".repeat(empty)}${COLORS.reset}`;
|
|
20
|
+
}
|
|
21
|
+
function printCheckTsdocHuman(result, options) {
|
|
22
|
+
process.stdout.write(`
|
|
23
|
+
${COLORS.bold}TSDoc Coverage Report${COLORS.reset}
|
|
24
|
+
|
|
25
|
+
`);
|
|
26
|
+
for (const pkg of result.packages) {
|
|
27
|
+
const color = pkg.percentage >= 80 ? COLORS.green : pkg.percentage >= 50 ? COLORS.yellow : COLORS.red;
|
|
28
|
+
process.stdout.write(` ${color}${pkg.percentage.toString().padStart(3)}%${COLORS.reset} ${bar(pkg.percentage)} ${pkg.name}
|
|
29
|
+
`);
|
|
30
|
+
if (pkg.total > 0) {
|
|
31
|
+
const parts = [];
|
|
32
|
+
if (pkg.documented > 0)
|
|
33
|
+
parts.push(`${COLORS.green}${pkg.documented} documented${COLORS.reset}`);
|
|
34
|
+
if (pkg.partial > 0)
|
|
35
|
+
parts.push(`${COLORS.yellow}${pkg.partial} partial${COLORS.reset}`);
|
|
36
|
+
if (pkg.undocumented > 0)
|
|
37
|
+
parts.push(`${COLORS.red}${pkg.undocumented} undocumented${COLORS.reset}`);
|
|
38
|
+
process.stdout.write(` ${COLORS.dim}${pkg.total} declarations:${COLORS.reset} ${parts.join(", ")}
|
|
39
|
+
`);
|
|
40
|
+
} else {
|
|
41
|
+
process.stdout.write(` ${COLORS.dim}no exported declarations${COLORS.reset}
|
|
42
|
+
`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const { summary } = result;
|
|
46
|
+
process.stdout.write(`
|
|
47
|
+
${COLORS.bold}Summary:${COLORS.reset} ${summary.percentage}% coverage (${summary.documented} documented, ${summary.partial} partial, ${summary.undocumented} undocumented of ${summary.total} total)
|
|
48
|
+
`);
|
|
49
|
+
const minCoverage = options?.minCoverage ?? 0;
|
|
50
|
+
if (options?.strict && summary.percentage < minCoverage) {
|
|
51
|
+
process.stderr.write(`
|
|
52
|
+
${COLORS.red}Coverage ${summary.percentage}% is below minimum threshold of ${minCoverage}%${COLORS.reset}
|
|
53
|
+
`);
|
|
54
|
+
}
|
|
55
|
+
process.stdout.write(`
|
|
56
|
+
`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { resolveJsonMode, printCheckTsdocHuman };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tooling/src/cli/internal/tsdoc-types.ts
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
var coverageLevelSchema = z.enum([
|
|
5
|
+
"documented",
|
|
6
|
+
"partial",
|
|
7
|
+
"undocumented"
|
|
8
|
+
]);
|
|
9
|
+
var declarationCoverageSchema = z.object({
|
|
10
|
+
name: z.string(),
|
|
11
|
+
kind: z.string(),
|
|
12
|
+
level: coverageLevelSchema,
|
|
13
|
+
file: z.string(),
|
|
14
|
+
line: z.number()
|
|
15
|
+
});
|
|
16
|
+
var coverageSummarySchema = z.object({
|
|
17
|
+
documented: z.number(),
|
|
18
|
+
partial: z.number(),
|
|
19
|
+
undocumented: z.number(),
|
|
20
|
+
total: z.number(),
|
|
21
|
+
percentage: z.number()
|
|
22
|
+
});
|
|
23
|
+
var packageCoverageSchema = z.object({
|
|
24
|
+
name: z.string(),
|
|
25
|
+
path: z.string(),
|
|
26
|
+
declarations: z.array(declarationCoverageSchema),
|
|
27
|
+
documented: z.number(),
|
|
28
|
+
partial: z.number(),
|
|
29
|
+
undocumented: z.number(),
|
|
30
|
+
total: z.number(),
|
|
31
|
+
percentage: z.number()
|
|
32
|
+
});
|
|
33
|
+
var tsDocCheckResultSchema = z.object({
|
|
34
|
+
ok: z.boolean(),
|
|
35
|
+
packages: z.array(packageCoverageSchema),
|
|
36
|
+
summary: coverageSummarySchema
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export { coverageLevelSchema, declarationCoverageSchema, coverageSummarySchema, packageCoverageSchema, tsDocCheckResultSchema };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Get current git branch name */
|
|
2
|
+
declare function getCurrentBranch(): string;
|
|
3
|
+
declare function runGit(args: readonly string[]): {
|
|
4
|
+
readonly ok: boolean;
|
|
5
|
+
readonly lines: readonly string[];
|
|
6
|
+
};
|
|
7
|
+
/** Check if branch is a TDD RED phase branch */
|
|
8
|
+
declare function isRedPhaseBranch(branch: string): boolean;
|
|
9
|
+
/** Check if branch is a scaffold branch */
|
|
10
|
+
declare function isScaffoldBranch(branch: string): boolean;
|
|
11
|
+
/** Check if branch is a changeset release branch */
|
|
12
|
+
declare function isReleaseBranch(branch: string): boolean;
|
|
13
|
+
/** Determine if a file path is test-related */
|
|
14
|
+
declare function isTestOnlyPath(path: string): boolean;
|
|
15
|
+
/** Check if all paths in the list are test-related */
|
|
16
|
+
declare function areFilesTestOnly(paths: readonly string[]): boolean;
|
|
17
|
+
interface PushChangedFiles {
|
|
18
|
+
readonly files: readonly string[];
|
|
19
|
+
readonly deterministic: boolean;
|
|
20
|
+
readonly source: "upstream" | "baseRef" | "undetermined";
|
|
21
|
+
}
|
|
22
|
+
/** Check if bypass is safe: deterministic range with test-only changes */
|
|
23
|
+
declare function canBypassRedPhaseByChangedFiles(changedFiles: PushChangedFiles): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Check whether any changed files are package source files.
|
|
26
|
+
*
|
|
27
|
+
* Matches files under "packages/PKGNAME/src/" (any depth).
|
|
28
|
+
*/
|
|
29
|
+
declare function hasPackageSourceChanges(changedFiles: PushChangedFiles): boolean;
|
|
30
|
+
/** Determine which files have changed for the current push */
|
|
31
|
+
declare function getChangedFilesForPush(): PushChangedFiles;
|
|
32
|
+
/** Check if any branch in context is a RED phase branch */
|
|
33
|
+
declare function hasRedPhaseBranchInContext(currentBranch: string): boolean;
|
|
34
|
+
type ScriptMap = Readonly<Record<string, string | undefined>>;
|
|
35
|
+
type VerificationPlan = {
|
|
36
|
+
readonly ok: true;
|
|
37
|
+
readonly scripts: readonly string[];
|
|
38
|
+
readonly source: "verify:push" | "verify:ci" | "fallback";
|
|
39
|
+
} | {
|
|
40
|
+
readonly ok: false;
|
|
41
|
+
readonly error: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Derive strict pre-push verification from package scripts.
|
|
45
|
+
*
|
|
46
|
+
* Priority:
|
|
47
|
+
* 1) `verify:push`
|
|
48
|
+
* 2) `verify:ci`
|
|
49
|
+
* 3) fallback sequence: `typecheck`, `check|lint`, `build`, `test`
|
|
50
|
+
*/
|
|
51
|
+
declare function createVerificationPlan(scripts: ScriptMap): VerificationPlan;
|
|
52
|
+
/** Read and normalize scripts from package.json */
|
|
53
|
+
declare function readPackageScripts(cwd?: string): ScriptMap;
|
|
54
|
+
interface BunVersionCheckResult {
|
|
55
|
+
readonly matches: boolean;
|
|
56
|
+
readonly expected?: string;
|
|
57
|
+
readonly actual?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check that the local Bun version matches the pinned version in ".bun-version".
|
|
61
|
+
*
|
|
62
|
+
* @param projectRoot - Directory containing ".bun-version" (defaults to cwd)
|
|
63
|
+
* @returns Result indicating whether versions match
|
|
64
|
+
*/
|
|
65
|
+
declare function checkBunVersion(projectRoot?: string): BunVersionCheckResult;
|
|
66
|
+
/**
|
|
67
|
+
* Print a one-line TSDoc coverage summary across all workspace packages.
|
|
68
|
+
*
|
|
69
|
+
* Discovers package entry points ("packages/STAR/src/index.ts"), analyzes
|
|
70
|
+
* TSDoc coverage, and outputs a single summary line. This is advisory
|
|
71
|
+
* only -- the result does not affect the exit code.
|
|
72
|
+
*/
|
|
73
|
+
declare function printTsdocSummary(log: (msg: string) => void): Promise<void>;
|
|
74
|
+
export { getCurrentBranch, runGit, isRedPhaseBranch, isScaffoldBranch, isReleaseBranch, isTestOnlyPath, areFilesTestOnly, PushChangedFiles, canBypassRedPhaseByChangedFiles, hasPackageSourceChanges, getChangedFilesForPush, hasRedPhaseBranchInContext, VerificationPlan, createVerificationPlan, readPackageScripts, BunVersionCheckResult, checkBunVersion, printTsdocSummary };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
printCheckTsdocHuman,
|
|
4
|
+
resolveJsonMode
|
|
5
|
+
} from "./tooling-5ynz680q.js";
|
|
6
|
+
import {
|
|
7
|
+
analyzeCheckTsdoc
|
|
8
|
+
} from "./tooling-5xxctk9b.js";
|
|
9
|
+
// packages/tooling/src/cli/check-tsdoc.ts
|
|
10
|
+
async function runCheckTsdoc(options = {}) {
|
|
11
|
+
const result = analyzeCheckTsdoc(options);
|
|
12
|
+
if (!result) {
|
|
13
|
+
process.stderr.write(`No packages found with src/index.ts entry points.
|
|
14
|
+
` + `Searched: packages/*/src/index.ts, apps/*/src/index.ts, src/index.ts
|
|
15
|
+
` + `Use --package <path> to specify a package path explicitly.
|
|
16
|
+
`);
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (resolveJsonMode(options)) {
|
|
21
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
22
|
+
`);
|
|
23
|
+
} else {
|
|
24
|
+
printCheckTsdocHuman(result, {
|
|
25
|
+
strict: options.strict,
|
|
26
|
+
minCoverage: options.minCoverage
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { runCheckTsdoc };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
entryToSubpath
|
|
4
|
+
} from "./tooling-ayps7c4x.js";
|
|
5
|
+
|
|
6
|
+
// packages/tooling/src/cli/internal/exports-fs.ts
|
|
7
|
+
function matchesExclude(subpath, excludes) {
|
|
8
|
+
return excludes.some((pattern) => new Bun.Glob(pattern).match(subpath));
|
|
9
|
+
}
|
|
10
|
+
var CLI_EXCLUSION_PATTERNS = [
|
|
11
|
+
"**/cli.ts",
|
|
12
|
+
"**/cli/index.ts",
|
|
13
|
+
"**/bin.ts",
|
|
14
|
+
"**/bin/index.ts"
|
|
15
|
+
];
|
|
16
|
+
function isCliEntrypoint(entry) {
|
|
17
|
+
return CLI_EXCLUSION_PATTERNS.some((pattern) => new Bun.Glob(pattern).match(entry));
|
|
18
|
+
}
|
|
19
|
+
function buildExportValue(entry) {
|
|
20
|
+
const distPath = entry.replace(/^src\//, "").replace(/\.[cm]?[jt]sx?$/, "");
|
|
21
|
+
return {
|
|
22
|
+
import: {
|
|
23
|
+
types: `./dist/${distPath}.d.ts`,
|
|
24
|
+
default: `./dist/${distPath}.js`
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function discoverEntries(packageRoot) {
|
|
29
|
+
const glob = new Bun.Glob("src/**/*.ts");
|
|
30
|
+
const entries = [];
|
|
31
|
+
for (const match of glob.scanSync({ cwd: packageRoot, dot: false })) {
|
|
32
|
+
if (match.includes("__tests__") || match.endsWith(".test.ts")) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
entries.push(match);
|
|
36
|
+
}
|
|
37
|
+
return entries.toSorted();
|
|
38
|
+
}
|
|
39
|
+
function addConfigFileExports(expected, pkg) {
|
|
40
|
+
const CONFIG_RE = /\.(json|jsonc|yml|yaml|toml)$/;
|
|
41
|
+
const configFiles = (pkg.files ?? []).filter((file) => CONFIG_RE.test(file) && file !== "package.json");
|
|
42
|
+
for (const file of configFiles) {
|
|
43
|
+
expected[`./${file}`] = `./${file}`;
|
|
44
|
+
let base = file.replace(CONFIG_RE, "");
|
|
45
|
+
const match = base.match(/^(.+)\.preset(?:\.(.+))?$/);
|
|
46
|
+
if (match?.[1]) {
|
|
47
|
+
base = match[2] ? `${match[1]}-${match[2]}` : match[1];
|
|
48
|
+
}
|
|
49
|
+
if (base !== file) {
|
|
50
|
+
expected[`./${base}`] = `./${file}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function computeExpectedExports(packageRoot, workspace, pkg) {
|
|
55
|
+
const entries = discoverEntries(packageRoot);
|
|
56
|
+
const exportsConfig = typeof workspace.config?.exports === "object" ? workspace.config.exports : undefined;
|
|
57
|
+
const excludes = exportsConfig?.exclude ?? [];
|
|
58
|
+
const customExports = exportsConfig?.customExports ?? {};
|
|
59
|
+
const expected = {};
|
|
60
|
+
const subpathEntries = new Map;
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (isCliEntrypoint(entry))
|
|
63
|
+
continue;
|
|
64
|
+
const subpath = entryToSubpath(entry);
|
|
65
|
+
if (matchesExclude(subpath, excludes))
|
|
66
|
+
continue;
|
|
67
|
+
const existing = subpathEntries.get(subpath);
|
|
68
|
+
if (existing) {
|
|
69
|
+
if (!existing.endsWith("/index.ts") && entry.endsWith("/index.ts")) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
subpathEntries.set(subpath, entry);
|
|
74
|
+
}
|
|
75
|
+
for (const [subpath, entry] of subpathEntries) {
|
|
76
|
+
expected[subpath] = buildExportValue(entry);
|
|
77
|
+
}
|
|
78
|
+
for (const [key, value] of Object.entries(customExports)) {
|
|
79
|
+
expected[`./${key.replace(/^\.\//, "")}`] = value;
|
|
80
|
+
}
|
|
81
|
+
addConfigFileExports(expected, pkg);
|
|
82
|
+
expected["./package.json"] = "./package.json";
|
|
83
|
+
return expected;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { computeExpectedExports };
|