@outfitter/tooling 0.2.3 → 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.
Files changed (35) hide show
  1. package/README.md +69 -1
  2. package/biome.json +1 -1
  3. package/dist/cli/check-changeset.d.ts +12 -10
  4. package/dist/cli/check-changeset.js +7 -1
  5. package/dist/cli/check-exports.d.ts +2 -2
  6. package/dist/cli/check-exports.js +3 -1
  7. package/dist/cli/check-readme-imports.d.ts +3 -2
  8. package/dist/cli/check-readme-imports.js +5 -1
  9. package/dist/cli/check-tsdoc.d.ts +2 -0
  10. package/dist/cli/check-tsdoc.js +36 -0
  11. package/dist/cli/index.js +208 -51
  12. package/dist/cli/pre-push.d.ts +8 -1
  13. package/dist/cli/pre-push.js +6 -1
  14. package/dist/index.d.ts +79 -4
  15. package/dist/index.js +17 -6
  16. package/dist/registry/build.d.ts +1 -1
  17. package/dist/registry/build.js +8 -5
  18. package/dist/registry/index.d.ts +2 -2
  19. package/dist/registry/index.js +1 -1
  20. package/dist/registry/schema.d.ts +1 -1
  21. package/dist/registry/schema.js +1 -1
  22. package/dist/shared/@outfitter/{tooling-8sd32ts6.js → tooling-2n2dpsaa.js} +48 -2
  23. package/dist/shared/@outfitter/tooling-cj5vsa9k.js +184 -0
  24. package/dist/shared/@outfitter/{tooling-tf22zt9p.js → tooling-enjcenja.js} +5 -2
  25. package/dist/shared/@outfitter/tooling-njw4z34x.d.ts +140 -0
  26. package/dist/shared/@outfitter/tooling-qk5xgmxr.js +405 -0
  27. package/dist/shared/@outfitter/{tooling-q0d60xb3.d.ts → tooling-wesswf21.d.ts} +2 -1
  28. package/dist/shared/@outfitter/{tooling-g83d0kjv.js → tooling-wv09k6hr.js} +3 -3
  29. package/dist/shared/{chunk-6a7tjcgm.js → chunk-7tdgbqb0.js} +5 -1
  30. package/dist/shared/chunk-cmde0fwx.js +421 -0
  31. package/dist/version.d.ts +1 -1
  32. package/package.json +14 -6
  33. package/registry/registry.json +6 -6
  34. package/dist/shared/@outfitter/tooling-3w8vr2w3.js +0 -94
  35. package/dist/shared/chunk-8aenrm6f.js +0 -18
package/dist/cli/index.js CHANGED
@@ -1,12 +1,17 @@
1
1
  #!/usr/bin/env bun
2
2
  import {
3
- VERSION
4
- } from "../shared/chunk-8aenrm6f.js";
3
+ VERSION,
4
+ analyzeSourceFile,
5
+ calculateCoverage,
6
+ runCheckTsdoc
7
+ } from "../shared/chunk-cmde0fwx.js";
5
8
  import {
6
9
  __require
7
10
  } from "../shared/chunk-3s189drz.js";
8
11
 
9
12
  // src/cli/index.ts
13
+ import { exitWithError } from "@outfitter/cli";
14
+ import { createCLI } from "@outfitter/cli/command";
10
15
  import { Command } from "commander";
11
16
 
12
17
  // src/cli/check.ts
@@ -203,6 +208,8 @@ Add the missing entries to ${COLORS.blue}bunup.config.ts${COLORS.reset} defineWo
203
208
  }
204
209
 
205
210
  // src/cli/check-changeset.ts
211
+ import { existsSync, readFileSync } from "node:fs";
212
+ import { join } from "node:path";
206
213
  function getChangedPackagePaths(files) {
207
214
  const packageNames = new Set;
208
215
  const pattern = /^packages\/([^/]+)\/src\//;
@@ -234,6 +241,73 @@ function checkChangesetRequired(changedPackages, changesetFiles) {
234
241
  }
235
242
  return { ok: false, missingFor: changedPackages };
236
243
  }
