@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,58 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tooling/src/cli/internal/exports-analysis.ts
|
|
3
|
+
function entryToSubpath(entry) {
|
|
4
|
+
const stripped = entry.replace(/^src\//, "").replace(/\.[cm]?[jt]sx?$/, "");
|
|
5
|
+
if (stripped === "index") {
|
|
6
|
+
return ".";
|
|
7
|
+
}
|
|
8
|
+
if (stripped.endsWith("/index")) {
|
|
9
|
+
return `./${stripped.slice(0, -"/index".length)}`;
|
|
10
|
+
}
|
|
11
|
+
return `./${stripped}`;
|
|
12
|
+
}
|
|
13
|
+
function compareExports(input) {
|
|
14
|
+
const { name, actual, expected, path } = input;
|
|
15
|
+
const actualKeys = new Set(Object.keys(actual));
|
|
16
|
+
const expectedKeys = new Set(Object.keys(expected));
|
|
17
|
+
const added = [];
|
|
18
|
+
const removed = [];
|
|
19
|
+
const changed = [];
|
|
20
|
+
for (const key of expectedKeys) {
|
|
21
|
+
if (!actualKeys.has(key)) {
|
|
22
|
+
added.push(key);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const key of actualKeys) {
|
|
26
|
+
if (!expectedKeys.has(key)) {
|
|
27
|
+
removed.push(key);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const key of actualKeys) {
|
|
31
|
+
if (expectedKeys.has(key)) {
|
|
32
|
+
const actualValue = actual[key];
|
|
33
|
+
const expectedValue = expected[key];
|
|
34
|
+
if (JSON.stringify(actualValue) !== JSON.stringify(expectedValue)) {
|
|
35
|
+
changed.push({ key, expected: expectedValue, actual: actualValue });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
added.sort();
|
|
40
|
+
removed.sort();
|
|
41
|
+
changed.sort((a, b) => a.key.localeCompare(b.key));
|
|
42
|
+
if (added.length === 0 && removed.length === 0 && changed.length === 0) {
|
|
43
|
+
return { name, status: "ok" };
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
name,
|
|
47
|
+
status: "drift",
|
|
48
|
+
drift: {
|
|
49
|
+
package: name,
|
|
50
|
+
path: path ?? "",
|
|
51
|
+
added,
|
|
52
|
+
removed,
|
|
53
|
+
changed
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { entryToSubpath, compareExports };
|
|
@@ -2,23 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
analyzeSourceFile,
|
|
4
4
|
calculateCoverage
|
|
5
|
-
} from "./tooling-
|
|
5
|
+
} from "./tooling-5xxctk9b.js";
|
|
6
6
|
|
|
7
|
-
// packages/tooling/src/cli/pre-push.ts
|
|
7
|
+
// packages/tooling/src/cli/internal/pre-push-checks.ts
|
|
8
8
|
import { existsSync, readFileSync } from "fs";
|
|
9
9
|
import { join, resolve } from "path";
|
|
10
10
|
import ts from "typescript";
|
|
11
|
-
var COLORS = {
|
|
12
|
-
reset: "\x1B[0m",
|
|
13
|
-
red: "\x1B[31m",
|
|
14
|
-
green: "\x1B[32m",
|
|
15
|
-
yellow: "\x1B[33m",
|
|
16
|
-
blue: "\x1B[34m"
|
|
17
|
-
};
|
|
18
|
-
function log(msg) {
|
|
19
|
-
process.stdout.write(`${msg}
|
|
20
|
-
`);
|
|
21
|
-
}
|
|
22
11
|
function getCurrentBranch() {
|
|
23
12
|
const result = Bun.spawnSync(["git", "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
24
13
|
return result.stdout.toString().trim();
|
|
@@ -70,28 +59,6 @@ function hasPackageSourceChanges(changedFiles) {
|
|
|
70
59
|
const packageSrcPattern = /^packages\/[^/]+\/src\//;
|
|
71
60
|
return changedFiles.files.some((f) => packageSrcPattern.test(f));
|
|
72
61
|
}
|
|
73
|
-
async function printTsdocSummary() {
|
|
74
|
-
const glob = new Bun.Glob("packages/*/src/index.ts");
|
|
75
|
-
const cwd = process.cwd();
|
|
76
|
-
const allDeclarations = [];
|
|
77
|
-
for (const entry of glob.scanSync({ cwd })) {
|
|
78
|
-
const filePath = resolve(cwd, entry);
|
|
79
|
-
const content = await Bun.file(filePath).text();
|
|
80
|
-
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
81
|
-
allDeclarations.push(...analyzeSourceFile(sourceFile));
|
|
82
|
-
}
|
|
83
|
-
if (allDeclarations.length === 0)
|
|
84
|
-
return;
|
|
85
|
-
const coverage = calculateCoverage(allDeclarations);
|
|
86
|
-
const parts = [];
|
|
87
|
-
if (coverage.documented > 0)
|
|
88
|
-
parts.push(`${coverage.documented} documented`);
|
|
89
|
-
if (coverage.partial > 0)
|
|
90
|
-
parts.push(`${coverage.partial} partial`);
|
|
91
|
-
if (coverage.undocumented > 0)
|
|
92
|
-
parts.push(`${coverage.undocumented} undocumented`);
|
|
93
|
-
log(`${COLORS.blue}TSDoc${COLORS.reset}: ${coverage.percentage}% coverage (${parts.join(", ")} of ${coverage.total} total)`);
|
|
94
|
-
}
|
|
95
62
|
function resolveBaseRef() {
|
|
96
63
|
const candidates = [
|
|
97
64
|
"origin/main",
|
|
@@ -150,35 +117,6 @@ function getChangedFilesForPush() {
|
|
|
150
117
|
source: "undetermined"
|
|
151
118
|
};
|
|
152
119
|
}
|
|
153
|
-
function maybeSkipForRedPhase(reason, branch) {
|
|
154
|
-
const changedFiles = getChangedFilesForPush();
|
|
155
|
-
if (!changedFiles.deterministic) {
|
|
156
|
-
log(`${COLORS.yellow}RED-phase bypass denied${COLORS.reset}: could not determine full push diff range`);
|
|
157
|
-
log("Running strict verification.");
|
|
158
|
-
log("");
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
if (!canBypassRedPhaseByChangedFiles(changedFiles)) {
|
|
162
|
-
log(`${COLORS.yellow}RED-phase bypass denied${COLORS.reset}: changed files are not test-only`);
|
|
163
|
-
if (changedFiles.files.length > 0) {
|
|
164
|
-
log(`Changed files (${changedFiles.source}): ${changedFiles.files.join(", ")}`);
|
|
165
|
-
} else {
|
|
166
|
-
log(`No changed files detected in ${changedFiles.source} range. Running strict verification.`);
|
|
167
|
-
}
|
|
168
|
-
log("");
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
if (reason === "branch") {
|
|
172
|
-
log(`${COLORS.yellow}TDD RED phase${COLORS.reset} detected: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
173
|
-
} else {
|
|
174
|
-
log(`${COLORS.yellow}Scaffold branch${COLORS.reset} with RED phase branch in context: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
175
|
-
}
|
|
176
|
-
log(`${COLORS.yellow}Skipping strict verification${COLORS.reset} - changed files are test-only`);
|
|
177
|
-
log(`Diff source: ${changedFiles.source}`);
|
|
178
|
-
log("");
|
|
179
|
-
log("Remember: GREEN phase (implementation) must make these tests pass!");
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
120
|
function hasRedPhaseBranchInContext(currentBranch) {
|
|
183
121
|
let branches = [];
|
|
184
122
|
try {
|
|
@@ -209,6 +147,9 @@ function hasRedPhaseBranchInContext(currentBranch) {
|
|
|
209
147
|
return false;
|
|
210
148
|
}
|
|
211
149
|
function createVerificationPlan(scripts) {
|
|
150
|
+
if (scripts["verify:push"]) {
|
|
151
|
+
return { ok: true, scripts: ["verify:push"], source: "verify:push" };
|
|
152
|
+
}
|
|
212
153
|
if (scripts["verify:ci"]) {
|
|
213
154
|
return { ok: true, scripts: ["verify:ci"], source: "verify:ci" };
|
|
214
155
|
}
|
|
@@ -247,14 +188,6 @@ function readPackageScripts(cwd = process.cwd()) {
|
|
|
247
188
|
return {};
|
|
248
189
|
}
|
|
249
190
|
}
|
|
250
|
-
function runScript(scriptName) {
|
|
251
|
-
log("");
|
|
252
|
-
log(`Running: ${COLORS.blue}bun run ${scriptName}${COLORS.reset}`);
|
|
253
|
-
const result = Bun.spawnSync(["bun", "run", scriptName], {
|
|
254
|
-
stdio: ["inherit", "inherit", "inherit"]
|
|
255
|
-
});
|
|
256
|
-
return result.exitCode === 0;
|
|
257
|
-
}
|
|
258
191
|
function checkBunVersion(projectRoot = process.cwd()) {
|
|
259
192
|
const versionFile = join(projectRoot, ".bun-version");
|
|
260
193
|
if (!existsSync(versionFile)) {
|
|
@@ -267,83 +200,29 @@ function checkBunVersion(projectRoot = process.cwd()) {
|
|
|
267
200
|
}
|
|
268
201
|
return { matches: false, expected, actual };
|
|
269
202
|
}
|
|
270
|
-
async function
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (!versionCheck.matches) {
|
|
280
|
-
log(`${COLORS.red}Bun version mismatch${COLORS.reset}: running ${versionCheck.actual}, pinned ${versionCheck.expected}`);
|
|
281
|
-
log("Fix: bunx @outfitter/tooling upgrade-bun");
|
|
282
|
-
log("");
|
|
283
|
-
process.exitCode = 1;
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
const branch = getCurrentBranch();
|
|
287
|
-
if (isReleaseBranch(branch)) {
|
|
288
|
-
log(`${COLORS.yellow}Release branch detected${COLORS.reset}: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
289
|
-
log(`${COLORS.yellow}Skipping strict verification${COLORS.reset} for automated changeset release push`);
|
|
290
|
-
process.exitCode = 0;
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
if (isRedPhaseBranch(branch)) {
|
|
294
|
-
if (maybeSkipForRedPhase("branch", branch)) {
|
|
295
|
-
process.exitCode = 0;
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
if (isScaffoldBranch(branch)) {
|
|
300
|
-
if (hasRedPhaseBranchInContext(branch)) {
|
|
301
|
-
if (maybeSkipForRedPhase("context", branch)) {
|
|
302
|
-
process.exitCode = 0;
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
const plan = createVerificationPlan(readPackageScripts());
|
|
308
|
-
if (!plan.ok) {
|
|
309
|
-
log(`${COLORS.red}Strict pre-push verification is not configured${COLORS.reset}`);
|
|
310
|
-
log(plan.error);
|
|
311
|
-
log("");
|
|
312
|
-
log("Add one of:");
|
|
313
|
-
log(" - verify:ci");
|
|
314
|
-
log(" - typecheck + (check or lint) + build + test");
|
|
315
|
-
process.exitCode = 1;
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
log(`Running strict verification for branch: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
319
|
-
if (plan.source === "verify:ci") {
|
|
320
|
-
log("Using `verify:ci` script.");
|
|
321
|
-
} else {
|
|
322
|
-
log(`Using fallback scripts: ${plan.scripts.join(" -> ")}`);
|
|
203
|
+
async function printTsdocSummary(log) {
|
|
204
|
+
const glob = new Bun.Glob("packages/*/src/index.ts");
|
|
205
|
+
const cwd = process.cwd();
|
|
206
|
+
const allDeclarations = [];
|
|
207
|
+
for (const entry of glob.scanSync({ cwd })) {
|
|
208
|
+
const filePath = resolve(cwd, entry);
|
|
209
|
+
const content = await Bun.file(filePath).text();
|
|
210
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
211
|
+
allDeclarations.push(...analyzeSourceFile(sourceFile));
|
|
323
212
|
}
|
|
324
|
-
|
|
325
|
-
if (runScript(scriptName)) {
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
log("");
|
|
329
|
-
log(`${COLORS.red}Verification failed${COLORS.reset} on script: ${scriptName}`);
|
|
330
|
-
log("");
|
|
331
|
-
log("If this is intentional TDD RED phase work, name your branch:");
|
|
332
|
-
log(" - feature-tests");
|
|
333
|
-
log(" - feature/tests");
|
|
334
|
-
log(" - feature_tests");
|
|
335
|
-
process.exitCode = 1;
|
|
213
|
+
if (allDeclarations.length === 0)
|
|
336
214
|
return;
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
if (
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
215
|
+
const coverage = calculateCoverage(allDeclarations);
|
|
216
|
+
const parts = [];
|
|
217
|
+
if (coverage.documented > 0)
|
|
218
|
+
parts.push(`${coverage.documented} documented`);
|
|
219
|
+
if (coverage.partial > 0)
|
|
220
|
+
parts.push(`${coverage.partial} partial`);
|
|
221
|
+
if (coverage.undocumented > 0)
|
|
222
|
+
parts.push(`${coverage.undocumented} undocumented`);
|
|
223
|
+
const BLUE = "\x1B[34m";
|
|
224
|
+
const RESET = "\x1B[0m";
|
|
225
|
+
log(`${BLUE}TSDoc${RESET}: ${coverage.percentage}% coverage (${parts.join(", ")} of ${coverage.total} total)`);
|
|
347
226
|
}
|
|
348
227
|
|
|
349
|
-
export { isRedPhaseBranch, isScaffoldBranch, isReleaseBranch, isTestOnlyPath, areFilesTestOnly, canBypassRedPhaseByChangedFiles, hasPackageSourceChanges, createVerificationPlan, checkBunVersion,
|
|
228
|
+
export { getCurrentBranch, runGit, isRedPhaseBranch, isScaffoldBranch, isReleaseBranch, isTestOnlyPath, areFilesTestOnly, canBypassRedPhaseByChangedFiles, hasPackageSourceChanges, getChangedFilesForPush, hasRedPhaseBranchInContext, createVerificationPlan, readPackageScripts, checkBunVersion, printTsdocSummary };
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure analysis functions for comparing maps.
|
|
3
|
+
*
|
|
4
|
+
* Types and stateless comparison logic used by the check-exports command.
|
|
5
|
+
* No filesystem access — all inputs are passed as arguments.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
1
9
|
/** A package.json map: keys are subpaths, values are conditions or strings */
|
|
2
10
|
type ExportMap = Record<string, unknown>;
|
|
3
11
|
/** Describes drift between expected and actual exports for a single package */
|
|
@@ -46,14 +54,4 @@ declare function entryToSubpath(entry: string): string;
|
|
|
46
54
|
* Returns a PackageResult with status "ok" or "drift" and detailed diff.
|
|
47
55
|
*/
|
|
48
56
|
declare function compareExports(input: CompareInput): PackageResult;
|
|
49
|
-
|
|
50
|
-
readonly json?: boolean;
|
|
51
|
-
}
|
|
52
|
-
declare function resolveJsonMode(options?: CheckExportsOptions): boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Run check-exports across all workspace packages.
|
|
55
|
-
*
|
|
56
|
-
* Reads the bunup workspace config to discover packages and their * settings, then compares expected vs actual exports in each package.json.
|
|
57
|
-
*/
|
|
58
|
-
declare function runCheckExports(options?: CheckExportsOptions): Promise<void>;
|
|
59
|
-
export { ExportMap, ExportDrift, PackageResult, CheckResult, CompareInput, entryToSubpath, compareExports, CheckExportsOptions, resolveJsonMode, runCheckExports };
|
|
57
|
+
export { ExportMap, ExportDrift, PackageResult, CheckResult, CompareInput, entryToSubpath, compareExports };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CheckTsDocOptions, TsDocCheckResult } from "./tooling-tj9p41vj.js";
|
|
2
|
+
/** Resolve whether JSON output mode is active. */
|
|
3
|
+
declare function resolveJsonMode(options?: CheckTsDocOptions): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Print a TSDoc coverage result in human-readable format.
|
|
6
|
+
*
|
|
7
|
+
* Renders a bar chart per package with summary statistics. Writes to stdout/stderr.
|
|
8
|
+
*
|
|
9
|
+
* @param result - The coverage result to print
|
|
10
|
+
* @param options - Display options (strict mode, coverage threshold for warning)
|
|
11
|
+
*/
|
|
12
|
+
declare function printCheckTsdocHuman(result: TsDocCheckResult, options?: {
|
|
13
|
+
strict?: boolean | undefined;
|
|
14
|
+
minCoverage?: number | undefined;
|
|
15
|
+
}): void;
|
|
16
|
+
export { resolveJsonMode, printCheckTsdocHuman };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tooling/src/cli/check-home-paths.ts
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
function formatLeakLineText(lineText) {
|
|
7
|
+
return lineText.trimEnd();
|
|
8
|
+
}
|
|
9
|
+
function writeLeakSummary(stderr, leaks) {
|
|
10
|
+
stderr.write(`Hardcoded home directory paths detected:
|
|
11
|
+
`);
|
|
12
|
+
for (const leak of leaks) {
|
|
13
|
+
stderr.write(` ${leak.filePath}:${leak.line}:${leak.column} ${formatLeakLineText(leak.lineText)}
|
|
14
|
+
`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function writeReplacementHint(stderr, leaks) {
|
|
18
|
+
const matchedText = leaks[0]?.matchedText;
|
|
19
|
+
if (!matchedText) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
stderr.write(`
|
|
23
|
+
Replace ${JSON.stringify(matchedText)} with a repo-relative or home-agnostic path before committing.
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
function escapeRegExp(value) {
|
|
27
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28
|
+
}
|
|
29
|
+
function buildHomePathPattern(homePath) {
|
|
30
|
+
if (homePath.trim().length === 0) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const patternSource = homePath.split(/(\\+)/).map((segment) => segment.includes("\\") ? "\\\\+" : escapeRegExp(segment)).join("");
|
|
34
|
+
return new RegExp(`${patternSource}(?![\\w.-])`, "g");
|
|
35
|
+
}
|
|
36
|
+
function findHomePathLeaks(content, homePathOrPattern) {
|
|
37
|
+
let pattern;
|
|
38
|
+
if (homePathOrPattern instanceof RegExp) {
|
|
39
|
+
if (!homePathOrPattern.flags.includes("g")) {
|
|
40
|
+
throw new TypeError("findHomePathLeaks: RegExp must have the global (g) flag set");
|
|
41
|
+
}
|
|
42
|
+
pattern = homePathOrPattern;
|
|
43
|
+
} else {
|
|
44
|
+
pattern = buildHomePathPattern(homePathOrPattern);
|
|
45
|
+
}
|
|
46
|
+
if (!pattern) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const leaks = [];
|
|
50
|
+
const lines = content.split(/\r?\n/);
|
|
51
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
52
|
+
const line = lines[index] ?? "";
|
|
53
|
+
for (const match of line.matchAll(pattern)) {
|
|
54
|
+
leaks.push({
|
|
55
|
+
line: index + 1,
|
|
56
|
+
column: (match.index ?? 0) + 1,
|
|
57
|
+
matchedText: match[0],
|
|
58
|
+
lineText: line
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return leaks;
|
|
63
|
+
}
|
|
64
|
+
function scanFilesForHardcodedHomePaths(filePaths, options = {}) {
|
|
65
|
+
const cwd = options.cwd ?? process.cwd();
|
|
66
|
+
const homePath = options.homeDir ?? homedir();
|
|
67
|
+
const readFile = options.readFile ?? readFileSync;
|
|
68
|
+
const leaks = [];
|
|
69
|
+
const failures = [];
|
|
70
|
+
const pattern = buildHomePathPattern(homePath);
|
|
71
|
+
if (!pattern) {
|
|
72
|
+
return { leaks, failures };
|
|
73
|
+
}
|
|
74
|
+
for (const filePath of filePaths) {
|
|
75
|
+
const absolutePath = resolve(cwd, filePath);
|
|
76
|
+
let fileContent;
|
|
77
|
+
try {
|
|
78
|
+
fileContent = readFile(absolutePath, "utf-8");
|
|
79
|
+
} catch (error) {
|
|
80
|
+
const errno = error;
|
|
81
|
+
if (errno.code === "ENOENT") {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
failures.push({
|
|
85
|
+
filePath,
|
|
86
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
87
|
+
});
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const fileLeaks = findHomePathLeaks(fileContent, pattern);
|
|
91
|
+
for (const leak of fileLeaks) {
|
|
92
|
+
leaks.push({
|
|
93
|
+
filePath,
|
|
94
|
+
...leak
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { leaks, failures };
|
|
99
|
+
}
|
|
100
|
+
function runCheckHomePaths(paths, options = {}) {
|
|
101
|
+
const stderr = options.stderr ?? process.stderr;
|
|
102
|
+
const setExitCode = options.setExitCode ?? ((code) => {
|
|
103
|
+
process.exitCode = code;
|
|
104
|
+
});
|
|
105
|
+
const { failures, leaks } = scanFilesForHardcodedHomePaths(paths, options.scanOptions);
|
|
106
|
+
if (failures.length > 0) {
|
|
107
|
+
const unreadableTarget = failures.length === 1 ? "file" : "files";
|
|
108
|
+
stderr.write(`Unreadable ${unreadableTarget} while scanning for hardcoded home paths:
|
|
109
|
+
`);
|
|
110
|
+
for (const failure of failures) {
|
|
111
|
+
stderr.write(` ${failure.filePath}: ${failure.reason}
|
|
112
|
+
`);
|
|
113
|
+
}
|
|
114
|
+
if (leaks.length > 0) {
|
|
115
|
+
stderr.write(`
|
|
116
|
+
`);
|
|
117
|
+
writeLeakSummary(stderr, leaks);
|
|
118
|
+
writeReplacementHint(stderr, leaks);
|
|
119
|
+
stderr.write(`
|
|
120
|
+
`);
|
|
121
|
+
} else {
|
|
122
|
+
stderr.write(`
|
|
123
|
+
`);
|
|
124
|
+
}
|
|
125
|
+
stderr.write(`Fix file permissions or remove the unreadable ${unreadableTarget} before committing.
|
|
126
|
+
`);
|
|
127
|
+
setExitCode(1);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (leaks.length === 0) {
|
|
131
|
+
setExitCode(0);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
writeLeakSummary(stderr, leaks);
|
|
135
|
+
writeReplacementHint(stderr, leaks);
|
|
136
|
+
setExitCode(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { findHomePathLeaks, scanFilesForHardcodedHomePaths, runCheckHomePaths };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CheckTsDocOptions } from "./tooling-tj9p41vj.js";
|
|
2
|
+
/**
|
|
3
|
+
* Run check-tsdoc across workspace packages.
|
|
4
|
+
*
|
|
5
|
+
* Discovers packages with `src/index.ts` entry points, analyzes TSDoc
|
|
6
|
+
* coverage on exported declarations, and reports per-package statistics.
|
|
7
|
+
* Calls `process.exit()` on completion.
|
|
8
|
+
*/
|
|
9
|
+
declare function runCheckTsdoc(options?: CheckTsDocOptions): Promise<void>;
|
|
10
|
+
export { runCheckTsdoc };
|