@outfitter/tooling 0.3.0 → 0.3.3
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/dist/cli/index.js +3 -6
- package/package.json +3 -2
- package/registry/registry.json +3 -3
- package/dist/cli/check-boundary-invocations.d.ts +0 -34
- package/dist/cli/check-boundary-invocations.js +0 -14
- package/dist/cli/check-bunup-registry.d.ts +0 -36
- package/dist/cli/check-bunup-registry.js +0 -12
- package/dist/cli/check-changeset.d.ts +0 -66
- package/dist/cli/check-changeset.js +0 -20
- package/dist/cli/check-clean-tree.d.ts +0 -36
- package/dist/cli/check-clean-tree.js +0 -14
- package/dist/cli/check-exports.d.ts +0 -2
- package/dist/cli/check-exports.js +0 -14
- package/dist/cli/check-readme-imports.d.ts +0 -61
- package/dist/cli/check-readme-imports.js +0 -198
- package/dist/cli/check-tsdoc.d.ts +0 -2
- package/dist/cli/check-tsdoc.js +0 -36
- package/dist/cli/check.d.ts +0 -19
- package/dist/cli/check.js +0 -10
- package/dist/cli/fix.d.ts +0 -19
- package/dist/cli/fix.js +0 -10
- package/dist/cli/init.d.ts +0 -31
- package/dist/cli/init.js +0 -12
- package/dist/cli/pre-push.d.ts +0 -48
- package/dist/cli/pre-push.js +0 -25
- package/dist/cli/upgrade-bun.d.ts +0 -8
- package/dist/cli/upgrade-bun.js +0 -8
- package/dist/registry/build.d.ts +0 -6
- package/dist/registry/build.js +0 -150
- package/dist/registry/index.d.ts +0 -3
- package/dist/registry/index.js +0 -13
- package/dist/registry/schema.d.ts +0 -2
- package/dist/registry/schema.js +0 -12
- package/dist/shared/@outfitter/tooling-0x5q15ec.js +0 -21
- package/dist/shared/@outfitter/tooling-1y8w5ahg.js +0 -70
- package/dist/shared/@outfitter/tooling-2n2dpsaa.js +0 -323
- package/dist/shared/@outfitter/tooling-9errkcvk.js +0 -21
- package/dist/shared/@outfitter/tooling-9vs606gq.d.ts +0 -3
- package/dist/shared/@outfitter/tooling-9yzd08v1.js +0 -146
- package/dist/shared/@outfitter/tooling-cj5vsa9k.js +0 -184
- package/dist/shared/@outfitter/tooling-ctmgnap5.js +0 -19
- package/dist/shared/@outfitter/tooling-dvwh9qve.js +0 -4
- package/dist/shared/@outfitter/tooling-enjcenja.js +0 -229
- package/dist/shared/@outfitter/tooling-kcvs6mys.js +0 -1
- package/dist/shared/@outfitter/tooling-mxwc1n8w.js +0 -68
- package/dist/shared/@outfitter/tooling-njw4z34x.d.ts +0 -140
- package/dist/shared/@outfitter/tooling-qk5xgmxr.js +0 -405
- package/dist/shared/@outfitter/tooling-r9976n43.js +0 -100
- package/dist/shared/@outfitter/tooling-sjm8nebx.d.ts +0 -109
- package/dist/shared/@outfitter/tooling-t17gnh9b.js +0 -78
- package/dist/shared/@outfitter/tooling-wesswf21.d.ts +0 -59
- package/dist/shared/@outfitter/tooling-wv09k6hr.js +0 -23
- package/dist/version.d.ts +0 -2
- package/dist/version.js +0 -8
|
@@ -1,405 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
__require
|
|
4
|
-
} from "./tooling-dvwh9qve.js";
|
|
5
|
-
|
|
6
|
-
// packages/tooling/src/cli/check-tsdoc.ts
|
|
7
|
-
import { resolve } from "path";
|
|
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
|
-
function isExportedDeclaration(node) {
|
|
45
|
-
if (ts.isExportDeclaration(node))
|
|
46
|
-
return false;
|
|
47
|
-
if (ts.isExportAssignment(node))
|
|
48
|
-
return false;
|
|
49
|
-
const isDeclaration = ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isClassDeclaration(node) || ts.isEnumDeclaration(node) || ts.isVariableStatement(node);
|
|
50
|
-
if (!isDeclaration)
|
|
51
|
-
return false;
|
|
52
|
-
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
53
|
-
if (!modifiers)
|
|
54
|
-
return false;
|
|
55
|
-
return modifiers.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword);
|
|
56
|
-
}
|
|
57
|
-
function getDeclarationName(node) {
|
|
58
|
-
if (ts.isVariableStatement(node)) {
|
|
59
|
-
const decl = node.declarationList.declarations[0];
|
|
60
|
-
if (decl && ts.isIdentifier(decl.name)) {
|
|
61
|
-
return decl.name.text;
|
|
62
|
-
}
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isClassDeclaration(node) || ts.isEnumDeclaration(node)) {
|
|
66
|
-
return node.name?.text;
|
|
67
|
-
}
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
function getDeclarationKind(node) {
|
|
71
|
-
if (ts.isFunctionDeclaration(node))
|
|
72
|
-
return "function";
|
|
73
|
-
if (ts.isInterfaceDeclaration(node))
|
|
74
|
-
return "interface";
|
|
75
|
-
if (ts.isTypeAliasDeclaration(node))
|
|
76
|
-
return "type";
|
|
77
|
-
if (ts.isClassDeclaration(node))
|
|
78
|
-
return "class";
|
|
79
|
-
if (ts.isEnumDeclaration(node))
|
|
80
|
-
return "enum";
|
|
81
|
-
if (ts.isVariableStatement(node))
|
|
82
|
-
return "variable";
|
|
83
|
-
return "unknown";
|
|
84
|
-
}
|
|
85
|
-
function hasJSDocComment(node, sourceFile) {
|
|
86
|
-
const sourceText = sourceFile.getFullText();
|
|
87
|
-
const ranges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
88
|
-
if (!ranges)
|
|
89
|
-
return false;
|
|
90
|
-
return ranges.some((range) => {
|
|
91
|
-
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia)
|
|
92
|
-
return false;
|
|
93
|
-
const text = sourceText.slice(range.pos, range.end);
|
|
94
|
-
return text.startsWith("/**");
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
function memberHasJSDoc(member, sourceFile) {
|
|
98
|
-
const sourceText = sourceFile.getFullText();
|
|
99
|
-
const ranges = ts.getLeadingCommentRanges(sourceText, member.getFullStart());
|
|
100
|
-
if (!ranges)
|
|
101
|
-
return false;
|
|
102
|
-
return ranges.some((range) => {
|
|
103
|
-
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia)
|
|
104
|
-
return false;
|
|
105
|
-
const text = sourceText.slice(range.pos, range.end);
|
|
106
|
-
return text.startsWith("/**");
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
function classifyDeclaration(node, sourceFile) {
|
|
110
|
-
const hasDoc = hasJSDocComment(node, sourceFile);
|
|
111
|
-
if (!hasDoc)
|
|
112
|
-
return "undocumented";
|
|
113
|
-
if (ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node)) {
|
|
114
|
-
const members = node.members;
|
|
115
|
-
if (members.length > 0) {
|
|
116
|
-
const allMembersDocumented = members.every((member) => memberHasJSDoc(member, sourceFile));
|
|
117
|
-
if (!allMembersDocumented)
|
|
118
|
-
return "partial";
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return "documented";
|
|
122
|
-
}
|
|
123
|
-
function analyzeSourceFile(sourceFile) {
|
|
124
|
-
const results = [];
|
|
125
|
-
for (const statement of sourceFile.statements) {
|
|
126
|
-
if (!isExportedDeclaration(statement))
|
|
127
|
-
continue;
|
|
128
|
-
const name = getDeclarationName(statement);
|
|
129
|
-
if (!name)
|
|
130
|
-
continue;
|
|
131
|
-
const kind = getDeclarationKind(statement);
|
|
132
|
-
const level = classifyDeclaration(statement, sourceFile);
|
|
133
|
-
const { line } = sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile));
|
|
134
|
-
results.push({
|
|
135
|
-
name,
|
|
136
|
-
kind,
|
|
137
|
-
level,
|
|
138
|
-
file: sourceFile.fileName,
|
|
139
|
-
line: line + 1
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
return results;
|
|
143
|
-
}
|
|
144
|
-
function calculateCoverage(declarations) {
|
|
145
|
-
const total = declarations.length;
|
|
146
|
-
if (total === 0) {
|
|
147
|
-
return {
|
|
148
|
-
documented: 0,
|
|
149
|
-
partial: 0,
|
|
150
|
-
undocumented: 0,
|
|
151
|
-
total: 0,
|
|
152
|
-
percentage: 100
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
const documented = declarations.filter((d) => d.level === "documented").length;
|
|
156
|
-
const partial = declarations.filter((d) => d.level === "partial").length;
|
|
157
|
-
const undocumented = declarations.filter((d) => d.level === "undocumented").length;
|
|
158
|
-
const score = documented + partial * 0.5;
|
|
159
|
-
const percentage = Math.round(score / total * 100);
|
|
160
|
-
return { documented, partial, undocumented, total, percentage };
|
|
161
|
-
}
|
|
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
|
-
function discoverPackages(cwd) {
|
|
181
|
-
const packages = [];
|
|
182
|
-
const seenEntryPoints = new Set;
|
|
183
|
-
for (const pattern of ["packages/*/src/index.ts", "apps/*/src/index.ts"]) {
|
|
184
|
-
const glob = new Bun.Glob(pattern);
|
|
185
|
-
for (const match of glob.scanSync({ cwd, dot: false })) {
|
|
186
|
-
const parts = match.split("/");
|
|
187
|
-
const rootDir = parts[0];
|
|
188
|
-
const pkgDir = parts[1];
|
|
189
|
-
if (!rootDir || !pkgDir)
|
|
190
|
-
continue;
|
|
191
|
-
const entryPoint = resolve(cwd, match);
|
|
192
|
-
if (seenEntryPoints.has(entryPoint)) {
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
seenEntryPoints.add(entryPoint);
|
|
196
|
-
const pkgRoot = resolve(cwd, rootDir, pkgDir);
|
|
197
|
-
let pkgName = pkgDir;
|
|
198
|
-
try {
|
|
199
|
-
const pkgJson = JSON.parse(__require("fs").readFileSync(resolve(pkgRoot, "package.json"), "utf-8"));
|
|
200
|
-
if (pkgJson.name)
|
|
201
|
-
pkgName = pkgJson.name;
|
|
202
|
-
} catch {}
|
|
203
|
-
packages.push({
|
|
204
|
-
name: pkgName,
|
|
205
|
-
path: pkgRoot,
|
|
206
|
-
entryPoint
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (packages.length === 0) {
|
|
211
|
-
const entryPoint = resolve(cwd, "src/index.ts");
|
|
212
|
-
try {
|
|
213
|
-
__require("fs").accessSync(entryPoint);
|
|
214
|
-
let pkgName = "root";
|
|
215
|
-
try {
|
|
216
|
-
const pkgJson = JSON.parse(__require("fs").readFileSync(resolve(cwd, "package.json"), "utf-8"));
|
|
217
|
-
if (pkgJson.name)
|
|
218
|
-
pkgName = pkgJson.name;
|
|
219
|
-
} catch {}
|
|
220
|
-
packages.push({
|
|
221
|
-
name: pkgName,
|
|
222
|
-
path: cwd,
|
|
223
|
-
entryPoint
|
|
224
|
-
});
|
|
225
|
-
seenEntryPoints.add(entryPoint);
|
|
226
|
-
} catch {}
|
|
227
|
-
}
|
|
228
|
-
return packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
229
|
-
}
|
|
230
|
-
function collectReExportedSourceFiles(sourceFile, program, pkgPath) {
|
|
231
|
-
const result = [];
|
|
232
|
-
const seen = new Set;
|
|
233
|
-
for (const statement of sourceFile.statements) {
|
|
234
|
-
if (!ts.isExportDeclaration(statement))
|
|
235
|
-
continue;
|
|
236
|
-
if (!statement.moduleSpecifier)
|
|
237
|
-
continue;
|
|
238
|
-
if (!ts.isStringLiteral(statement.moduleSpecifier))
|
|
239
|
-
continue;
|
|
240
|
-
const specifier = statement.moduleSpecifier.text;
|
|
241
|
-
if (!specifier.startsWith("."))
|
|
242
|
-
continue;
|
|
243
|
-
const resolvedModule = ts.resolveModuleName(specifier, sourceFile.fileName, program.getCompilerOptions(), ts.sys);
|
|
244
|
-
const resolvedFileName = resolvedModule.resolvedModule?.resolvedFileName;
|
|
245
|
-
if (!resolvedFileName)
|
|
246
|
-
continue;
|
|
247
|
-
if (!resolvedFileName.startsWith(pkgPath))
|
|
248
|
-
continue;
|
|
249
|
-
if (seen.has(resolvedFileName))
|
|
250
|
-
continue;
|
|
251
|
-
seen.add(resolvedFileName);
|
|
252
|
-
const sf = program.getSourceFile(resolvedFileName);
|
|
253
|
-
if (sf)
|
|
254
|
-
result.push(sf);
|
|
255
|
-
}
|
|
256
|
-
return result;
|
|
257
|
-
}
|
|
258
|
-
function analyzePackage(pkg, workspaceCwd) {
|
|
259
|
-
try {
|
|
260
|
-
__require("fs").accessSync(pkg.entryPoint);
|
|
261
|
-
} catch {
|
|
262
|
-
return {
|
|
263
|
-
name: pkg.name,
|
|
264
|
-
path: pkg.path,
|
|
265
|
-
declarations: [],
|
|
266
|
-
documented: 0,
|
|
267
|
-
partial: 0,
|
|
268
|
-
undocumented: 0,
|
|
269
|
-
total: 0,
|
|
270
|
-
percentage: 0
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
let tsconfigPath = resolve(pkg.path, "tsconfig.json");
|
|
274
|
-
try {
|
|
275
|
-
__require("fs").accessSync(tsconfigPath);
|
|
276
|
-
} catch {
|
|
277
|
-
tsconfigPath = resolve(workspaceCwd, "tsconfig.json");
|
|
278
|
-
}
|
|
279
|
-
const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
280
|
-
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, pkg.path);
|
|
281
|
-
const program = ts.createProgram({
|
|
282
|
-
rootNames: [pkg.entryPoint],
|
|
283
|
-
options: parsedConfig.options,
|
|
284
|
-
host: ts.createCompilerHost(parsedConfig.options)
|
|
285
|
-
});
|
|
286
|
-
const sourceFile = program.getSourceFile(pkg.entryPoint);
|
|
287
|
-
if (!sourceFile) {
|
|
288
|
-
return {
|
|
289
|
-
name: pkg.name,
|
|
290
|
-
path: pkg.path,
|
|
291
|
-
declarations: [],
|
|
292
|
-
documented: 0,
|
|
293
|
-
partial: 0,
|
|
294
|
-
undocumented: 0,
|
|
295
|
-
total: 0,
|
|
296
|
-
percentage: 0
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
const declarations = analyzeSourceFile(sourceFile);
|
|
300
|
-
const reExportedFiles = collectReExportedSourceFiles(sourceFile, program, pkg.path);
|
|
301
|
-
for (const sf of reExportedFiles) {
|
|
302
|
-
declarations.push(...analyzeSourceFile(sf));
|
|
303
|
-
}
|
|
304
|
-
const stats = calculateCoverage(declarations);
|
|
305
|
-
return {
|
|
306
|
-
name: pkg.name,
|
|
307
|
-
path: pkg.path,
|
|
308
|
-
declarations,
|
|
309
|
-
...stats
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
function analyzeCheckTsdoc(options = {}) {
|
|
313
|
-
const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
|
|
314
|
-
const minCoverage = options.minCoverage ?? 0;
|
|
315
|
-
let packages;
|
|
316
|
-
if (options.paths && options.paths.length > 0) {
|
|
317
|
-
packages = options.paths.map((p) => {
|
|
318
|
-
const absPath = resolve(cwd, p);
|
|
319
|
-
const entryPoint = resolve(absPath, "src/index.ts");
|
|
320
|
-
let name = p;
|
|
321
|
-
try {
|
|
322
|
-
const pkgJson = JSON.parse(__require("fs").readFileSync(resolve(absPath, "package.json"), "utf-8"));
|
|
323
|
-
if (pkgJson.name)
|
|
324
|
-
name = pkgJson.name;
|
|
325
|
-
} catch {}
|
|
326
|
-
return { name, path: absPath, entryPoint };
|
|
327
|
-
});
|
|
328
|
-
} else {
|
|
329
|
-
packages = discoverPackages(cwd);
|
|
330
|
-
}
|
|
331
|
-
if (packages.length === 0) {
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
const packageResults = [];
|
|
335
|
-
for (const pkg of packages) {
|
|
336
|
-
packageResults.push(analyzePackage(pkg, cwd));
|
|
337
|
-
}
|
|
338
|
-
const allDeclarations = packageResults.flatMap((p) => p.declarations);
|
|
339
|
-
const summary = calculateCoverage(allDeclarations);
|
|
340
|
-
const ok = !options.strict || summary.percentage >= minCoverage;
|
|
341
|
-
return {
|
|
342
|
-
ok,
|
|
343
|
-
packages: packageResults,
|
|
344
|
-
summary
|
|
345
|
-
};
|
|
346
|
-
}
|
|
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.exit(1);
|
|
392
|
-
}
|
|
393
|
-
if (resolveJsonMode(options)) {
|
|
394
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
395
|
-
`);
|
|
396
|
-
} else {
|
|
397
|
-
printCheckTsdocHuman(result, {
|
|
398
|
-
strict: options.strict,
|
|
399
|
-
minCoverage: options.minCoverage
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
process.exit(result.ok ? 0 : 1);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
export { coverageLevelSchema, declarationCoverageSchema, coverageSummarySchema, packageCoverageSchema, tsDocCheckResultSchema, isExportedDeclaration, getDeclarationName, getDeclarationKind, classifyDeclaration, analyzeSourceFile, calculateCoverage, resolveJsonMode, analyzeCheckTsdoc, printCheckTsdocHuman, runCheckTsdoc };
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/tooling/src/cli/check-boundary-invocations.ts
|
|
3
|
-
import { relative, resolve } from "path";
|
|
4
|
-
var ROOT_RUNS_PACKAGE_SRC = /\bbun(?:x)?\s+(?:run\s+)?(?:\.\.\/|\.\/)?packages\/[^/\s]+\/src\/\S+/;
|
|
5
|
-
var CD_PACKAGE_THEN_RUNS_SRC = /\bcd\s+(?:\.\.\/|\.\/)?packages\/[^/\s]+\s*&&\s*bun(?:x)?\s+(?:run\s+)?(?:\.\.\/|\.\/)?src\/\S+/;
|
|
6
|
-
function detectBoundaryViolation(location) {
|
|
7
|
-
if (ROOT_RUNS_PACKAGE_SRC.test(location.command)) {
|
|
8
|
-
return { ...location, rule: "root-runs-package-src" };
|
|
9
|
-
}
|
|
10
|
-
if (CD_PACKAGE_THEN_RUNS_SRC.test(location.command)) {
|
|
11
|
-
return { ...location, rule: "cd-package-then-runs-src" };
|
|
12
|
-
}
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
function findBoundaryViolations(entries) {
|
|
16
|
-
const violations = [];
|
|
17
|
-
for (const entry of entries) {
|
|
18
|
-
for (const [scriptName, command] of Object.entries(entry.scripts)) {
|
|
19
|
-
const violation = detectBoundaryViolation({
|
|
20
|
-
file: entry.file,
|
|
21
|
-
scriptName,
|
|
22
|
-
command
|
|
23
|
-
});
|
|
24
|
-
if (violation) {
|
|
25
|
-
violations.push(violation);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return violations.sort((a, b) => {
|
|
30
|
-
const fileCompare = a.file.localeCompare(b.file);
|
|
31
|
-
if (fileCompare !== 0) {
|
|
32
|
-
return fileCompare;
|
|
33
|
-
}
|
|
34
|
-
return a.scriptName.localeCompare(b.scriptName);
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
async function readScriptEntries(cwd, options = {}) {
|
|
38
|
-
const entries = [];
|
|
39
|
-
const rootPackagePath = resolve(cwd, "package.json");
|
|
40
|
-
const candidatePaths = [rootPackagePath];
|
|
41
|
-
if (options.appManifestRelativePaths) {
|
|
42
|
-
for (const file of options.appManifestRelativePaths) {
|
|
43
|
-
candidatePaths.push(resolve(cwd, file));
|
|
44
|
-
}
|
|
45
|
-
} else {
|
|
46
|
-
const appPackageGlob = new Bun.Glob("apps/*/package.json");
|
|
47
|
-
for (const match of appPackageGlob.scanSync({ cwd })) {
|
|
48
|
-
candidatePaths.push(resolve(cwd, match));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const readPackageJson = options.readPackageJson ?? (async (filePath) => await Bun.file(filePath).json());
|
|
52
|
-
for (const filePath of candidatePaths) {
|
|
53
|
-
const isRootManifest = filePath === rootPackagePath;
|
|
54
|
-
try {
|
|
55
|
-
const pkg = await readPackageJson(filePath);
|
|
56
|
-
if (!pkg.scripts) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
entries.push({
|
|
60
|
-
file: relative(cwd, filePath),
|
|
61
|
-
scripts: pkg.scripts
|
|
62
|
-
});
|
|
63
|
-
} catch (error) {
|
|
64
|
-
if (isRootManifest) {
|
|
65
|
-
const message = error instanceof Error ? error.message : "unknown parse error";
|
|
66
|
-
throw new Error(`Failed to read root package manifest (${filePath}): ${message}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return entries;
|
|
71
|
-
}
|
|
72
|
-
async function runCheckBoundaryInvocations() {
|
|
73
|
-
const cwd = process.cwd();
|
|
74
|
-
let entries;
|
|
75
|
-
try {
|
|
76
|
-
entries = await readScriptEntries(cwd);
|
|
77
|
-
} catch (error) {
|
|
78
|
-
const message = error instanceof Error ? error.message : "unknown read failure";
|
|
79
|
-
process.stderr.write(`Boundary invocation check failed before evaluation: ${message}
|
|
80
|
-
`);
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
const violations = findBoundaryViolations(entries);
|
|
84
|
-
if (violations.length === 0) {
|
|
85
|
-
process.stdout.write(`No boundary invocation violations detected in root/apps scripts.
|
|
86
|
-
`);
|
|
87
|
-
process.exit(0);
|
|
88
|
-
}
|
|
89
|
-
process.stderr.write(`Boundary invocation violations detected:
|
|
90
|
-
|
|
91
|
-
`);
|
|
92
|
-
for (const violation of violations) {
|
|
93
|
-
process.stderr.write(`- ${violation.file}#${violation.scriptName}: ${violation.command}
|
|
94
|
-
`);
|
|
95
|
-
}
|
|
96
|
-
process.stderr.write("\nUse canonical command surfaces (e.g. `outfitter repo ...` or package bins) instead of executing packages/*/src directly.\n");
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export { detectBoundaryViolation, findBoundaryViolations, readScriptEntries, runCheckBoundaryInvocations };
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { ZodType } from "zod";
|
|
2
|
-
/**
|
|
3
|
-
* File entry in a block.
|
|
4
|
-
*/
|
|
5
|
-
interface FileEntry {
|
|
6
|
-
/** Destination path relative to project root */
|
|
7
|
-
path: string;
|
|
8
|
-
/** File contents (embedded in registry) */
|
|
9
|
-
content: string;
|
|
10
|
-
/** Whether to chmod +x after copying */
|
|
11
|
-
executable?: boolean | undefined;
|
|
12
|
-
/** Whether to process as a template (future) */
|
|
13
|
-
template?: boolean | undefined;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Schema for a file entry in a block.
|
|
17
|
-
* Represents a file that will be copied to the user's project.
|
|
18
|
-
*/
|
|
19
|
-
declare const FileEntrySchema: ZodType<FileEntry>;
|
|
20
|
-
/**
|
|
21
|
-
* Block in the registry.
|
|
22
|
-
*/
|
|
23
|
-
interface Block {
|
|
24
|
-
/** Block name (matches the key in blocks record) */
|
|
25
|
-
name: string;
|
|
26
|
-
/** Human-readable description */
|
|
27
|
-
description: string;
|
|
28
|
-
/** Files included in this block */
|
|
29
|
-
files?: FileEntry[] | undefined;
|
|
30
|
-
/** npm dependencies to add to package.json */
|
|
31
|
-
dependencies?: Record<string, string> | undefined;
|
|
32
|
-
/** npm devDependencies to add to package.json */
|
|
33
|
-
devDependencies?: Record<string, string> | undefined;
|
|
34
|
-
/** Other blocks this block extends (for composite blocks) */
|
|
35
|
-
extends?: string[] | undefined;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Schema for a block in the registry.
|
|
39
|
-
* A block is a collection of related files that can be added together.
|
|
40
|
-
*/
|
|
41
|
-
declare const BlockSchema: ZodType<Block>;
|
|
42
|
-
/**
|
|
43
|
-
* Complete registry structure.
|
|
44
|
-
*/
|
|
45
|
-
interface Registry {
|
|
46
|
-
/** Registry schema version */
|
|
47
|
-
version: string;
|
|
48
|
-
/** Map of block name to block definition */
|
|
49
|
-
blocks: Record<string, Block>;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Schema for the complete registry.
|
|
53
|
-
* Contains all available blocks with their files and metadata.
|
|
54
|
-
*/
|
|
55
|
-
declare const RegistrySchema: ZodType<Registry>;
|
|
56
|
-
/**
|
|
57
|
-
* Block definition used in the build script.
|
|
58
|
-
* Specifies how to collect source files into a block.
|
|
59
|
-
*/
|
|
60
|
-
interface BlockDefinition {
|
|
61
|
-
/** Human-readable description */
|
|
62
|
-
description: string;
|
|
63
|
-
/** Source file paths (relative to repo root) */
|
|
64
|
-
files?: string[];
|
|
65
|
-
/** Remap source paths to destination paths */
|
|
66
|
-
remap?: Record<string, string>;
|
|
67
|
-
/** npm dependencies */
|
|
68
|
-
dependencies?: Record<string, string>;
|
|
69
|
-
/** npm devDependencies */
|
|
70
|
-
devDependencies?: Record<string, string>;
|
|
71
|
-
/** Other blocks this block extends */
|
|
72
|
-
extends?: string[];
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Configuration for the registry build.
|
|
76
|
-
*/
|
|
77
|
-
interface RegistryBuildConfig {
|
|
78
|
-
/** Registry schema version */
|
|
79
|
-
version: string;
|
|
80
|
-
/** Block definitions */
|
|
81
|
-
blocks: Record<string, BlockDefinition>;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Result of adding a block to a project.
|
|
85
|
-
*/
|
|
86
|
-
interface AddBlockResult {
|
|
87
|
-
/** Files that were created */
|
|
88
|
-
created: string[];
|
|
89
|
-
/** Files that were skipped (already exist) */
|
|
90
|
-
skipped: string[];
|
|
91
|
-
/** Files that were overwritten (with --force) */
|
|
92
|
-
overwritten: string[];
|
|
93
|
-
/** Dependencies added to package.json */
|
|
94
|
-
dependencies: Record<string, string>;
|
|
95
|
-
/** devDependencies added to package.json */
|
|
96
|
-
devDependencies: Record<string, string>;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Options for the add command.
|
|
100
|
-
*/
|
|
101
|
-
interface AddBlockOptions {
|
|
102
|
-
/** Overwrite existing files */
|
|
103
|
-
force?: boolean;
|
|
104
|
-
/** Show what would be added without making changes */
|
|
105
|
-
dryRun?: boolean;
|
|
106
|
-
/** Working directory (defaults to cwd) */
|
|
107
|
-
cwd?: string;
|
|
108
|
-
}
|
|
109
|
-
export { FileEntry, FileEntrySchema, Block, BlockSchema, Registry, RegistrySchema, BlockDefinition, RegistryBuildConfig, AddBlockResult, AddBlockOptions };
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/tooling/src/cli/check-bunup-registry.ts
|
|
3
|
-
import { resolve } from "path";
|
|
4
|
-
function extractBunupFilterName(script) {
|
|
5
|
-
const match = script.match(/bunup\s+--filter\s+(\S+)/);
|
|
6
|
-
return match?.[1] ?? null;
|
|
7
|
-
}
|
|
8
|
-
function findUnregisteredPackages(packagesWithFilter, registeredNames) {
|
|
9
|
-
const registered = new Set(registeredNames);
|
|
10
|
-
const missing = packagesWithFilter.filter((name) => !registered.has(name)).sort();
|
|
11
|
-
return {
|
|
12
|
-
ok: missing.length === 0,
|
|
13
|
-
missing
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
var COLORS = {
|
|
17
|
-
reset: "\x1B[0m",
|
|
18
|
-
red: "\x1B[31m",
|
|
19
|
-
green: "\x1B[32m",
|
|
20
|
-
yellow: "\x1B[33m",
|
|
21
|
-
blue: "\x1B[34m",
|
|
22
|
-
dim: "\x1B[2m"
|
|
23
|
-
};
|
|
24
|
-
async function runCheckBunupRegistry() {
|
|
25
|
-
const cwd = process.cwd();
|
|
26
|
-
const configPath = resolve(cwd, "bunup.config.ts");
|
|
27
|
-
let registeredNames;
|
|
28
|
-
try {
|
|
29
|
-
const configModule = await import(configPath);
|
|
30
|
-
const rawConfig = configModule.default;
|
|
31
|
-
if (!Array.isArray(rawConfig)) {
|
|
32
|
-
process.stderr.write(`bunup.config.ts must export a workspace array
|
|
33
|
-
`);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
registeredNames = rawConfig.map((entry) => entry.name);
|
|
37
|
-
} catch {
|
|
38
|
-
process.stderr.write(`Could not load bunup.config.ts from ${cwd}
|
|
39
|
-
`);
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
const packagesWithFilter = [];
|
|
43
|
-
const glob = new Bun.Glob("{packages,apps}/*/package.json");
|
|
44
|
-
for (const match of glob.scanSync({ cwd })) {
|
|
45
|
-
const pkgPath = resolve(cwd, match);
|
|
46
|
-
try {
|
|
47
|
-
const pkg = await Bun.file(pkgPath).json();
|
|
48
|
-
const buildScript = pkg.scripts?.["build"];
|
|
49
|
-
if (!buildScript)
|
|
50
|
-
continue;
|
|
51
|
-
const filterName = extractBunupFilterName(buildScript);
|
|
52
|
-
if (filterName) {
|
|
53
|
-
packagesWithFilter.push(filterName);
|
|
54
|
-
}
|
|
55
|
-
} catch {}
|
|
56
|
-
}
|
|
57
|
-
const result = findUnregisteredPackages(packagesWithFilter, registeredNames);
|
|
58
|
-
if (result.ok) {
|
|
59
|
-
process.stdout.write(`${COLORS.green}All ${packagesWithFilter.length} packages with bunup --filter are registered in bunup.config.ts.${COLORS.reset}
|
|
60
|
-
`);
|
|
61
|
-
process.exit(0);
|
|
62
|
-
}
|
|
63
|
-
process.stderr.write(`${COLORS.red}${result.missing.length} package(s) have bunup --filter build scripts but are not registered in bunup.config.ts:${COLORS.reset}
|
|
64
|
-
|
|
65
|
-
`);
|
|
66
|
-
for (const name of result.missing) {
|
|
67
|
-
process.stderr.write(` ${COLORS.yellow}${name}${COLORS.reset} ${COLORS.dim}(missing from workspace array)${COLORS.reset}
|
|
68
|
-
`);
|
|
69
|
-
}
|
|
70
|
-
process.stderr.write(`
|
|
71
|
-
Add the missing entries to ${COLORS.blue}bunup.config.ts${COLORS.reset} defineWorkspace array.
|
|
72
|
-
`);
|
|
73
|
-
process.stderr.write(`Without registration, ${COLORS.dim}bunup --filter <name>${COLORS.reset} silently exits with no output.
|
|
74
|
-
`);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export { extractBunupFilterName, findUnregisteredPackages, runCheckBunupRegistry };
|