@outfitter/tooling 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -1
- package/biome.json +1 -1
- package/dist/cli/check-changeset.d.ts +12 -10
- package/dist/cli/check-changeset.js +7 -1
- package/dist/cli/check-exports.d.ts +1 -1
- package/dist/cli/check-readme-imports.d.ts +1 -1
- package/dist/cli/check-tsdoc.d.ts +2 -0
- package/dist/cli/check-tsdoc.js +36 -0
- package/dist/cli/index.js +166 -24
- package/dist/cli/pre-push.d.ts +8 -1
- package/dist/cli/pre-push.js +6 -1
- package/dist/index.d.ts +79 -4
- package/dist/index.js +17 -6
- package/dist/registry/build.d.ts +1 -1
- package/dist/registry/build.js +8 -5
- package/dist/registry/index.d.ts +2 -2
- package/dist/registry/index.js +1 -1
- package/dist/registry/schema.d.ts +1 -1
- package/dist/registry/schema.js +1 -1
- package/dist/shared/@outfitter/{tooling-8sd32ts6.js → tooling-2n2dpsaa.js} +48 -2
- package/dist/shared/@outfitter/tooling-cj5vsa9k.js +184 -0
- package/dist/shared/@outfitter/tooling-njw4z34x.d.ts +140 -0
- package/dist/shared/@outfitter/tooling-qk5xgmxr.js +405 -0
- package/dist/shared/@outfitter/{tooling-g83d0kjv.js → tooling-wv09k6hr.js} +3 -3
- package/dist/shared/chunk-cmde0fwx.js +421 -0
- package/dist/version.d.ts +1 -1
- package/package.json +12 -6
- package/registry/registry.json +6 -6
- package/dist/shared/@outfitter/tooling-3w8vr2w3.js +0 -94
- package/dist/shared/chunk-8aenrm6f.js +0 -18
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./chunk-3s189drz.js";
|
|
4
|
+
|
|
5
|
+
// src/cli/check-tsdoc.ts
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
var coverageLevelSchema = z.enum([
|
|
10
|
+
"documented",
|
|
11
|
+
"partial",
|
|
12
|
+
"undocumented"
|
|
13
|
+
]);
|
|
14
|
+
var declarationCoverageSchema = z.object({
|
|
15
|
+
name: z.string(),
|
|
16
|
+
kind: z.string(),
|
|
17
|
+
level: coverageLevelSchema,
|
|
18
|
+
file: z.string(),
|
|
19
|
+
line: z.number()
|
|
20
|
+
});
|
|
21
|
+
var coverageSummarySchema = z.object({
|
|
22
|
+
documented: z.number(),
|
|
23
|
+
partial: z.number(),
|
|
24
|
+
undocumented: z.number(),
|
|
25
|
+
total: z.number(),
|
|
26
|
+
percentage: z.number()
|
|
27
|
+
});
|
|
28
|
+
var packageCoverageSchema = z.object({
|
|
29
|
+
name: z.string(),
|
|
30
|
+
path: z.string(),
|
|
31
|
+
declarations: z.array(declarationCoverageSchema),
|
|
32
|
+
documented: z.number(),
|
|
33
|
+
partial: z.number(),
|
|
34
|
+
undocumented: z.number(),
|
|
35
|
+
total: z.number(),
|
|
36
|
+
percentage: z.number()
|
|
37
|
+
});
|
|
38
|
+
var tsDocCheckResultSchema = z.object({
|
|
39
|
+
ok: z.boolean(),
|
|
40
|
+
packages: z.array(packageCoverageSchema),
|
|
41
|
+
summary: coverageSummarySchema
|
|
42
|
+
});
|
|
43
|
+
function isExportedDeclaration(node) {
|
|
44
|
+
if (ts.isExportDeclaration(node))
|
|
45
|
+
return false;
|
|
46
|
+
if (ts.isExportAssignment(node))
|
|
47
|
+
return false;
|
|
48
|
+
const isDeclaration = ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isClassDeclaration(node) || ts.isEnumDeclaration(node) || ts.isVariableStatement(node);
|
|
49
|
+
if (!isDeclaration)
|
|
50
|
+
return false;
|
|
51
|
+
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
52
|
+
if (!modifiers)
|
|
53
|
+
return false;
|
|
54
|
+
return modifiers.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword);
|
|
55
|
+
}
|
|
56
|
+
function getDeclarationName(node) {
|
|
57
|
+
if (ts.isVariableStatement(node)) {
|
|
58
|
+
const decl = node.declarationList.declarations[0];
|
|
59
|
+
if (decl && ts.isIdentifier(decl.name)) {
|
|
60
|
+
return decl.name.text;
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isClassDeclaration(node) || ts.isEnumDeclaration(node)) {
|
|
65
|
+
return node.name?.text;
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
function getDeclarationKind(node) {
|
|
70
|
+
if (ts.isFunctionDeclaration(node))
|
|
71
|
+
return "function";
|
|
72
|
+
if (ts.isInterfaceDeclaration(node))
|
|
73
|
+
return "interface";
|
|
74
|
+
if (ts.isTypeAliasDeclaration(node))
|
|
75
|
+
return "type";
|
|
76
|
+
if (ts.isClassDeclaration(node))
|
|
77
|
+
return "class";
|
|
78
|
+
if (ts.isEnumDeclaration(node))
|
|
79
|
+
return "enum";
|
|
80
|
+
if (ts.isVariableStatement(node))
|
|
81
|
+
return "variable";
|
|
82
|
+
return "unknown";
|
|
83
|
+
}
|
|
84
|
+
function hasJSDocComment(node, sourceFile) {
|
|
85
|
+
const sourceText = sourceFile.getFullText();
|
|
86
|
+
const ranges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
87
|
+
if (!ranges)
|
|
88
|
+
return false;
|
|
89
|
+
return ranges.some((range) => {
|
|
90
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia)
|
|
91
|
+
return false;
|
|
92
|
+
const text = sourceText.slice(range.pos, range.end);
|
|
93
|
+
return text.startsWith("/**");
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function memberHasJSDoc(member, sourceFile) {
|
|
97
|
+
const sourceText = sourceFile.getFullText();
|
|
98
|
+
const ranges = ts.getLeadingCommentRanges(sourceText, member.getFullStart());
|
|
99
|
+
if (!ranges)
|
|
100
|
+
return false;
|
|
101
|
+
return ranges.some((range) => {
|
|
102
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia)
|
|
103
|
+
return false;
|
|
104
|
+
const text = sourceText.slice(range.pos, range.end);
|
|
105
|
+
return text.startsWith("/**");
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function classifyDeclaration(node, sourceFile) {
|
|
109
|
+
const hasDoc = hasJSDocComment(node, sourceFile);
|
|
110
|
+
if (!hasDoc)
|
|
111
|
+
return "undocumented";
|
|
112
|
+
if (ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node)) {
|
|
113
|
+
const members = node.members;
|
|
114
|
+
if (members.length > 0) {
|
|
115
|
+
const allMembersDocumented = members.every((member) => memberHasJSDoc(member, sourceFile));
|
|
116
|
+
if (!allMembersDocumented)
|
|
117
|
+
return "partial";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return "documented";
|
|
121
|
+
}
|
|
122
|
+
function analyzeSourceFile(sourceFile) {
|
|
123
|
+
const results = [];
|
|
124
|
+
for (const statement of sourceFile.statements) {
|
|
125
|
+
if (!isExportedDeclaration(statement))
|
|
126
|
+
continue;
|
|
127
|
+
const name = getDeclarationName(statement);
|
|
128
|
+
if (!name)
|
|
129
|
+
continue;
|
|
130
|
+
const kind = getDeclarationKind(statement);
|
|
131
|
+
const level = classifyDeclaration(statement, sourceFile);
|
|
132
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile));
|
|
133
|
+
results.push({
|
|
134
|
+
name,
|
|
135
|
+
kind,
|
|
136
|
+
level,
|
|
137
|
+
file: sourceFile.fileName,
|
|
138
|
+
line: line + 1
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
function calculateCoverage(declarations) {
|
|
144
|
+
const total = declarations.length;
|
|
145
|
+
if (total === 0) {
|
|
146
|
+
return {
|
|
147
|
+
documented: 0,
|
|
148
|
+
partial: 0,
|
|
149
|
+
undocumented: 0,
|
|
150
|
+
total: 0,
|
|
151
|
+
percentage: 100
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const documented = declarations.filter((d) => d.level === "documented").length;
|
|
155
|
+
const partial = declarations.filter((d) => d.level === "partial").length;
|
|
156
|
+
const undocumented = declarations.filter((d) => d.level === "undocumented").length;
|
|
157
|
+
const score = documented + partial * 0.5;
|
|
158
|
+
const percentage = Math.round(score / total * 100);
|
|
159
|
+
return { documented, partial, undocumented, total, percentage };
|
|
160
|
+
}
|
|
161
|
+
var COLORS = {
|
|
162
|
+
reset: "\x1B[0m",
|
|
163
|
+
red: "\x1B[31m",
|
|
164
|
+
green: "\x1B[32m",
|
|
165
|
+
yellow: "\x1B[33m",
|
|
166
|
+
blue: "\x1B[34m",
|
|
167
|
+
dim: "\x1B[2m",
|
|
168
|
+
bold: "\x1B[1m"
|
|
169
|
+
};
|
|
170
|
+
function resolveJsonMode(options = {}) {
|
|
171
|
+
return options.json ?? process.env["OUTFITTER_JSON"] === "1";
|
|
172
|
+
}
|
|
173
|
+
function bar(percentage, width = 20) {
|
|
174
|
+
const filled = Math.round(percentage / 100 * width);
|
|
175
|
+
const empty = width - filled;
|
|
176
|
+
const color = percentage >= 80 ? COLORS.green : percentage >= 50 ? COLORS.yellow : COLORS.red;
|
|
177
|
+
return `${color}${"█".repeat(filled)}${COLORS.dim}${"░".repeat(empty)}${COLORS.reset}`;
|
|
178
|
+
}
|
|
179
|
+
function discoverPackages(cwd) {
|
|
180
|
+
const packages = [];
|
|
181
|
+
const seenEntryPoints = new Set;
|
|
182
|
+
for (const pattern of ["packages/*/src/index.ts", "apps/*/src/index.ts"]) {
|
|
183
|
+
const glob = new Bun.Glob(pattern);
|
|
184
|
+
for (const match of glob.scanSync({ cwd, dot: false })) {
|
|
185
|
+
const parts = match.split("/");
|
|
186
|
+
const rootDir = parts[0];
|
|
187
|
+
const pkgDir = parts[1];
|
|
188
|
+
if (!rootDir || !pkgDir)
|
|
189
|
+
continue;
|
|
190
|
+
const entryPoint = resolve(cwd, match);
|
|
191
|
+
if (seenEntryPoints.has(entryPoint)) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
seenEntryPoints.add(entryPoint);
|
|
195
|
+
const pkgRoot = resolve(cwd, rootDir, pkgDir);
|
|
196
|
+
let pkgName = pkgDir;
|
|
197
|
+
try {
|
|
198
|
+
const pkgJson = JSON.parse(__require("node:fs").readFileSync(resolve(pkgRoot, "package.json"), "utf-8"));
|
|
199
|
+
if (pkgJson.name)
|
|
200
|
+
pkgName = pkgJson.name;
|
|
201
|
+
} catch {}
|
|
202
|
+
packages.push({
|
|
203
|
+
name: pkgName,
|
|
204
|
+
path: pkgRoot,
|
|
205
|
+
entryPoint
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (packages.length === 0) {
|
|
210
|
+
const entryPoint = resolve(cwd, "src/index.ts");
|
|
211
|
+
try {
|
|
212
|
+
__require("node:fs").accessSync(entryPoint);
|
|
213
|
+
let pkgName = "root";
|
|
214
|
+
try {
|
|
215
|
+
const pkgJson = JSON.parse(__require("node:fs").readFileSync(resolve(cwd, "package.json"), "utf-8"));
|
|
216
|
+
if (pkgJson.name)
|
|
217
|
+
pkgName = pkgJson.name;
|
|
218
|
+
} catch {}
|
|
219
|
+
packages.push({
|
|
220
|
+
name: pkgName,
|
|
221
|
+
path: cwd,
|
|
222
|
+
entryPoint
|
|
223
|
+
});
|
|
224
|
+
seenEntryPoints.add(entryPoint);
|
|
225
|
+
} catch {}
|
|
226
|
+
}
|
|
227
|
+
return packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
228
|
+
}
|
|
229
|
+
function collectReExportedSourceFiles(sourceFile, program, pkgPath) {
|
|
230
|
+
const result = [];
|
|
231
|
+
const seen = new Set;
|
|
232
|
+
for (const statement of sourceFile.statements) {
|
|
233
|
+
if (!ts.isExportDeclaration(statement))
|
|
234
|
+
continue;
|
|
235
|
+
if (!statement.moduleSpecifier)
|
|
236
|
+
continue;
|
|
237
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier))
|
|
238
|
+
continue;
|
|
239
|
+
const specifier = statement.moduleSpecifier.text;
|
|
240
|
+
if (!specifier.startsWith("."))
|
|
241
|
+
continue;
|
|
242
|
+
const resolvedModule = ts.resolveModuleName(specifier, sourceFile.fileName, program.getCompilerOptions(), ts.sys);
|
|
243
|
+
const resolvedFileName = resolvedModule.resolvedModule?.resolvedFileName;
|
|
244
|
+
if (!resolvedFileName)
|
|
245
|
+
continue;
|
|
246
|
+
if (!resolvedFileName.startsWith(pkgPath))
|
|
247
|
+
continue;
|
|
248
|
+
if (seen.has(resolvedFileName))
|
|
249
|
+
continue;
|
|
250
|
+
seen.add(resolvedFileName);
|
|
251
|
+
const sf = program.getSourceFile(resolvedFileName);
|
|
252
|
+
if (sf)
|
|
253
|
+
result.push(sf);
|
|
254
|
+
}
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
function analyzePackage(pkg, workspaceCwd) {
|
|
258
|
+
try {
|
|
259
|
+
__require("node:fs").accessSync(pkg.entryPoint);
|
|
260
|
+
} catch {
|
|
261
|
+
return {
|
|
262
|
+
name: pkg.name,
|
|
263
|
+
path: pkg.path,
|
|
264
|
+
declarations: [],
|
|
265
|
+
documented: 0,
|
|
266
|
+
partial: 0,
|
|
267
|
+
undocumented: 0,
|
|
268
|
+
total: 0,
|
|
269
|
+
percentage: 0
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
let tsconfigPath = resolve(pkg.path, "tsconfig.json");
|
|
273
|
+
try {
|
|
274
|
+
__require("node:fs").accessSync(tsconfigPath);
|
|
275
|
+
} catch {
|
|
276
|
+
tsconfigPath = resolve(workspaceCwd, "tsconfig.json");
|
|
277
|
+
}
|
|
278
|
+
const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
279
|
+
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, pkg.path);
|
|
280
|
+
const program = ts.createProgram({
|
|
281
|
+
rootNames: [pkg.entryPoint],
|
|
282
|
+
options: parsedConfig.options,
|
|
283
|
+
host: ts.createCompilerHost(parsedConfig.options)
|
|
284
|
+
});
|
|
285
|
+
const sourceFile = program.getSourceFile(pkg.entryPoint);
|
|
286
|
+
if (!sourceFile) {
|
|
287
|
+
return {
|
|
288
|
+
name: pkg.name,
|
|
289
|
+
path: pkg.path,
|
|
290
|
+
declarations: [],
|
|
291
|
+
documented: 0,
|
|
292
|
+
partial: 0,
|
|
293
|
+
undocumented: 0,
|
|
294
|
+
total: 0,
|
|
295
|
+
percentage: 0
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const declarations = analyzeSourceFile(sourceFile);
|
|
299
|
+
const reExportedFiles = collectReExportedSourceFiles(sourceFile, program, pkg.path);
|
|
300
|
+
for (const sf of reExportedFiles) {
|
|
301
|
+
declarations.push(...analyzeSourceFile(sf));
|
|
302
|
+
}
|
|
303
|
+
const stats = calculateCoverage(declarations);
|
|
304
|
+
return {
|
|
305
|
+
name: pkg.name,
|
|
306
|
+
path: pkg.path,
|
|
307
|
+
declarations,
|
|
308
|
+
...stats
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function analyzeCheckTsdoc(options = {}) {
|
|
312
|
+
const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
|
|
313
|
+
const minCoverage = options.minCoverage ?? 0;
|
|
314
|
+
let packages;
|
|
315
|
+
if (options.paths && options.paths.length > 0) {
|
|
316
|
+
packages = options.paths.map((p) => {
|
|
317
|
+
const absPath = resolve(cwd, p);
|
|
318
|
+
const entryPoint = resolve(absPath, "src/index.ts");
|
|
319
|
+
let name = p;
|
|
320
|
+
try {
|
|
321
|
+
const pkgJson = JSON.parse(__require("node:fs").readFileSync(resolve(absPath, "package.json"), "utf-8"));
|
|
322
|
+
if (pkgJson.name)
|
|
323
|
+
name = pkgJson.name;
|
|
324
|
+
} catch {}
|
|
325
|
+
return { name, path: absPath, entryPoint };
|
|
326
|
+
});
|
|
327
|
+
} else {
|
|
328
|
+
packages = discoverPackages(cwd);
|
|
329
|
+
}
|
|
330
|
+
if (packages.length === 0) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
const packageResults = [];
|
|
334
|
+
for (const pkg of packages) {
|
|
335
|
+
packageResults.push(analyzePackage(pkg, cwd));
|
|
336
|
+
}
|
|
337
|
+
const allDeclarations = packageResults.flatMap((p) => p.declarations);
|
|
338
|
+
const summary = calculateCoverage(allDeclarations);
|
|
339
|
+
const ok = !options.strict || summary.percentage >= minCoverage;
|
|
340
|
+
return {
|
|
341
|
+
ok,
|
|
342
|
+
packages: packageResults,
|
|
343
|
+
summary
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function printCheckTsdocHuman(result, options) {
|
|
347
|
+
process.stdout.write(`
|
|
348
|
+
${COLORS.bold}TSDoc Coverage Report${COLORS.reset}
|
|
349
|
+
|
|
350
|
+
`);
|
|
351
|
+
for (const pkg of result.packages) {
|
|
352
|
+
const color = pkg.percentage >= 80 ? COLORS.green : pkg.percentage >= 50 ? COLORS.yellow : COLORS.red;
|
|
353
|
+
process.stdout.write(` ${color}${pkg.percentage.toString().padStart(3)}%${COLORS.reset} ${bar(pkg.percentage)} ${pkg.name}
|
|
354
|
+
`);
|
|
355
|
+
if (pkg.total > 0) {
|
|
356
|
+
const parts = [];
|
|
357
|
+
if (pkg.documented > 0)
|
|
358
|
+
parts.push(`${COLORS.green}${pkg.documented} documented${COLORS.reset}`);
|
|
359
|
+
if (pkg.partial > 0)
|
|
360
|
+
parts.push(`${COLORS.yellow}${pkg.partial} partial${COLORS.reset}`);
|
|
361
|
+
if (pkg.undocumented > 0)
|
|
362
|
+
parts.push(`${COLORS.red}${pkg.undocumented} undocumented${COLORS.reset}`);
|
|
363
|
+
process.stdout.write(` ${COLORS.dim}${pkg.total} declarations:${COLORS.reset} ${parts.join(", ")}
|
|
364
|
+
`);
|
|
365
|
+
} else {
|
|
366
|
+
process.stdout.write(` ${COLORS.dim}no exported declarations${COLORS.reset}
|
|
367
|
+
`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const { summary } = result;
|
|
371
|
+
process.stdout.write(`
|
|
372
|
+
${COLORS.bold}Summary:${COLORS.reset} ${summary.percentage}% coverage (${summary.documented} documented, ${summary.partial} partial, ${summary.undocumented} undocumented of ${summary.total} total)
|
|
373
|
+
`);
|
|
374
|
+
const minCoverage = options?.minCoverage ?? 0;
|
|
375
|
+
if (options?.strict && summary.percentage < minCoverage) {
|
|
376
|
+
process.stderr.write(`
|
|
377
|
+
${COLORS.red}Coverage ${summary.percentage}% is below minimum threshold of ${minCoverage}%${COLORS.reset}
|
|
378
|
+
`);
|
|
379
|
+
}
|
|
380
|
+
process.stdout.write(`
|
|
381
|
+
`);
|
|
382
|
+
}
|
|
383
|
+
async function runCheckTsdoc(options = {}) {
|
|
384
|
+
const result = analyzeCheckTsdoc(options);
|
|
385
|
+
if (!result) {
|
|
386
|
+
process.stderr.write(`No packages found with src/index.ts entry points.
|
|
387
|
+
` + `Searched: packages/*/src/index.ts, apps/*/src/index.ts, src/index.ts
|
|
388
|
+
` + `Use --package <path> to specify a package path explicitly.
|
|
389
|
+
`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
if (resolveJsonMode(options)) {
|
|
393
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
394
|
+
`);
|
|
395
|
+
} else {
|
|
396
|
+
printCheckTsdocHuman(result, {
|
|
397
|
+
strict: options.strict,
|
|
398
|
+
minCoverage: options.minCoverage
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
process.exit(result.ok ? 0 : 1);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/version.ts
|
|
405
|
+
import { readFileSync } from "node:fs";
|
|
406
|
+
import { createRequire } from "node:module";
|
|
407
|
+
var DEFAULT_VERSION = "0.0.0";
|
|
408
|
+
function readPackageVersion() {
|
|
409
|
+
try {
|
|
410
|
+
const require2 = createRequire(import.meta.url);
|
|
411
|
+
const pkgPath = require2.resolve("@outfitter/tooling/package.json");
|
|
412
|
+
const packageJson = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
413
|
+
if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
|
|
414
|
+
return packageJson.version;
|
|
415
|
+
}
|
|
416
|
+
} catch {}
|
|
417
|
+
return DEFAULT_VERSION;
|
|
418
|
+
}
|
|
419
|
+
var VERSION = readPackageVersion();
|
|
420
|
+
|
|
421
|
+
export { coverageLevelSchema, declarationCoverageSchema, packageCoverageSchema, tsDocCheckResultSchema, analyzeSourceFile, calculateCoverage, analyzeCheckTsdoc, printCheckTsdocHuman, runCheckTsdoc, VERSION };
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { VERSION } from "./shared/@outfitter/tooling-9vs606gq";
|
|
1
|
+
import { VERSION } from "./shared/@outfitter/tooling-9vs606gq.js";
|
|
2
2
|
export { VERSION };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outfitter/tooling",
|
|
3
3
|
"description": "Dev tooling configuration presets for Outfitter projects (biome, typescript, lefthook, markdownlint)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -31,6 +31,12 @@
|
|
|
31
31
|
"default": "./dist/cli/check.js"
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
|
+
"./cli/check-tsdoc": {
|
|
35
|
+
"import": {
|
|
36
|
+
"types": "./dist/cli/check-tsdoc.d.ts",
|
|
37
|
+
"default": "./dist/cli/check-tsdoc.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
34
40
|
"./cli/fix": {
|
|
35
41
|
"import": {
|
|
36
42
|
"types": "./dist/cli/fix.d.ts",
|
|
@@ -76,13 +82,13 @@
|
|
|
76
82
|
"prepublishOnly": "bun ../../scripts/check-publish-manifest.ts"
|
|
77
83
|
},
|
|
78
84
|
"dependencies": {
|
|
79
|
-
"@outfitter/cli": "0.5.
|
|
85
|
+
"@outfitter/cli": "0.5.2",
|
|
80
86
|
"commander": "^14.0.2",
|
|
81
|
-
"
|
|
87
|
+
"typescript": "^5.9.3",
|
|
88
|
+
"zod": "^4.3.5"
|
|
82
89
|
},
|
|
83
90
|
"devDependencies": {
|
|
84
|
-
"@types/bun": "^1.3.
|
|
85
|
-
"typescript": "^5.9.3",
|
|
91
|
+
"@types/bun": "^1.3.9",
|
|
86
92
|
"yaml": "^2.8.2"
|
|
87
93
|
},
|
|
88
94
|
"peerDependencies": {
|
|
@@ -102,7 +108,7 @@
|
|
|
102
108
|
}
|
|
103
109
|
},
|
|
104
110
|
"engines": {
|
|
105
|
-
"bun": ">=1.3.
|
|
111
|
+
"bun": ">=1.3.9"
|
|
106
112
|
},
|
|
107
113
|
"keywords": [
|
|
108
114
|
"outfitter",
|
package/registry/registry.json
CHANGED
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
"files": [
|
|
23
23
|
{
|
|
24
24
|
"path": "biome.json",
|
|
25
|
-
"content": "{\n\t\"$schema\": \"https://biomejs.dev/schemas/2.
|
|
25
|
+
"content": "{\n\t\"$schema\": \"https://biomejs.dev/schemas/2.4.4/schema.json\",\n\t\"root\": false,\n\t\"javascript\": {\n\t\t\"globals\": [\"Bun\"]\n\t},\n\t\"linter\": {\n\t\t\"rules\": {\n\t\t\t\"complexity\": {\n\t\t\t\t\"useLiteralKeys\": \"off\",\n\t\t\t\t\"noVoid\": \"off\",\n\t\t\t\t\"noExcessiveCognitiveComplexity\": \"off\"\n\t\t\t},\n\t\t\t\"performance\": {\n\t\t\t\t\"useTopLevelRegex\": \"off\"\n\t\t\t},\n\t\t\t\"style\": {\n\t\t\t\t\"useBlockStatements\": \"off\"\n\t\t\t},\n\t\t\t\"suspicious\": {\n\t\t\t\t\"noConsole\": \"error\"\n\t\t\t}\n\t\t}\n\t},\n\t\"vcs\": {\n\t\t\"enabled\": true,\n\t\t\"clientKind\": \"git\",\n\t\t\"useIgnoreFile\": true\n\t},\n\t\"files\": {\n\t\t\"ignoreUnknown\": true,\n\t\t\"includes\": [\n\t\t\t\"**\",\n\t\t\t\"!**/node_modules\",\n\t\t\t\"!**/dist\",\n\t\t\t\"!**/.turbo\",\n\t\t\t\"!**/*.gen.ts\",\n\t\t\t\"!registry/registry.json\"\n\t\t]\n\t},\n\t\"overrides\": [\n\t\t{\n\t\t\t\"includes\": [\n\t\t\t\t\"packages/*/src/index.ts\",\n\t\t\t\t\"apps/*/src/index.ts\",\n\t\t\t\t\"**/index.ts\"\n\t\t\t],\n\t\t\t\"linter\": {\n\t\t\t\t\"rules\": {\n\t\t\t\t\t\"performance\": {\n\t\t\t\t\t\t\"noBarrelFile\": \"off\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"includes\": [\"**/*.test.ts\", \"**/__tests__/**/*\"],\n\t\t\t\"linter\": {\n\t\t\t\t\"rules\": {\n\t\t\t\t\t\"suspicious\": {\n\t\t\t\t\t\t\"useAwait\": \"off\",\n\t\t\t\t\t\t\"noConsole\": \"off\"\n\t\t\t\t\t},\n\t\t\t\t\t\"performance\": {\n\t\t\t\t\t\t\"noDelete\": \"off\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"includes\": [\"apps/**/*.ts\", \"scripts/**/*.ts\", \"**/scripts/**/*.ts\"],\n\t\t\t\"linter\": {\n\t\t\t\t\"rules\": {\n\t\t\t\t\t\"suspicious\": {\n\t\t\t\t\t\t\"noConsole\": \"off\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t]\n}\n"
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"ultracite": "^7.
|
|
29
|
+
"ultracite": "^7.2.3"
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"lefthook": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
}
|
|
40
40
|
],
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@outfitter/tooling": "^0.2.
|
|
43
|
-
"lefthook": "^2.
|
|
44
|
-
"ultracite": "^7.
|
|
42
|
+
"@outfitter/tooling": "^0.2.4",
|
|
43
|
+
"lefthook": "^2.1.1",
|
|
44
|
+
"ultracite": "^7.2.3"
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"markdownlint": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"files": [
|
|
61
61
|
{
|
|
62
62
|
"path": "scripts/bootstrap.sh",
|
|
63
|
-
"content": "#!/usr/bin/env bash\n#\n# bootstrap.sh — Get this repo from clone to runnable\n#\n# Usage: ./scripts/bootstrap.sh [--force]\n#\n# By default, exits immediately if all tools and deps are present.\n# Use --force to run full bootstrap regardless.\n#\n\nset -euo pipefail\n\n# -----------------------------------------------------------------------------\n# Fast path — exit immediately if all tools and deps are present\n# -----------------------------------------------------------------------------\nif [[ \"${1:-}\" != \"--force\" ]]; then\n all_present=true\n command -v bun &>/dev/null
|
|
63
|
+
"content": "#!/usr/bin/env bash\n#\n# bootstrap.sh — Get this repo from clone to runnable\n#\n# Usage: ./scripts/bootstrap.sh [--force]\n#\n# By default, exits immediately if all tools and deps are present.\n# Use --force to run full bootstrap regardless.\n#\n# This is the generic template distributed via `outfitter add bootstrap`.\n# It does NOT include project-specific tools like Graphite.\n# Add project-specific tools by extending this script or using the\n# TS bootstrap API's `extend` callback.\n\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd -- \"$(dirname -- \"${BASH_SOURCE[0]}\")\" && pwd)\"\nREPO_ROOT=\"$(cd -- \"$SCRIPT_DIR/..\" && pwd)\"\nBUN_VERSION_FILE=\"$REPO_ROOT/.bun-version\"\nPINNED_BUN_VERSION=\"\"\n\nif [[ -f \"$BUN_VERSION_FILE\" ]]; then\n PINNED_BUN_VERSION=\"$(tr -d '[:space:]' < \"$BUN_VERSION_FILE\")\"\n if [[ -z \"$PINNED_BUN_VERSION\" ]]; then\n echo \"Warning: .bun-version is empty; falling back to latest Bun install\" >&2\n fi\nfi\n\n# -----------------------------------------------------------------------------\n# Fast path — exit immediately if all tools and deps are present\n# -----------------------------------------------------------------------------\nif [[ \"${1:-}\" != \"--force\" ]]; then\n all_present=true\n\n if command -v bun &>/dev/null; then\n installed_bun_version=\"$(bun --version)\"\n if [[ -n \"$PINNED_BUN_VERSION\" ]]; then\n [[ \"$installed_bun_version\" == \"$PINNED_BUN_VERSION\" ]] || all_present=false\n fi\n else\n all_present=false\n fi\n\n command -v gh &>/dev/null || all_present=false\n command -v markdownlint-cli2 &>/dev/null || all_present=false\n [[ -d \"$REPO_ROOT/node_modules\" ]] || all_present=false\n\n if $all_present; then\n exit 0 # All good, nothing to do\n fi\nfi\n\n# Strip --force if present\n[[ \"${1:-}\" == \"--force\" ]] && shift\n\n# Colors\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[0;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m' # No Color\n\ninfo() { echo -e \"${BLUE}▸${NC} $1\"; }\nsuccess() { echo -e \"${GREEN}✓${NC} $1\"; }\nwarn() { echo -e \"${YELLOW}!${NC} $1\"; }\nerror() { echo -e \"${RED}✗${NC} $1\" >&2; }\n\n# Check if command exists\nhas() { command -v \"$1\" &>/dev/null; }\n\n# Detect OS\nOS=\"$(uname -s)\"\ncase \"$OS\" in\n Darwin) IS_MACOS=true ;;\n Linux) IS_MACOS=false ;;\n *) error \"Unsupported OS: $OS\"; exit 1 ;;\nesac\n\n# -----------------------------------------------------------------------------\n# Homebrew (macOS only)\n# -----------------------------------------------------------------------------\ninstall_homebrew() {\n if $IS_MACOS && ! has brew; then\n info \"Installing Homebrew...\"\n /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\n success \"Homebrew installed\"\n fi\n}\n\n# -----------------------------------------------------------------------------\n# Bun\n# -----------------------------------------------------------------------------\ninstall_bun() {\n local installed_bun_version=\"\"\n\n if has bun; then\n installed_bun_version=\"$(bun --version)\"\n fi\n\n if [[ -n \"$installed_bun_version\" ]]; then\n if [[ -n \"$PINNED_BUN_VERSION\" && \"$installed_bun_version\" == \"$PINNED_BUN_VERSION\" ]]; then\n success \"Bun already installed ($installed_bun_version)\"\n return\n fi\n if [[ -z \"$PINNED_BUN_VERSION\" ]]; then\n success \"Bun already installed ($installed_bun_version)\"\n return\n fi\n fi\n\n if [[ -n \"$PINNED_BUN_VERSION\" && -n \"$installed_bun_version\" ]]; then\n info \"Updating Bun from $installed_bun_version to $PINNED_BUN_VERSION...\"\n elif [[ -n \"$PINNED_BUN_VERSION\" ]]; then\n info \"Installing Bun $PINNED_BUN_VERSION...\"\n else\n info \"Installing Bun (latest stable)...\"\n fi\n\n if [[ -n \"$PINNED_BUN_VERSION\" ]]; then\n curl -fsSL https://bun.sh/install | bash -s -- \"bun-v$PINNED_BUN_VERSION\"\n else\n curl -fsSL https://bun.sh/install | bash\n fi\n\n # Source the updated profile\n export BUN_INSTALL=\"$HOME/.bun\"\n export PATH=\"$BUN_INSTALL/bin:$PATH\"\n hash -r\n\n local resolved_bun_version\n resolved_bun_version=\"$(bun --version)\"\n\n if [[ -n \"$PINNED_BUN_VERSION\" && \"$resolved_bun_version\" != \"$PINNED_BUN_VERSION\" ]]; then\n error \"Expected Bun $PINNED_BUN_VERSION but found $resolved_bun_version after install\"\n exit 1\n fi\n\n success \"Bun ready ($resolved_bun_version)\"\n}\n\n# -----------------------------------------------------------------------------\n# GitHub CLI (gh)\n# -----------------------------------------------------------------------------\ninstall_gh() {\n if has gh; then\n success \"GitHub CLI already installed ($(gh --version | head -1))\"\n else\n info \"Installing GitHub CLI...\"\n if $IS_MACOS; then\n brew install gh\n else\n # Linux: use official apt repo\n curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg\n echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main\" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null\n sudo apt update && sudo apt install gh -y\n fi\n success \"GitHub CLI installed\"\n fi\n}\n\n# -----------------------------------------------------------------------------\n# markdownlint-cli2\n# -----------------------------------------------------------------------------\ninstall_markdownlint() {\n if has markdownlint-cli2; then\n success \"markdownlint-cli2 already installed\"\n else\n info \"Installing markdownlint-cli2...\"\n bun install -g markdownlint-cli2\n success \"markdownlint-cli2 installed\"\n fi\n}\n\n# -----------------------------------------------------------------------------\n# Auth checks\n# -----------------------------------------------------------------------------\ncheck_auth() {\n echo \"\"\n info \"Checking authentication...\"\n\n # GitHub CLI\n if [[ -n \"${GH_TOKEN:-}\" ]] || [[ -n \"${GITHUB_TOKEN:-}\" ]]; then\n success \"GitHub CLI token found in environment\"\n elif gh auth status &>/dev/null; then\n success \"GitHub CLI already authenticated\"\n else\n echo \" GitHub CLI not authenticated. Run 'gh auth login' or set GH_TOKEN\"\n fi\n\n # Add project-specific auth checks (e.g., Graphite) below\n}\n\n# -----------------------------------------------------------------------------\n# Project dependencies\n# -----------------------------------------------------------------------------\ninstall_deps() {\n info \"Installing project dependencies...\"\n (\n cd \"$REPO_ROOT\"\n bun install\n )\n success \"Dependencies installed\"\n}\n\n# -----------------------------------------------------------------------------\n# Main\n# -----------------------------------------------------------------------------\nmain() {\n echo \"\"\n echo -e \"${BLUE}Outfitter Bootstrap${NC}\"\n echo \"────────────────────────\"\n echo \"\"\n\n # Prerequisites\n if $IS_MACOS; then\n install_homebrew\n fi\n\n # Core tools\n install_bun\n install_gh\n install_markdownlint\n # Add project-specific tools (e.g., Graphite) below\n\n # Auth status\n check_auth\n\n echo \"\"\n\n # Project setup\n install_deps\n\n echo \"\"\n echo -e \"${GREEN}Bootstrap complete!${NC}\"\n echo \"\"\n echo \"Next steps:\"\n echo \" bun run build # Build all packages\"\n echo \" bun run test # Run tests\"\n echo \"\"\n}\n\nmain \"$@\"\n",
|
|
64
64
|
"executable": true
|
|
65
65
|
}
|
|
66
66
|
]
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/tooling/src/cli/check-changeset.ts
|
|
3
|
-
function getChangedPackagePaths(files) {
|
|
4
|
-
const packageNames = new Set;
|
|
5
|
-
const pattern = /^packages\/([^/]+)\/src\//;
|
|
6
|
-
for (const file of files) {
|
|
7
|
-
const match = pattern.exec(file);
|
|
8
|
-
if (match?.[1]) {
|
|
9
|
-
packageNames.add(match[1]);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
return [...packageNames].sort();
|
|
13
|
-
}
|
|
14
|
-
function getChangedChangesetFiles(files) {
|
|
15
|
-
const pattern = /^\.changeset\/([^/]+\.md)$/;
|
|
16
|
-
const results = [];
|
|
17
|
-
for (const file of files) {
|
|
18
|
-
const match = pattern.exec(file);
|
|
19
|
-
if (match?.[1] && match[1] !== "README.md") {
|
|
20
|
-
results.push(match[1]);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return results.sort();
|
|
24
|
-
}
|
|
25
|
-
function checkChangesetRequired(changedPackages, changesetFiles) {
|
|
26
|
-
if (changedPackages.length === 0) {
|
|
27
|
-
return { ok: true, missingFor: [] };
|
|
28
|
-
}
|
|
29
|
-
if (changesetFiles.length > 0) {
|
|
30
|
-
return { ok: true, missingFor: [] };
|
|
31
|
-
}
|
|
32
|
-
return { ok: false, missingFor: changedPackages };
|
|
33
|
-
}
|
|
34
|
-
var COLORS = {
|
|
35
|
-
reset: "\x1B[0m",
|
|
36
|
-
red: "\x1B[31m",
|
|
37
|
-
green: "\x1B[32m",
|
|
38
|
-
yellow: "\x1B[33m",
|
|
39
|
-
blue: "\x1B[34m",
|
|
40
|
-
dim: "\x1B[2m"
|
|
41
|
-
};
|
|
42
|
-
async function runCheckChangeset(options = {}) {
|
|
43
|
-
if (options.skip || process.env["NO_CHANGESET"] === "1") {
|
|
44
|
-
process.stdout.write(`${COLORS.dim}check-changeset skipped (NO_CHANGESET=1)${COLORS.reset}
|
|
45
|
-
`);
|
|
46
|
-
process.exit(0);
|
|
47
|
-
}
|
|
48
|
-
if (process.env["GITHUB_EVENT_NAME"] === "push") {
|
|
49
|
-
process.stdout.write(`${COLORS.dim}check-changeset skipped (push event)${COLORS.reset}
|
|
50
|
-
`);
|
|
51
|
-
process.exit(0);
|
|
52
|
-
}
|
|
53
|
-
const cwd = process.cwd();
|
|
54
|
-
let changedFiles;
|
|
55
|
-
try {
|
|
56
|
-
const proc = Bun.spawnSync(["git", "diff", "--name-only", "origin/main...HEAD"], { cwd });
|
|
57
|
-
if (proc.exitCode !== 0) {
|
|
58
|
-
process.exit(0);
|
|
59
|
-
}
|
|
60
|
-
changedFiles = proc.stdout.toString().trim().split(`
|
|
61
|
-
`).filter((line) => line.length > 0);
|
|
62
|
-
} catch {
|
|
63
|
-
process.exit(0);
|
|
64
|
-
}
|
|
65
|
-
const changedPackages = getChangedPackagePaths(changedFiles);
|
|
66
|
-
if (changedPackages.length === 0) {
|
|
67
|
-
process.stdout.write(`${COLORS.green}No package source changes detected.${COLORS.reset}
|
|
68
|
-
`);
|
|
69
|
-
process.exit(0);
|
|
70
|
-
}
|
|
71
|
-
const changesetFiles = getChangedChangesetFiles(changedFiles);
|
|
72
|
-
const check = checkChangesetRequired(changedPackages, changesetFiles);
|
|
73
|
-
if (check.ok) {
|
|
74
|
-
process.stdout.write(`${COLORS.green}Changeset found for ${changedPackages.length} changed package(s).${COLORS.reset}
|
|
75
|
-
`);
|
|
76
|
-
process.exit(0);
|
|
77
|
-
}
|
|
78
|
-
process.stderr.write(`${COLORS.red}Missing changeset!${COLORS.reset}
|
|
79
|
-
|
|
80
|
-
`);
|
|
81
|
-
process.stderr.write(`The following packages have source changes but no changeset:
|
|
82
|
-
|
|
83
|
-
`);
|
|
84
|
-
for (const pkg of check.missingFor) {
|
|
85
|
-
process.stderr.write(` ${COLORS.yellow}@outfitter/${pkg}${COLORS.reset}
|
|
86
|
-
`);
|
|
87
|
-
}
|
|
88
|
-
process.stderr.write(`
|
|
89
|
-
Run ${COLORS.blue}bun run changeset${COLORS.reset} to add a changeset, ` + `or add the ${COLORS.blue}no-changeset${COLORS.reset} label to skip.
|
|
90
|
-
`);
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export { getChangedPackagePaths, getChangedChangesetFiles, checkChangesetRequired, runCheckChangeset };
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// src/version.ts
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
var DEFAULT_VERSION = "0.0.0";
|
|
5
|
-
function readPackageVersion() {
|
|
6
|
-
try {
|
|
7
|
-
const require2 = createRequire(import.meta.url);
|
|
8
|
-
const pkgPath = require2.resolve("@outfitter/tooling/package.json");
|
|
9
|
-
const packageJson = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
10
|
-
if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
|
|
11
|
-
return packageJson.version;
|
|
12
|
-
}
|
|
13
|
-
} catch {}
|
|
14
|
-
return DEFAULT_VERSION;
|
|
15
|
-
}
|
|
16
|
-
var VERSION = readPackageVersion();
|
|
17
|
-
|
|
18
|
-
export { VERSION };
|