244
+ function parseIgnoredPackagesFromChangesetConfig(jsonContent) {
245
+ try {
246
+ const parsed = JSON.parse(jsonContent);
247
+ if (!Array.isArray(parsed.ignore)) {
248
+ return [];
249
+ }
250
+ return parsed.ignore.filter((entry) => typeof entry === "string");
251
+ } catch {
252
+ return [];
253
+ }
254
+ }
255
+ function parseChangesetFrontmatterPackageNames(markdownContent) {
256
+ const frontmatterMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdownContent);
257
+ if (!frontmatterMatch?.[1]) {
258
+ return [];
259
+ }
260
+ const packages = new Set;
261
+ for (const line of frontmatterMatch[1].split(/\r?\n/)) {
262
+ const trimmed = line.trim();
263
+ const match = /^(["']?)(@[^"':\s]+\/[^"':\s]+)\1\s*:/.exec(trimmed);
264
+ if (match?.[2]) {
265
+ packages.add(match[2]);
266
+ }
267
+ }
268
+ return [...packages].sort();
269
+ }
270
+ function findIgnoredPackageReferences(input) {
271
+ if (input.ignoredPackages.length === 0 || input.changesetFiles.length === 0) {
272
+ return [];
273
+ }
274
+ const ignored = new Set(input.ignoredPackages);
275
+ const results = [];
276
+ for (const file of input.changesetFiles) {
277
+ const content = input.readChangesetFile(file);
278
+ const referencedPackages = parseChangesetFrontmatterPackageNames(content);
279
+ const invalidReferences = referencedPackages.filter((pkg) => ignored.has(pkg));
280
+ if (invalidReferences.length > 0) {
281
+ results.push({ file, packages: invalidReferences.sort() });
282
+ }
283
+ }
284
+ return results.sort((a, b) => a.file.localeCompare(b.file));
285
+ }
286
+ function loadIgnoredPackages(cwd) {
287
+ const configPath = join(cwd, ".changeset", "config.json");
288
+ if (!existsSync(configPath)) {
289
+ return [];
290
+ }
291
+ try {
292
+ return parseIgnoredPackagesFromChangesetConfig(readFileSync(configPath, "utf-8"));
293
+ } catch {
294
+ return [];
295
+ }
296
+ }
297
+ function getIgnoredReferencesForChangedChangesets(cwd, changesetFiles) {
298
+ const ignoredPackages = loadIgnoredPackages(cwd);
299
+ return findIgnoredPackageReferences({
300
+ changesetFiles,
301
+ ignoredPackages,
302
+ readChangesetFile: (filename) => {
303
+ try {
304
+ return readFileSync(join(cwd, ".changeset", filename), "utf-8");
305
+ } catch {
306
+ return "";
307
+ }
308
+ }
309
+ });
310
+ }
237
311
  var COLORS2 = {
238
312
  reset: "\x1B[0m",
239
313
  red: "\x1B[31m",
@@ -273,25 +347,46 @@ async function runCheckChangeset(options = {}) {
273
347
  }
274
348
  const changesetFiles = getChangedChangesetFiles(changedFiles);
275
349
  const check = checkChangesetRequired(changedPackages, changesetFiles);
276
- if (check.ok) {
277
- process.stdout.write(`${COLORS2.green}Changeset found for ${changedPackages.length} changed package(s).${COLORS2.reset}
350
+ if (!check.ok) {
351
+ process.stderr.write(`${COLORS2.red}Missing changeset!${COLORS2.reset}
352
+
278
353
  `);
279
- process.exit(0);
354
+ process.stderr.write(`The following packages have source changes but no changeset:
355
+
356
+ `);
357
+ for (const pkg of check.missingFor) {
358
+ process.stderr.write(` ${COLORS2.yellow}@outfitter/${pkg}${COLORS2.reset}
359
+ `);
360
+ }
361
+ process.stderr.write(`
362
+ Run ${COLORS2.blue}bun run changeset${COLORS2.reset} to add a changeset, ` + `or add the ${COLORS2.blue}no-changeset${COLORS2.reset} label to skip.
363
+ `);
364
+ process.exit(1);
280
365
  }
281
- process.stderr.write(`${COLORS2.red}Missing changeset!${COLORS2.reset}
366
+ const ignoredReferences = getIgnoredReferencesForChangedChangesets(cwd, changesetFiles);
367
+ if (ignoredReferences.length > 0) {
368
+ process.stderr.write(`${COLORS2.red}Invalid changeset package reference(s).${COLORS2.reset}
282
369
 
283
370
  `);
284
- process.stderr.write(`The following packages have source changes but no changeset:
371
+ process.stderr.write(`Changesets must not reference packages listed in .changeset/config.json ignore:
285
372
 
286
373
  `);
287
- for (const pkg of check.missingFor) {
288
- process.stderr.write(` ${COLORS2.yellow}@outfitter/${pkg}${COLORS2.reset}
374
+ for (const reference of ignoredReferences) {
375
+ process.stderr.write(` ${COLORS2.yellow}${reference.file}${COLORS2.reset}
289
376
  `);
377
+ for (const pkg of reference.packages) {
378
+ process.stderr.write(` - ${pkg}
379
+ `);
380
+ }
381
+ }
382
+ process.stderr.write(`
383
+ Update the affected changeset files to remove ignored packages before merging.
384
+ `);
385
+ process.exit(1);
290
386
  }
291
- process.stderr.write(`
292
- Run ${COLORS2.blue}bun run changeset${COLORS2.reset} to add a changeset, ` + `or add the ${COLORS2.blue}no-changeset${COLORS2.reset} label to skip.
387
+ process.stdout.write(`${COLORS2.green}Changeset found for ${changedPackages.length} changed package(s).${COLORS2.reset}
293
388
  `);
294
- process.exit(1);
389
+ process.exit(0);
295
390
  }
296
391
 
297
392
  // src/cli/check-clean-tree.ts
@@ -501,6 +596,9 @@ var COLORS4 = {
501
596
  blue: "\x1B[34m",
502
597
  dim: "\x1B[2m"
503
598
  };
599
+ function resolveJsonMode(options = {}) {
600
+ return options.json ?? process.env["OUTFITTER_JSON"] === "1";
601
+ }
504
602
  async function runCheckExports(options = {}) {
505
603
  const cwd = process.cwd();
506
604
  const configPath = resolve3(cwd, "bunup.config.ts");
@@ -543,7 +641,7 @@ async function runCheckExports(options = {}) {
543
641
  ok: results.every((r) => r.status === "ok"),
544
642
  packages: results
545
643
  };
546
- if (options.json) {
644
+ if (resolveJsonMode(options)) {
547
645
  process.stdout.write(`${JSON.stringify(checkResult, null, 2)}
548
646
  `);
549
647
  } else {
@@ -669,8 +767,9 @@ async function runInit(cwd = process.cwd()) {
669
767
  }
670
768
 
671
769
  // src/cli/pre-push.ts
672
- import { existsSync, readFileSync } from "node:fs";
673
- import { join } from "node:path";
770
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
771
+ import { join as join2, resolve as resolve4 } from "node:path";
772
+ import ts from "typescript";
674
773
  var COLORS5 = {
675
774
  reset: "\x1B[0m",
676
775
  red: "\x1B[31m",
@@ -707,6 +806,9 @@ function isRedPhaseBranch(branch) {
707
806
  function isScaffoldBranch(branch) {
708
807
  return branch.endsWith("-scaffold") || branch.endsWith("/scaffold") || branch.endsWith("_scaffold");
709
808
  }
809
+ function isReleaseBranch(branch) {
810
+ return branch.startsWith("changeset-release/");
811
+ }
710
812
  var TEST_PATH_PATTERNS = [
711
813
  /(^|\/)__tests__\//,
712
814
  /(^|\/)__snapshots__\//,
@@ -726,6 +828,32 @@ function areFilesTestOnly(paths) {
726
828
  function canBypassRedPhaseByChangedFiles(changedFiles) {
727
829
  return changedFiles.deterministic && areFilesTestOnly(changedFiles.files);
728
830
  }
831
+ function hasPackageSourceChanges(changedFiles) {
832
+ const packageSrcPattern = /^packages\/[^/]+\/src\//;
833
+ return changedFiles.files.some((f) => packageSrcPattern.test(f));
834
+ }
835
+ async function printTsdocSummary() {
836
+ const glob = new Bun.Glob("packages/*/src/index.ts");
837
+ const cwd = process.cwd();
838
+ const allDeclarations = [];
839
+ for (const entry of glob.scanSync({ cwd })) {
840
+ const filePath = resolve4(cwd, entry);
841
+ const content = await Bun.file(filePath).text();
842
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
843
+ allDeclarations.push(...analyzeSourceFile(sourceFile));
844
+ }
845
+ if (allDeclarations.length === 0)
846
+ return;
847
+ const coverage = calculateCoverage(allDeclarations);
848
+ const parts = [];
849
+ if (coverage.documented > 0)
850
+ parts.push(`${coverage.documented} documented`);
851
+ if (coverage.partial > 0)
852
+ parts.push(`${coverage.partial} partial`);
853
+ if (coverage.undocumented > 0)
854
+ parts.push(`${coverage.undocumented} undocumented`);
855
+ log(`${COLORS5.blue}TSDoc${COLORS5.reset}: ${coverage.percentage}% coverage (${parts.join(", ")} of ${coverage.total} total)`);
856
+ }
729
857
  function resolveBaseRef() {
730
858
  const candidates = [
731
859
  "origin/main",
@@ -863,12 +991,12 @@ function createVerificationPlan(scripts) {
863
991
  };
864
992
  }
865
993
  function readPackageScripts(cwd = process.cwd()) {
866
- const packageJsonPath = join(cwd, "package.json");
867
- if (!existsSync(packageJsonPath)) {
994
+ const packageJsonPath = join2(cwd, "package.json");
995
+ if (!existsSync2(packageJsonPath)) {
868
996
  return {};
869
997
  }
870
998
  try {
871
- const parsed = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
999
+ const parsed = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
872
1000
  const scripts = parsed.scripts ?? {};
873
1001
  const normalized = {};
874
1002
  for (const [name, value] of Object.entries(scripts)) {
@@ -893,6 +1021,11 @@ async function runPrePush(options = {}) {
893
1021
  log(`${COLORS5.blue}Pre-push verify${COLORS5.reset} (TDD-aware)`);
894
1022
  log("");
895
1023
  const branch = getCurrentBranch();
1024
+ if (isReleaseBranch(branch)) {
1025
+ log(`${COLORS5.yellow}Release branch detected${COLORS5.reset}: ${COLORS5.blue}${branch}${COLORS5.reset}`);
1026
+ log(`${COLORS5.yellow}Skipping strict verification${COLORS5.reset} for automated changeset release push`);
1027
+ process.exit(0);
1028
+ }
896
1029
  if (isRedPhaseBranch(branch)) {
897
1030
  if (maybeSkipForRedPhase("branch", branch)) {
898
1031
  process.exit(0);
@@ -938,14 +1071,20 @@ async function runPrePush(options = {}) {
938
1071
  log(" - feature_tests");
939
1072
  process.exit(1);
940
1073
  }
1074
+ const changedFiles = getChangedFilesForPush();
1075
+ if (hasPackageSourceChanges(changedFiles)) {
1076
+ try {
1077
+ await printTsdocSummary();
1078
+ } catch {}
1079
+ }
941
1080
  log("");
942
1081
  log(`${COLORS5.green}Strict verification passed${COLORS5.reset}`);
943
1082
  process.exit(0);
944
1083
  }
945
1084
 
946
1085
  // src/cli/upgrade-bun.ts
947
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
948
- import { join as join2 } from "node:path";
1086
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "node:fs";
1087
+ import { join as join3 } from "node:path";
949
1088
  var COLORS6 = {
950
1089
  reset: "\x1B[0m",
951
1090
  red: "\x1B[31m",
@@ -983,13 +1122,13 @@ function findPackageJsonFiles(dir) {
983
1122
  const glob = new Bun.Glob("**/package.json");
984
1123
  for (const path of glob.scanSync({ cwd: dir })) {
985
1124
  if (!path.includes("node_modules")) {
986
- results.push(join2(dir, path));
1125
+ results.push(join3(dir, path));
987
1126
  }
988
1127
  }
989
1128
  return results;
990
1129
  }
991
1130
  function updateEnginesBun(filePath, version) {
992
- const content = readFileSync2(filePath, "utf-8");
1131
+ const content = readFileSync3(filePath, "utf-8");
993
1132
  const pattern = /"bun":\s*">=[\d.]+"/;
994
1133
  if (!pattern.test(content)) {
995
1134
  return false;
@@ -1002,7 +1141,7 @@ function updateEnginesBun(filePath, version) {
1002
1141
  return false;
1003
1142
  }
1004
1143
  function updateTypesBun(filePath, version) {
1005
- const content = readFileSync2(filePath, "utf-8");
1144
+ const content = readFileSync3(filePath, "utf-8");
1006
1145
  const pattern = /"@types\/bun":\s*"\^[\d.]+"/;
1007
1146
  if (!pattern.test(content)) {
1008
1147
  return false;
@@ -1016,14 +1155,14 @@ function updateTypesBun(filePath, version) {
1016
1155
  }
1017
1156
  async function runUpgradeBun(targetVersion, options = {}) {
1018
1157
  const cwd = process.cwd();
1019
- const bunVersionFile = join2(cwd, ".bun-version");
1158
+ const bunVersionFile = join3(cwd, ".bun-version");
1020
1159
  let version = targetVersion;
1021
1160
  if (!version) {
1022
1161
  info("Fetching latest Bun version...");
1023
1162
  version = await fetchLatestVersion();
1024
1163
  log2(`Latest version: ${version}`);
1025
1164
  }
1026
- const currentVersion = existsSync2(bunVersionFile) ? readFileSync2(bunVersionFile, "utf-8").trim() : "unknown";
1165
+ const currentVersion = existsSync3(bunVersionFile) ? readFileSync3(bunVersionFile, "utf-8").trim() : "unknown";
1027
1166
  log2(`Current version: ${currentVersion}`);
1028
1167
  if (currentVersion === version) {
1029
1168
  success(`Already on version ${version}`);
@@ -1088,40 +1227,58 @@ async function runUpgradeBun(targetVersion, options = {}) {
1088
1227
  }
1089
1228
 
1090
1229
  // src/cli/index.ts
1091
- var program = new Command;
1092
- program.name("tooling").description("Dev tooling configuration management for Outfitter projects").version(VERSION);
1093
- program.command("init").description("Initialize tooling config in the current project").action(async () => {
1094
- await runInit();
1230
+ var cli = createCLI({
1231
+ name: "tooling",
1232
+ version: VERSION,
1233
+ description: "Dev tooling configuration management for Outfitter projects",
1234
+ onError: (error) => {
1235
+ const err = error instanceof Error ? error : new Error("An unexpected error occurred");
1236
+ exitWithError(err);
1237
+ }
1095
1238
  });
1096
- program.command("check").description("Run linting checks (wraps ultracite)").argument("[paths...]", "Paths to check").action(async (paths) => {
1239
+ function register(command) {
1240
+ cli.register(command);
1241
+ }
1242
+ register(new Command("init").description("Initialize tooling config in the current project").action(async () => {
1243
+ await runInit();
1244
+ }));
1245
+ register(new Command("check").description("Run linting checks (wraps ultracite)").argument("[paths...]", "Paths to check").action(async (paths) => {
1097
1246
  await runCheck(paths);
1098
- });
1099
- program.command("fix").description("Fix linting issues (wraps ultracite)").argument("[paths...]", "Paths to fix").action(async (paths) => {
1247
+ }));
1248
+ register(new Command("fix").description("Fix linting issues (wraps ultracite)").argument("[paths...]", "Paths to fix").action(async (paths) => {
1100
1249
  await runFix(paths);
1101
- });
1102
- program.command("upgrade-bun").description("Upgrade Bun version across the project").argument("[version]", "Target version (defaults to latest)").option("--no-install", "Skip installing Bun and updating lockfile").action(async (version, options) => {
1250
+ }));
1251
+ register(new Command("upgrade-bun").description("Upgrade Bun version across the project").argument("[version]", "Target version (defaults to latest)").option("--no-install", "Skip installing Bun and updating lockfile").action(async (version, options) => {
1103
1252
  await runUpgradeBun(version, options);
1104
- });
1105
- program.command("pre-push").description("TDD-aware pre-push strict verification hook").option("-f, --force", "Skip strict verification entirely").action(async (options) => {
1253
+ }));
1254
+ register(new Command("pre-push").description("TDD-aware pre-push strict verification hook").option("-f, --force", "Skip strict verification entirely").action(async (options) => {
1106
1255
  await runPrePush(options);
1107
- });
1108
- program.command("check-bunup-registry").description("Validate packages with bunup --filter are registered in bunup.config.ts").action(async () => {
1256
+ }));
1257
+ register(new Command("check-bunup-registry").description("Validate packages with bunup --filter are registered in bunup.config.ts").action(async () => {
1109
1258
  await runCheckBunupRegistry();
1110
- });
1111
- program.command("check-changeset").description("Validate PRs touching package source include a changeset").option("-s, --skip", "Skip changeset check").action(async (options) => {
1259
+ }));
1260
+ register(new Command("check-changeset").description("Validate PRs touching package source include a changeset").option("-s, --skip", "Skip changeset check").action(async (options) => {
1112
1261
  await runCheckChangeset(options);
1113
- });
1114
- program.command("check-exports").description("Validate package.json exports match source entry points").option("--json", "Output results as JSON").action(async (options) => {
1262
+ }));
1263
+ register(new Command("check-exports").description("Validate package.json exports match source entry points").option("--json", "Output results as JSON").action(async (options) => {
1115
1264
  await runCheckExports(options);
1116
- });
1117
- program.command("check-clean-tree").description("Assert working tree is clean (no modified or untracked files)").option("--paths <paths...>", "Limit check to specific paths").action(async (options) => {
1265
+ }));
1266
+ register(new Command("check-tsdoc").description("Check TSDoc coverage on exported declarations").argument("[paths...]", "Specific package paths to check").option("--strict", "Exit non-zero when coverage is below threshold").option("--json", "Output results as JSON").option("--min-coverage <percentage>", "Minimum coverage percentage", "0").action(async (paths, options) => {
1267
+ await runCheckTsdoc({
1268
+ paths: paths.length > 0 ? paths : undefined,
1269
+ strict: options.strict,
1270
+ json: options.json,
1271
+ minCoverage: options.minCoverage ? Number.parseInt(options.minCoverage, 10) : undefined
1272
+ });
1273
+ }));
1274
+ register(new Command("check-clean-tree").description("Assert working tree is clean (no modified or untracked files)").option("--paths <paths...>", "Limit check to specific paths").action(async (options) => {
1118
1275
  await runCheckCleanTree(options);
1119
- });
1120
- program.command("check-readme-imports").description("Validate README import examples match package exports").option("--json", "Output results as JSON").action(async (options) => {
1121
- const { runCheckReadmeImports } = await import("../shared/chunk-6a7tjcgm.js");
1276
+ }));
1277
+ register(new Command("check-readme-imports").description("Validate README import examples match package exports").option("--json", "Output results as JSON").action(async (options) => {
1278
+ const { runCheckReadmeImports } = await import("../shared/chunk-7tdgbqb0.js");
1122
1279
  await runCheckReadmeImports(options);
1123
- });
1124
- program.command("check-boundary-invocations").description("Validate root/app scripts do not execute packages/*/src entrypoints directly").action(async () => {
1280
+ }));
1281
+ register(new Command("check-boundary-invocations").description("Validate root/app scripts do not execute packages/*/src entrypoints directly").action(async () => {
1125
1282
  await runCheckBoundaryInvocations();
1126
- });
1127
- program.parse();
1283
+ }));
1284
+ await cli.parse();
@@ -6,6 +6,7 @@ declare function isRedPhaseBranch(branch: string): boolean;
6
6
  * Check if branch is a scaffold branch
7
7
  */
8
8
  declare function isScaffoldBranch(branch: string): boolean;
9
+ declare function isReleaseBranch(branch: string): boolean;
9
10
  declare function isTestOnlyPath(path: string): boolean;
10
11
  declare function areFilesTestOnly(paths: readonly string[]): boolean;
11
12
  interface PushChangedFiles {
@@ -14,6 +15,12 @@ interface PushChangedFiles {
14
15
  readonly source: "upstream" | "baseRef" | "undetermined";
15
16
  }
16
17
  declare function canBypassRedPhaseByChangedFiles(changedFiles: PushChangedFiles): boolean;
18
+ /**
19
+ * Check whether any changed files are package source files.
20
+ *
21
+ * Matches files under "packages/PKGNAME/src/" (any depth).
22
+ */
23
+ declare function hasPackageSourceChanges(changedFiles: PushChangedFiles): boolean;
17
24
  type ScriptMap = Readonly<Record<string, string | undefined>>;
18
25
  type VerificationPlan = {
19
26
  readonly ok: true;
@@ -38,4 +45,4 @@ interface PrePushOptions {
38
45
  * Main pre-push command
39
46
  */
40
47
  declare function runPrePush(options?: PrePushOptions): Promise<void>;
41
- export { runPrePush, isTestOnlyPath, isScaffoldBranch, isRedPhaseBranch, createVerificationPlan, canBypassRedPhaseByChangedFiles, areFilesTestOnly, VerificationPlan, PushChangedFiles, PrePushOptions };
48
+ export { runPrePush, isTestOnlyPath, isScaffoldBranch, isReleaseBranch, isRedPhaseBranch, hasPackageSourceChanges, createVerificationPlan, canBypassRedPhaseByChangedFiles, areFilesTestOnly, VerificationPlan, PushChangedFiles, PrePushOptions };
@@ -3,17 +3,22 @@ import {
3
3
  areFilesTestOnly,
4
4
  canBypassRedPhaseByChangedFiles,
5
5
  createVerificationPlan,
6
+ hasPackageSourceChanges,
6
7
  isRedPhaseBranch,
8
+ isReleaseBranch,
7
9
  isScaffoldBranch,
8
10
  isTestOnlyPath,
9
11
  runPrePush
10
- } from "../shared/@outfitter/tooling-8sd32ts6.js";
12
+ } from "../shared/@outfitter/tooling-2n2dpsaa.js";
13
+ import"../shared/@outfitter/tooling-qk5xgmxr.js";
11
14
  import"../shared/@outfitter/tooling-dvwh9qve.js";
12
15
  export {
13
16
  runPrePush,
14
17
  isTestOnlyPath,
15
18
  isScaffoldBranch,
19
+ isReleaseBranch,
16
20
  isRedPhaseBranch,
21
+ hasPackageSourceChanges,
17
22
  createVerificationPlan,
18
23
  canBypassRedPhaseByChangedFiles,
19
24
  areFilesTestOnly
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: ZodType<FileEntry>;
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: ZodType<Block>;
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: ZodType<Registry>;
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
- } from "./shared/chunk-8aenrm6f.js";
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,
@@ -1,4 +1,4 @@
1
- import { RegistryBuildConfig } from "../shared/@outfitter/tooling-sjm8nebx";
1
+ import { RegistryBuildConfig } from "../shared/@outfitter/tooling-sjm8nebx.js";
2
2
  /**
3
3
  * Registry build configuration
4
4
  */
@@ -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.1.1" }
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.1",
104
- lefthook: "^2.0.16",
105
- ultracite: "^7.1.1"
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: ["scripts/bootstrap.sh"]
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",
@@ -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 };
@@ -4,7 +4,7 @@ import {
4
4
  BlockSchema,
5
5
  FileEntrySchema,
6
6
  RegistrySchema
7
- } from "../shared/@outfitter/tooling-g83d0kjv.js";
7
+ } from "../shared/@outfitter/tooling-wv09k6hr.js";
8
8
  import"../shared/@outfitter/tooling-dvwh9qve.js";
9
9
  export {
10
10
  RegistrySchema,