@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
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,79 @@
|
|
|
1
1
|
import { ZodType } from "zod";
|
|
2
|
+
/** Coverage classification for a single declaration. */
|
|
3
|
+
type CoverageLevel = "documented" | "partial" | "undocumented";
|
|
4
|
+
/** Result for a single exported declaration. */
|
|
5
|
+
interface DeclarationCoverage {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly kind: string;
|
|
8
|
+
readonly level: CoverageLevel;
|
|
9
|
+
readonly file: string;
|
|
10
|
+
readonly line: number;
|
|
11
|
+
}
|
|
12
|
+
/** Coverage summary statistics. */
|
|
13
|
+
interface CoverageSummary {
|
|
14
|
+
readonly documented: number;
|
|
15
|
+
readonly partial: number;
|
|
16
|
+
readonly undocumented: number;
|
|
17
|
+
readonly total: number;
|
|
18
|
+
readonly percentage: number;
|
|
19
|
+
}
|
|
20
|
+
/** Per-package TSDoc coverage stats. */
|
|
21
|
+
interface PackageCoverage {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
readonly declarations: readonly DeclarationCoverage[];
|
|
25
|
+
readonly documented: number;
|
|
26
|
+
readonly partial: number;
|
|
27
|
+
readonly undocumented: number;
|
|
28
|
+
readonly total: number;
|
|
29
|
+
readonly percentage: number;
|
|
30
|
+
}
|
|
31
|
+
/** Aggregated result across all packages. */
|
|
32
|
+
interface TsDocCheckResult {
|
|
33
|
+
readonly ok: boolean;
|
|
34
|
+
readonly packages: readonly PackageCoverage[];
|
|
35
|
+
readonly summary: CoverageSummary;
|
|
36
|
+
}
|
|
37
|
+
/** Zod schema for {@link CoverageLevel}. */
|
|
38
|
+
declare const coverageLevelSchema: ZodType<CoverageLevel>;
|
|
39
|
+
/** Zod schema for {@link DeclarationCoverage}. */
|
|
40
|
+
declare const declarationCoverageSchema: ZodType<DeclarationCoverage>;
|
|
41
|
+
/** Zod schema for {@link PackageCoverage}. */
|
|
42
|
+
declare const packageCoverageSchema: ZodType<PackageCoverage>;
|
|
43
|
+
/** Zod schema for {@link TsDocCheckResult}. */
|
|
44
|
+
declare const tsDocCheckResultSchema: ZodType<TsDocCheckResult>;
|
|
45
|
+
/** Options for the check-tsdoc command. */
|
|
46
|
+
interface CheckTsDocOptions {
|
|
47
|
+
readonly strict?: boolean | undefined;
|
|
48
|
+
readonly json?: boolean | undefined;
|
|
49
|
+
readonly minCoverage?: number | undefined;
|
|
50
|
+
readonly cwd?: string | undefined;
|
|
51
|
+
readonly paths?: readonly string[] | undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Analyze TSDoc coverage across workspace packages.
|
|
55
|
+
*
|
|
56
|
+
* Pure function that discovers packages, analyzes TSDoc coverage on exported
|
|
57
|
+
* declarations, and returns the aggregated result. Does not print output or
|
|
58
|
+
* call `process.exit()`.
|
|
59
|
+
*
|
|
60
|
+
* @param options - Analysis options (paths, strict mode, coverage threshold)
|
|
61
|
+
* @returns Aggregated coverage result across all packages, or `null` if no packages found
|
|
62
|
+
*/
|
|
63
|
+
declare function analyzeCheckTsdoc(options?: CheckTsDocOptions): TsDocCheckResult | null;
|
|
64
|
+
/**
|
|
65
|
+
* Print a TSDoc coverage result in human-readable format.
|
|
66
|
+
*
|
|
67
|
+
* Renders a bar chart per package with summary statistics. Writes to stdout/stderr.
|
|
68
|
+
*
|
|
69
|
+
* @param result - The coverage result to print
|
|
70
|
+
* @param options - Display options (strict mode, coverage threshold for warning)
|
|
71
|
+
*/
|
|
72
|
+
declare function printCheckTsdocHuman(result: TsDocCheckResult, options?: {
|
|
73
|
+
strict?: boolean | undefined;
|
|
74
|
+
minCoverage?: number | undefined;
|
|
75
|
+
}): void;
|
|
76
|
+
import { ZodType as ZodType2 } from "zod";
|
|
2
77
|
/**
|
|
3
78
|
* File entry in a block.
|
|
4
79
|
*/
|
|
@@ -16,7 +91,7 @@ interface FileEntry {
|
|
|
16
91
|
* Schema for a file entry in a block.
|
|
17
92
|
* Represents a file that will be copied to the user's project.
|
|
18
93
|
*/
|
|
19
|
-
declare const FileEntrySchema:
|
|
94
|
+
declare const FileEntrySchema: ZodType2<FileEntry>;
|
|
20
95
|
/**
|
|
21
96
|
* Block in the registry.
|
|
22
97
|
*/
|
|
@@ -38,7 +113,7 @@ interface Block {
|
|
|
38
113
|
* Schema for a block in the registry.
|
|
39
114
|
* A block is a collection of related files that can be added together.
|
|
40
115
|
*/
|
|
41
|
-
declare const BlockSchema:
|
|
116
|
+
declare const BlockSchema: ZodType2<Block>;
|
|
42
117
|
/**
|
|
43
118
|
* Complete registry structure.
|
|
44
119
|
*/
|
|
@@ -52,7 +127,7 @@ interface Registry {
|
|
|
52
127
|
* Schema for the complete registry.
|
|
53
128
|
* Contains all available blocks with their files and metadata.
|
|
54
129
|
*/
|
|
55
|
-
declare const RegistrySchema:
|
|
130
|
+
declare const RegistrySchema: ZodType2<Registry>;
|
|
56
131
|
/**
|
|
57
132
|
* Block definition used in the build script.
|
|
58
133
|
* Specifies how to collect source files into a block.
|
|
@@ -108,4 +183,4 @@ interface AddBlockOptions {
|
|
|
108
183
|
}
|
|
109
184
|
/** Package version, read from package.json at load time. */
|
|
110
185
|
declare const VERSION: string;
|
|
111
|
-
export { VERSION, RegistrySchema, RegistryBuildConfig, Registry, FileEntrySchema, FileEntry, BlockSchema, BlockDefinition, Block, AddBlockResult, AddBlockOptions };
|
|
186
|
+
export { tsDocCheckResultSchema, printCheckTsdocHuman, packageCoverageSchema, declarationCoverageSchema, coverageLevelSchema, analyzeCheckTsdoc, VERSION, TsDocCheckResult, RegistrySchema, RegistryBuildConfig, Registry, PackageCoverage, FileEntrySchema, FileEntry, DeclarationCoverage, CoverageLevel, CheckTsDocOptions, BlockSchema, BlockDefinition, Block, AddBlockResult, AddBlockOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
VERSION
|
|
3
|
-
|
|
2
|
+
VERSION,
|
|
3
|
+
analyzeCheckTsdoc,
|
|
4
|
+
coverageLevelSchema,
|
|
5
|
+
declarationCoverageSchema,
|
|
6
|
+
packageCoverageSchema,
|
|
7
|
+
printCheckTsdocHuman,
|
|
8
|
+
tsDocCheckResultSchema
|
|
9
|
+
} from "./shared/chunk-cmde0fwx.js";
|
|
4
10
|
import"./shared/chunk-3s189drz.js";
|
|
5
|
-
|
|
6
11
|
// src/registry/schema.ts
|
|
7
12
|
import { z } from "zod";
|
|
8
13
|
var FileEntrySchema = z.object({
|
|
@@ -15,15 +20,21 @@ var BlockSchema = z.object({
|
|
|
15
20
|
name: z.string().min(1),
|
|
16
21
|
description: z.string().min(1),
|
|
17
22
|
files: z.array(FileEntrySchema).optional(),
|
|
18
|
-
dependencies: z.record(z.string()).optional(),
|
|
19
|
-
devDependencies: z.record(z.string()).optional(),
|
|
23
|
+
dependencies: z.record(z.string(), z.string()).optional(),
|
|
24
|
+
devDependencies: z.record(z.string(), z.string()).optional(),
|
|
20
25
|
extends: z.array(z.string()).optional()
|
|
21
26
|
});
|
|
22
27
|
var RegistrySchema = z.object({
|
|
23
28
|
version: z.string(),
|
|
24
|
-
blocks: z.record(BlockSchema)
|
|
29
|
+
blocks: z.record(z.string(), BlockSchema)
|
|
25
30
|
});
|
|
26
31
|
export {
|
|
32
|
+
tsDocCheckResultSchema,
|
|
33
|
+
printCheckTsdocHuman,
|
|
34
|
+
packageCoverageSchema,
|
|
35
|
+
declarationCoverageSchema,
|
|
36
|
+
coverageLevelSchema,
|
|
37
|
+
analyzeCheckTsdoc,
|
|
27
38
|
VERSION,
|
|
28
39
|
RegistrySchema,
|
|
29
40
|
FileEntrySchema,
|
package/dist/registry/build.d.ts
CHANGED
package/dist/registry/build.js
CHANGED
|
@@ -93,16 +93,16 @@ var REGISTRY_CONFIG = {
|
|
|
93
93
|
description: "Biome linter/formatter configuration via Ultracite",
|
|
94
94
|
files: ["packages/tooling/biome.json"],
|
|
95
95
|
remap: { "packages/tooling/biome.json": "biome.json" },
|
|
96
|
-
devDependencies: { ultracite: "^7.
|
|
96
|
+
devDependencies: { ultracite: "^7.2.3" }
|
|
97
97
|
},
|
|
98
98
|
lefthook: {
|
|
99
99
|
description: "Git hooks via Lefthook for pre-commit and pre-push",
|
|
100
100
|
files: ["packages/tooling/lefthook.yml"],
|
|
101
101
|
remap: { "packages/tooling/lefthook.yml": ".lefthook.yml" },
|
|
102
102
|
devDependencies: {
|
|
103
|
-
"@outfitter/tooling": "^0.2.
|
|
104
|
-
lefthook: "^2.
|
|
105
|
-
ultracite: "^7.
|
|
103
|
+
"@outfitter/tooling": "^0.2.4",
|
|
104
|
+
lefthook: "^2.1.1",
|
|
105
|
+
ultracite: "^7.2.3"
|
|
106
106
|
}
|
|
107
107
|
},
|
|
108
108
|
markdownlint: {
|
|
@@ -114,7 +114,10 @@ var REGISTRY_CONFIG = {
|
|
|
114
114
|
},
|
|
115
115
|
bootstrap: {
|
|
116
116
|
description: "Project bootstrap script for installing tools and dependencies",
|
|
117
|
-
files: ["
|
|
117
|
+
files: ["packages/tooling/templates/bootstrap.sh"],
|
|
118
|
+
remap: {
|
|
119
|
+
"packages/tooling/templates/bootstrap.sh": "scripts/bootstrap.sh"
|
|
120
|
+
}
|
|
118
121
|
},
|
|
119
122
|
scaffolding: {
|
|
120
123
|
description: "Full starter kit: Claude settings, Biome, Lefthook, markdownlint, and bootstrap script",
|
package/dist/registry/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "../shared/@outfitter/tooling-xqwn46sx";
|
|
2
|
-
import { AddBlockOptions, AddBlockResult, Block, BlockDefinition, BlockSchema, FileEntry, FileEntrySchema, Registry, RegistryBuildConfig, RegistrySchema } from "../shared/@outfitter/tooling-sjm8nebx";
|
|
1
|
+
import "../shared/@outfitter/tooling-xqwn46sx.js";
|
|
2
|
+
import { AddBlockOptions, AddBlockResult, Block, BlockDefinition, BlockSchema, FileEntry, FileEntrySchema, Registry, RegistryBuildConfig, RegistrySchema } from "../shared/@outfitter/tooling-sjm8nebx.js";
|
|
3
3
|
export { RegistrySchema, RegistryBuildConfig, Registry, FileEntrySchema, FileEntry, BlockSchema, BlockDefinition, Block, AddBlockResult, AddBlockOptions };
|
package/dist/registry/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { AddBlockOptions, AddBlockResult, Block, BlockDefinition, BlockSchema, FileEntry, FileEntrySchema, Registry, RegistryBuildConfig, RegistrySchema } from "../shared/@outfitter/tooling-sjm8nebx";
|
|
1
|
+
import { AddBlockOptions, AddBlockResult, Block, BlockDefinition, BlockSchema, FileEntry, FileEntrySchema, Registry, RegistryBuildConfig, RegistrySchema } from "../shared/@outfitter/tooling-sjm8nebx.js";
|
|
2
2
|
export { RegistrySchema, RegistryBuildConfig, Registry, FileEntrySchema, FileEntry, BlockSchema, BlockDefinition, Block, AddBlockResult, AddBlockOptions };
|
package/dist/registry/schema.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
analyzeSourceFile,
|
|
4
|
+
calculateCoverage
|
|
5
|
+
} from "./tooling-qk5xgmxr.js";
|
|
6
|
+
|
|
2
7
|
// packages/tooling/src/cli/pre-push.ts
|
|
3
8
|
import { existsSync, readFileSync } from "fs";
|
|
4
|
-
import { join } from "path";
|
|
9
|
+
import { join, resolve } from "path";
|
|
10
|
+
import ts from "typescript";
|
|
5
11
|
var COLORS = {
|
|
6
12
|
reset: "\x1B[0m",
|
|
7
13
|
red: "\x1B[31m",
|
|
@@ -38,6 +44,9 @@ function isRedPhaseBranch(branch) {
|
|
|
38
44
|
function isScaffoldBranch(branch) {
|
|
39
45
|
return branch.endsWith("-scaffold") || branch.endsWith("/scaffold") || branch.endsWith("_scaffold");
|
|
40
46
|
}
|
|
47
|
+
function isReleaseBranch(branch) {
|
|
48
|
+
return branch.startsWith("changeset-release/");
|
|
49
|
+
}
|
|
41
50
|
var TEST_PATH_PATTERNS = [
|
|
42
51
|
/(^|\/)__tests__\//,
|
|
43
52
|
/(^|\/)__snapshots__\//,
|
|
@@ -57,6 +66,32 @@ function areFilesTestOnly(paths) {
|
|
|
57
66
|
function canBypassRedPhaseByChangedFiles(changedFiles) {
|
|
58
67
|
return changedFiles.deterministic && areFilesTestOnly(changedFiles.files);
|
|
59
68
|
}
|
|
69
|
+
function hasPackageSourceChanges(changedFiles) {
|
|
70
|
+
const packageSrcPattern = /^packages\/[^/]+\/src\//;
|
|
71
|
+
return changedFiles.files.some((f) => packageSrcPattern.test(f));
|
|
72
|
+
}
|
|
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
|
+
}
|
|
60
95
|
function resolveBaseRef() {
|
|
61
96
|
const candidates = [
|
|
62
97
|
"origin/main",
|
|
@@ -224,6 +259,11 @@ async function runPrePush(options = {}) {
|
|
|
224
259
|
log(`${COLORS.blue}Pre-push verify${COLORS.reset} (TDD-aware)`);
|
|
225
260
|
log("");
|
|
226
261
|
const branch = getCurrentBranch();
|
|
262
|
+
if (isReleaseBranch(branch)) {
|
|
263
|
+
log(`${COLORS.yellow}Release branch detected${COLORS.reset}: ${COLORS.blue}${branch}${COLORS.reset}`);
|
|
264
|
+
log(`${COLORS.yellow}Skipping strict verification${COLORS.reset} for automated changeset release push`);
|
|
265
|
+
process.exit(0);
|
|
266
|
+
}
|
|
227
267
|
if (isRedPhaseBranch(branch)) {
|
|
228
268
|
if (maybeSkipForRedPhase("branch", branch)) {
|
|
229
269
|
process.exit(0);
|
|
@@ -269,9 +309,15 @@ async function runPrePush(options = {}) {
|
|
|
269
309
|
log(" - feature_tests");
|
|
270
310
|
process.exit(1);
|
|
271
311
|
}
|
|
312
|
+
const changedFiles = getChangedFilesForPush();
|
|
313
|
+
if (hasPackageSourceChanges(changedFiles)) {
|
|
314
|
+
try {
|
|
315
|
+
await printTsdocSummary();
|
|
316
|
+
} catch {}
|
|
317
|
+
}
|
|
272
318
|
log("");
|
|
273
319
|
log(`${COLORS.green}Strict verification passed${COLORS.reset}`);
|
|
274
320
|
process.exit(0);
|
|
275
321
|
}
|
|
276
322
|
|
|
277
|
-
export { isRedPhaseBranch, isScaffoldBranch, isTestOnlyPath, areFilesTestOnly, canBypassRedPhaseByChangedFiles, createVerificationPlan, runPrePush };
|
|
323
|
+
export { isRedPhaseBranch, isScaffoldBranch, isReleaseBranch, isTestOnlyPath, areFilesTestOnly, canBypassRedPhaseByChangedFiles, hasPackageSourceChanges, createVerificationPlan, runPrePush };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/tooling/src/cli/check-changeset.ts
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
function getChangedPackagePaths(files) {
|
|
6
|
+
const packageNames = new Set;
|
|
7
|
+
const pattern = /^packages\/([^/]+)\/src\//;
|
|
8
|
+
for (const file of files) {
|
|
9
|
+
const match = pattern.exec(file);
|
|
10
|
+
if (match?.[1]) {
|
|
11
|
+
packageNames.add(match[1]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return [...packageNames].sort();
|
|
15
|
+
}
|
|
16
|
+
function getChangedChangesetFiles(files) {
|
|
17
|
+
const pattern = /^\.changeset\/([^/]+\.md)$/;
|
|
18
|
+
const results = [];
|
|
19
|
+
for (const file of files) {
|
|
20
|
+
const match = pattern.exec(file);
|
|
21
|
+
if (match?.[1] && match[1] !== "README.md") {
|
|
22
|
+
results.push(match[1]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return results.sort();
|
|
26
|
+
}
|
|
27
|
+
function checkChangesetRequired(changedPackages, changesetFiles) {
|
|
28
|
+
if (changedPackages.length === 0) {
|
|
29
|
+
return { ok: true, missingFor: [] };
|
|
30
|
+
}
|
|
31
|
+
if (changesetFiles.length > 0) {
|
|
32
|
+
return { ok: true, missingFor: [] };
|
|
33
|
+
}
|
|
34
|
+
return { ok: false, missingFor: changedPackages };
|
|
35
|
+
}
|
|
36
|
+
function parseIgnoredPackagesFromChangesetConfig(jsonContent) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(jsonContent);
|
|
39
|
+
if (!Array.isArray(parsed.ignore)) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
return parsed.ignore.filter((entry) => typeof entry === "string");
|
|
43
|
+
} catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function parseChangesetFrontmatterPackageNames(markdownContent) {
|
|
48
|
+
const frontmatterMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdownContent);
|
|
49
|
+
if (!frontmatterMatch?.[1]) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
const packages = new Set;
|
|
53
|
+
for (const line of frontmatterMatch[1].split(/\r?\n/)) {
|
|
54
|
+
const trimmed = line.trim();
|
|
55
|
+
const match = /^(["']?)(@[^"':\s]+\/[^"':\s]+)\1\s*:/.exec(trimmed);
|
|
56
|
+
if (match?.[2]) {
|
|
57
|
+
packages.add(match[2]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return [...packages].sort();
|
|
61
|
+
}
|
|
62
|
+
function findIgnoredPackageReferences(input) {
|
|
63
|
+
if (input.ignoredPackages.length === 0 || input.changesetFiles.length === 0) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const ignored = new Set(input.ignoredPackages);
|
|
67
|
+
const results = [];
|
|
68
|
+
for (const file of input.changesetFiles) {
|
|
69
|
+
const content = input.readChangesetFile(file);
|
|
70
|
+
const referencedPackages = parseChangesetFrontmatterPackageNames(content);
|
|
71
|
+
const invalidReferences = referencedPackages.filter((pkg) => ignored.has(pkg));
|
|
72
|
+
if (invalidReferences.length > 0) {
|
|
73
|
+
results.push({ file, packages: invalidReferences.sort() });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return results.sort((a, b) => a.file.localeCompare(b.file));
|
|
77
|
+
}
|
|
78
|
+
function loadIgnoredPackages(cwd) {
|
|
79
|
+
const configPath = join(cwd, ".changeset", "config.json");
|
|
80
|
+
if (!existsSync(configPath)) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
return parseIgnoredPackagesFromChangesetConfig(readFileSync(configPath, "utf-8"));
|
|
85
|
+
} catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function getIgnoredReferencesForChangedChangesets(cwd, changesetFiles) {
|
|
90
|
+
const ignoredPackages = loadIgnoredPackages(cwd);
|
|
91
|
+
return findIgnoredPackageReferences({
|
|
92
|
+
changesetFiles,
|
|
93
|
+
ignoredPackages,
|
|
94
|
+
readChangesetFile: (filename) => {
|
|
95
|
+
try {
|
|
96
|
+
return readFileSync(join(cwd, ".changeset", filename), "utf-8");
|
|
97
|
+
} catch {
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
var COLORS = {
|
|
104
|
+
reset: "\x1B[0m",
|
|
105
|
+
red: "\x1B[31m",
|
|
106
|
+
green: "\x1B[32m",
|
|
107
|
+
yellow: "\x1B[33m",
|
|
108
|
+
blue: "\x1B[34m",
|
|
109
|
+
dim: "\x1B[2m"
|
|
110
|
+
};
|
|
111
|
+
async function runCheckChangeset(options = {}) {
|
|
112
|
+
if (options.skip || process.env["NO_CHANGESET"] === "1") {
|
|
113
|
+
process.stdout.write(`${COLORS.dim}check-changeset skipped (NO_CHANGESET=1)${COLORS.reset}
|
|
114
|
+
`);
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
if (process.env["GITHUB_EVENT_NAME"] === "push") {
|
|
118
|
+
process.stdout.write(`${COLORS.dim}check-changeset skipped (push event)${COLORS.reset}
|
|
119
|
+
`);
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
const cwd = process.cwd();
|
|
123
|
+
let changedFiles;
|
|
124
|
+
try {
|
|
125
|
+
const proc = Bun.spawnSync(["git", "diff", "--name-only", "origin/main...HEAD"], { cwd });
|
|
126
|
+
if (proc.exitCode !== 0) {
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
changedFiles = proc.stdout.toString().trim().split(`
|
|
130
|
+
`).filter((line) => line.length > 0);
|
|
131
|
+
} catch {
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
const changedPackages = getChangedPackagePaths(changedFiles);
|
|
135
|
+
if (changedPackages.length === 0) {
|
|
136
|
+
process.stdout.write(`${COLORS.green}No package source changes detected.${COLORS.reset}
|
|
137
|
+
`);
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
const changesetFiles = getChangedChangesetFiles(changedFiles);
|
|
141
|
+
const check = checkChangesetRequired(changedPackages, changesetFiles);
|
|
142
|
+
if (!check.ok) {
|
|
143
|
+
process.stderr.write(`${COLORS.red}Missing changeset!${COLORS.reset}
|
|
144
|
+
|
|
145
|
+
`);
|
|
146
|
+
process.stderr.write(`The following packages have source changes but no changeset:
|
|
147
|
+
|
|
148
|
+
`);
|
|
149
|
+
for (const pkg of check.missingFor) {
|
|
150
|
+
process.stderr.write(` ${COLORS.yellow}@outfitter/${pkg}${COLORS.reset}
|
|
151
|
+
`);
|
|
152
|
+
}
|
|
153
|
+
process.stderr.write(`
|
|
154
|
+
Run ${COLORS.blue}bun run changeset${COLORS.reset} to add a changeset, ` + `or add the ${COLORS.blue}no-changeset${COLORS.reset} label to skip.
|
|
155
|
+
`);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
const ignoredReferences = getIgnoredReferencesForChangedChangesets(cwd, changesetFiles);
|
|
159
|
+
if (ignoredReferences.length > 0) {
|
|
160
|
+
process.stderr.write(`${COLORS.red}Invalid changeset package reference(s).${COLORS.reset}
|
|
161
|
+
|
|
162
|
+
`);
|
|
163
|
+
process.stderr.write(`Changesets must not reference packages listed in .changeset/config.json ignore:
|
|
164
|
+
|
|
165
|
+
`);
|
|
166
|
+
for (const reference of ignoredReferences) {
|
|
167
|
+
process.stderr.write(` ${COLORS.yellow}${reference.file}${COLORS.reset}
|
|
168
|
+
`);
|
|
169
|
+
for (const pkg of reference.packages) {
|
|
170
|
+
process.stderr.write(` - ${pkg}
|
|
171
|
+
`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
process.stderr.write(`
|
|
175
|
+
Update the affected changeset files to remove ignored packages before merging.
|
|
176
|
+
`);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
process.stdout.write(`${COLORS.green}Changeset found for ${changedPackages.length} changed package(s).${COLORS.reset}
|
|
180
|
+
`);
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export { getChangedPackagePaths, getChangedChangesetFiles, checkChangesetRequired, parseIgnoredPackagesFromChangesetConfig, parseChangesetFrontmatterPackageNames, findIgnoredPackageReferences, runCheckChangeset };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { ZodType } from "zod";
|
|
3
|
+
/** Coverage classification for a single declaration. */
|
|
4
|
+
type CoverageLevel = "documented" | "partial" | "undocumented";
|
|
5
|
+
/** Result for a single exported declaration. */
|
|
6
|
+
interface DeclarationCoverage {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly kind: string;
|
|
9
|
+
readonly level: CoverageLevel;
|
|
10
|
+
readonly file: string;
|
|
11
|
+
readonly line: number;
|
|
12
|
+
}
|
|
13
|
+
/** Coverage summary statistics. */
|
|
14
|
+
interface CoverageSummary {
|
|
15
|
+
readonly documented: number;
|
|
16
|
+
readonly partial: number;
|
|
17
|
+
readonly undocumented: number;
|
|
18
|
+
readonly total: number;
|
|
19
|
+
readonly percentage: number;
|
|
20
|
+
}
|
|
21
|
+
/** Per-package TSDoc coverage stats. */
|
|
22
|
+
interface PackageCoverage {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly path: string;
|
|
25
|
+
readonly declarations: readonly DeclarationCoverage[];
|
|
26
|
+
readonly documented: number;
|
|
27
|
+
readonly partial: number;
|
|
28
|
+
readonly undocumented: number;
|
|
29
|
+
readonly total: number;
|
|
30
|
+
readonly percentage: number;
|
|
31
|
+
}
|
|
32
|
+
/** Aggregated result across all packages. */
|
|
33
|
+
interface TsDocCheckResult {
|
|
34
|
+
readonly ok: boolean;
|
|
35
|
+
readonly packages: readonly PackageCoverage[];
|
|
36
|
+
readonly summary: CoverageSummary;
|
|
37
|
+
}
|
|
38
|
+
/** Zod schema for {@link CoverageLevel}. */
|
|
39
|
+
declare const coverageLevelSchema: ZodType<CoverageLevel>;
|
|
40
|
+
/** Zod schema for {@link DeclarationCoverage}. */
|
|
41
|
+
declare const declarationCoverageSchema: ZodType<DeclarationCoverage>;
|
|
42
|
+
/** Zod schema for {@link CoverageSummary}. */
|
|
43
|
+
declare const coverageSummarySchema: ZodType<CoverageSummary>;
|
|
44
|
+
/** Zod schema for {@link PackageCoverage}. */
|
|
45
|
+
declare const packageCoverageSchema: ZodType<PackageCoverage>;
|
|
46
|
+
/** Zod schema for {@link TsDocCheckResult}. */
|
|
47
|
+
declare const tsDocCheckResultSchema: ZodType<TsDocCheckResult>;
|
|
48
|
+
/** Options for the check-tsdoc command. */
|
|
49
|
+
interface CheckTsDocOptions {
|
|
50
|
+
readonly strict?: boolean | undefined;
|
|
51
|
+
readonly json?: boolean | undefined;
|
|
52
|
+
readonly minCoverage?: number | undefined;
|
|
53
|
+
readonly cwd?: string | undefined;
|
|
54
|
+
readonly paths?: readonly string[] | undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check whether a node is an exported declaration worth checking.
|
|
58
|
+
*
|
|
59
|
+
* Returns true for function, interface, type alias, class, enum, and variable
|
|
60
|
+
* declarations that carry the `export` keyword. Re-exports (`{ ... } from`)
|
|
61
|
+
* and `*` are excluded since TSDoc belongs at the definition site.
|
|
62
|
+
*/
|
|
63
|
+
declare function isExportedDeclaration(node: ts.Node): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Extract the name of a declaration node.
|
|
66
|
+
*
|
|
67
|
+
* For variable statements, returns the name of the first variable declarator.
|
|
68
|
+
* Returns `undefined` for anonymous declarations (e.g., `function() {}`).
|
|
69
|
+
*/
|
|
70
|
+
declare function getDeclarationName(node: ts.Node): string | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Determine the kind label for a declaration node.
|
|
73
|
+
*
|
|
74
|
+
* Maps AST node types to human-readable kind strings used in coverage reports.
|
|
75
|
+
*/
|
|
76
|
+
declare function getDeclarationKind(node: ts.Node): string;
|
|
77
|
+
/**
|
|
78
|
+
* Classify a declaration's TSDoc coverage level.
|
|
79
|
+
*
|
|
80
|
+
* - `"documented"` -- has a JSDoc comment with a description. For interfaces
|
|
81
|
+
* and classes, all members must also have JSDoc comments.
|
|
82
|
+
* - `"partial"` -- the declaration has a JSDoc comment but some members
|
|
83
|
+
* (in interfaces/classes) lack documentation.
|
|
84
|
+
* - `"undocumented"` -- no JSDoc comment at all.
|
|
85
|
+
*/
|
|
86
|
+
declare function classifyDeclaration(node: ts.Node, sourceFile: ts.SourceFile): CoverageLevel;
|
|
87
|
+
/**
|
|
88
|
+
* Analyze all exported declarations in a source file for TSDoc coverage.
|
|
89
|
+
*
|
|
90
|
+
* Walks top-level statements, filters to exported declarations, and
|
|
91
|
+
* classifies each for documentation coverage.
|
|
92
|
+
*/
|
|
93
|
+
declare function analyzeSourceFile(sourceFile: ts.SourceFile): DeclarationCoverage[];
|
|
94
|
+
/**
|
|
95
|
+
* Calculate aggregate coverage statistics from declaration results.
|
|
96
|
+
*
|
|
97
|
+
* Partial documentation counts as half coverage in the percentage calculation.
|
|
98
|
+
* An empty array returns 100% (no declarations to check).
|
|
99
|
+
*/
|
|
100
|
+
declare function calculateCoverage(declarations: readonly DeclarationCoverage[]): {
|
|
101
|
+
documented: number;
|
|
102
|
+
partial: number;
|
|
103
|
+
undocumented: number;
|
|
104
|
+
total: number;
|
|
105
|
+
percentage: number;
|
|
106
|
+
};
|
|
107
|
+
/** Resolve whether JSON output mode is active. */
|
|
108
|
+
declare function resolveJsonMode(options?: CheckTsDocOptions): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Analyze TSDoc coverage across workspace packages.
|
|
111
|
+
*
|
|
112
|
+
* Pure function that discovers packages, analyzes TSDoc coverage on exported
|
|
113
|
+
* declarations, and returns the aggregated result. Does not print output or
|
|
114
|
+
* call `process.exit()`.
|
|
115
|
+
*
|
|
116
|
+
* @param options - Analysis options (paths, strict mode, coverage threshold)
|
|
117
|
+
* @returns Aggregated coverage result across all packages, or `null` if no packages found
|
|
118
|
+
*/
|
|
119
|
+
declare function analyzeCheckTsdoc(options?: CheckTsDocOptions): TsDocCheckResult | null;
|
|
120
|
+
/**
|
|
121
|
+
* Print a TSDoc coverage result in human-readable format.
|
|
122
|
+
*
|
|
123
|
+
* Renders a bar chart per package with summary statistics. Writes to stdout/stderr.
|
|
124
|
+
*
|
|
125
|
+
* @param result - The coverage result to print
|
|
126
|
+
* @param options - Display options (strict mode, coverage threshold for warning)
|
|
127
|
+
*/
|
|
128
|
+
declare function printCheckTsdocHuman(result: TsDocCheckResult, options?: {
|
|
129
|
+
strict?: boolean | undefined;
|
|
130
|
+
minCoverage?: number | undefined;
|
|
131
|
+
}): void;
|
|
132
|
+
/**
|
|
133
|
+
* Run check-tsdoc across workspace packages.
|
|
134
|
+
*
|
|
135
|
+
* Discovers packages with `src/index.ts` entry points, analyzes TSDoc
|
|
136
|
+
* coverage on exported declarations, and reports per-package statistics.
|
|
137
|
+
* Calls `process.exit()` on completion.
|
|
138
|
+
*/
|
|
139
|
+
declare function runCheckTsdoc(options?: CheckTsDocOptions): Promise<void>;
|
|
140
|
+
export { CoverageLevel, DeclarationCoverage, CoverageSummary, PackageCoverage, TsDocCheckResult, coverageLevelSchema, declarationCoverageSchema, coverageSummarySchema, packageCoverageSchema, tsDocCheckResultSchema, CheckTsDocOptions, isExportedDeclaration, getDeclarationName, getDeclarationKind, classifyDeclaration, analyzeSourceFile, calculateCoverage, resolveJsonMode, analyzeCheckTsdoc, printCheckTsdocHuman, runCheckTsdoc };
|