@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.
Files changed (50) hide show
  1. package/README.md +12 -3
  2. package/dist/cli/check-changeset.d.ts +28 -12
  3. package/dist/cli/check-changeset.js +5 -1
  4. package/dist/cli/check-exports.d.ts +2 -1
  5. package/dist/cli/check-exports.js +6 -3
  6. package/dist/cli/check-home-paths.d.ts +31 -0
  7. package/dist/cli/check-home-paths.js +12 -0
  8. package/dist/cli/check-readme-imports.d.ts +2 -1
  9. package/dist/cli/check-tsdoc.d.ts +4 -1
  10. package/dist/cli/check-tsdoc.js +16 -10
  11. package/dist/cli/index.js +16 -4
  12. package/dist/cli/internal/exports-analysis.d.ts +2 -0
  13. package/dist/cli/internal/exports-analysis.js +10 -0
  14. package/dist/cli/internal/exports-fs.d.ts +17 -0
  15. package/dist/cli/internal/exports-fs.js +9 -0
  16. package/dist/cli/internal/pre-push-checks.d.ts +2 -0
  17. package/dist/cli/internal/pre-push-checks.js +37 -0
  18. package/dist/cli/internal/tsdoc-analysis.d.ts +3 -0
  19. package/dist/cli/internal/tsdoc-analysis.js +26 -0
  20. package/dist/cli/internal/tsdoc-formatting.d.ts +3 -0
  21. package/dist/cli/internal/tsdoc-formatting.js +10 -0
  22. package/dist/cli/internal/tsdoc-types.d.ts +2 -0
  23. package/dist/cli/internal/tsdoc-types.js +16 -0
  24. package/dist/cli/pre-push.d.ts +2 -55
  25. package/dist/cli/pre-push.js +6 -4
  26. package/dist/index.d.ts +4 -1
  27. package/dist/shared/@outfitter/tooling-0zjz8eg9.js +106 -0
  28. package/dist/shared/@outfitter/tooling-2vv5y3s4.js +145 -0
  29. package/dist/shared/@outfitter/{tooling-875svjnz.js → tooling-5xxctk9b.js} +2 -113
  30. package/dist/shared/@outfitter/tooling-5ynz680q.js +59 -0
  31. package/dist/shared/@outfitter/tooling-7437rmy6.js +39 -0
  32. package/dist/shared/@outfitter/tooling-8qcwr06t.d.ts +74 -0
  33. package/dist/shared/@outfitter/tooling-a59br34g.js +32 -0
  34. package/dist/shared/@outfitter/tooling-a6q3zh7t.js +86 -0
  35. package/dist/shared/@outfitter/tooling-ayps7c4x.js +58 -0
  36. package/dist/shared/@outfitter/{tooling-d363b88r.js → tooling-c8q6mj8z.js} +27 -148
  37. package/dist/shared/@outfitter/{tooling-wesswf21.d.ts → tooling-cb0b8wsx.d.ts} +9 -11
  38. package/dist/shared/@outfitter/tooling-f8q38e9z.d.ts +16 -0
  39. package/dist/shared/@outfitter/tooling-h5dnevjw.js +139 -0
  40. package/dist/shared/@outfitter/tooling-j8d1h2zd.d.ts +10 -0
  41. package/dist/shared/@outfitter/tooling-mq2xvz96.js +285 -0
  42. package/dist/shared/@outfitter/tooling-stgnc2zx.d.ts +85 -0
  43. package/dist/shared/@outfitter/tooling-tj9p41vj.d.ts +55 -0
  44. package/dist/shared/@outfitter/tooling-y43b117h.d.ts +13 -0
  45. package/lefthook.yml +5 -1
  46. package/package.json +10 -4
  47. package/registry/registry.json +5 -5
  48. package/dist/shared/@outfitter/tooling-6cxfdx0q.js +0 -187
  49. package/dist/shared/@outfitter/tooling-h04te11c.js +0 -231
  50. package/dist/shared/@outfitter/tooling-njw4z34x.d.ts +0 -140
@@ -0,0 +1,285 @@
1
+ // @bun
2
+ // packages/tooling/src/cli/check-changeset.ts
3
+ import { existsSync, readFileSync } from "fs";
4
+ import { join } from "path";
5
+ function toWorkspacePackageName(packageName) {
6
+ return packageName.startsWith("@outfitter/") ? packageName : `@outfitter/${packageName}`;
7
+ }
8
+ function getReleasableChangedPackages(changedPackages, ignoredPackages) {
9
+ const ignored = new Set(ignoredPackages.map(toWorkspacePackageName));
10
+ return changedPackages.filter((packageName) => !ignored.has(toWorkspacePackageName(packageName)));
11
+ }
12
+ function getChangedPackagePaths(files) {
13
+ const packageNames = new Set;
14
+ const pattern = /^packages\/([^/]+)\/src\/(.+)$/;
15
+ for (const file of files) {
16
+ const match = pattern.exec(file);
17
+ if (match?.[1] && match[2] && isReleaseRelevantSourcePath(match[2])) {
18
+ packageNames.add(match[1]);
19
+ }
20
+ }
21
+ return [...packageNames].toSorted();
22
+ }
23
+ function getChangedChangesetFiles(files) {
24
+ const pattern = /^\.changeset\/([^/]+\.md)$/;
25
+ const results = [];
26
+ for (const file of files) {
27
+ const match = pattern.exec(file);
28
+ if (match?.[1] && match[1] !== "README.md") {
29
+ results.push(match[1]);
30
+ }
31
+ }
32
+ return results.toSorted();
33
+ }
34
+ function checkChangesetRequired(releasablePackages, changesetFiles, coveredPackages = []) {
35
+ if (releasablePackages.length === 0) {
36
+ return { ok: true, missingFor: [] };
37
+ }
38
+ if (changesetFiles.length === 0) {
39
+ return { ok: false, missingFor: releasablePackages };
40
+ }
41
+ const covered = new Set(coveredPackages);
42
+ const missingFor = releasablePackages.filter((packageName) => !covered.has(toWorkspacePackageName(packageName)));
43
+ return missingFor.length === 0 ? { ok: true, missingFor: [] } : { ok: false, missingFor };
44
+ }
45
+ function parseIgnoredPackagesFromChangesetConfig(jsonContent) {
46
+ try {
47
+ const parsed = JSON.parse(jsonContent);
48
+ if (!Array.isArray(parsed.ignore)) {
49
+ return [];
50
+ }
51
+ return parsed.ignore.filter((entry) => typeof entry === "string");
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+ function parseChangesetFrontmatterPackageNames(markdownContent) {
57
+ const frontmatterMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdownContent);
58
+ if (!frontmatterMatch?.[1]) {
59
+ return [];
60
+ }
61
+ const packages = new Set;
62
+ for (const line of frontmatterMatch[1].split(/\r?\n/)) {
63
+ const trimmed = line.trim();
64
+ const match = /^(["']?)(@[^"':\s]+\/[^"':\s]+)\1\s*:/.exec(trimmed);
65
+ if (match?.[2]) {
66
+ packages.add(match[2]);
67
+ }
68
+ }
69
+ return [...packages].toSorted();
70
+ }
71
+ function isReleaseRelevantSourcePath(relativePath) {
72
+ if (/(^|\/)__tests__\//.test(relativePath)) {
73
+ return false;
74
+ }
75
+ if (/(^|\/)__snapshots__\//.test(relativePath)) {
76
+ return false;
77
+ }
78
+ if (/\.(test|spec)\.[^/]+$/.test(relativePath)) {
79
+ return false;
80
+ }
81
+ return true;
82
+ }
83
+ function getDefaultGitDiffRange() {
84
+ return {
85
+ base: "origin/main",
86
+ head: "HEAD",
87
+ label: "origin/main...HEAD",
88
+ source: "default"
89
+ };
90
+ }
91
+ function resolveGitDiffRange(input) {
92
+ if (input.eventName !== "pull_request" || !input.eventPath) {
93
+ return getDefaultGitDiffRange();
94
+ }
95
+ const readEventFile = input.readEventFile ?? ((path) => readFileSync(path, "utf-8"));
96
+ try {
97
+ const payload = JSON.parse(readEventFile(input.eventPath));
98
+ const baseRef = payload.pull_request?.base?.ref;
99
+ const baseSha = payload.pull_request?.base?.sha;
100
+ const headRef = payload.pull_request?.head?.ref;
101
+ const headSha = payload.pull_request?.head?.sha;
102
+ if (!baseSha || !headSha) {
103
+ return getDefaultGitDiffRange();
104
+ }
105
+ return {
106
+ base: baseSha,
107
+ head: headSha,
108
+ label: `${baseRef ?? "base"} (${baseSha})...${headRef ?? "head"} (${headSha})`,
109
+ source: "pull_request"
110
+ };
111
+ } catch {
112
+ return getDefaultGitDiffRange();
113
+ }
114
+ }
115
+ function analyzeChangesetReferences(referencedPackages, ignored) {
116
+ const coveredPackages = referencedPackages.filter((packageName) => !ignored.has(packageName));
117
+ const ignoredReferences = referencedPackages.filter((packageName) => ignored.has(packageName));
118
+ return {
119
+ coveredPackages: coveredPackages.toSorted(),
120
+ ignoredReferences: ignoredReferences.toSorted()
121
+ };
122
+ }
123
+ function findIgnoredPackageReferences(input) {
124
+ if (input.ignoredPackages.length === 0 || input.changesetFiles.length === 0) {
125
+ return [];
126
+ }
127
+ const ignored = new Set(input.ignoredPackages);
128
+ const results = [];
129
+ for (const file of input.changesetFiles) {
130
+ const content = input.readChangesetFile(file);
131
+ const referencedPackages = parseChangesetFrontmatterPackageNames(content);
132
+ const invalidReferences = analyzeChangesetReferences(referencedPackages, ignored).ignoredReferences;
133
+ if (invalidReferences.length > 0) {
134
+ results.push({ file, packages: invalidReferences.toSorted() });
135
+ }
136
+ }
137
+ return results.toSorted((a, b) => a.file.localeCompare(b.file));
138
+ }
139
+ function loadIgnoredPackages(cwd) {
140
+ const configPath = join(cwd, ".changeset", "config.json");
141
+ if (!existsSync(configPath)) {
142
+ return [];
143
+ }
144
+ try {
145
+ return parseIgnoredPackagesFromChangesetConfig(readFileSync(configPath, "utf-8"));
146
+ } catch {
147
+ return [];
148
+ }
149
+ }
150
+ function analyzeChangedChangesets(cwd, changesetFiles, ignoredPackages) {
151
+ const covered = new Set;
152
+ const ignored = new Set(ignoredPackages);
153
+ const ignoredReferences = [];
154
+ for (const filename of changesetFiles) {
155
+ try {
156
+ const content = readFileSync(join(cwd, ".changeset", filename), "utf-8");
157
+ const referencedPackages = parseChangesetFrontmatterPackageNames(content);
158
+ const analysis = analyzeChangesetReferences(referencedPackages, ignored);
159
+ for (const packageName of analysis.coveredPackages) {
160
+ covered.add(packageName);
161
+ }
162
+ if (analysis.ignoredReferences.length > 0) {
163
+ ignoredReferences.push({
164
+ file: filename,
165
+ packages: analysis.ignoredReferences
166
+ });
167
+ }
168
+ } catch {
169
+ continue;
170
+ }
171
+ }
172
+ return {
173
+ coveredPackages: [...covered].toSorted(),
174
+ ignoredReferences: ignoredReferences.toSorted((a, b) => a.file.localeCompare(b.file))
175
+ };
176
+ }
177
+ var COLORS = {
178
+ reset: "\x1B[0m",
179
+ red: "\x1B[31m",
180
+ green: "\x1B[32m",
181
+ yellow: "\x1B[33m",
182
+ blue: "\x1B[34m",
183
+ dim: "\x1B[2m"
184
+ };
185
+ async function runCheckChangeset(options = {}) {
186
+ if (options.skip || process.env["NO_CHANGESET"] === "1") {
187
+ process.stdout.write(`${COLORS.dim}check-changeset skipped (NO_CHANGESET=1)${COLORS.reset}
188
+ `);
189
+ process.exitCode = 0;
190
+ return;
191
+ }
192
+ if (process.env["GITHUB_EVENT_NAME"] === "push") {
193
+ process.stdout.write(`${COLORS.dim}check-changeset skipped (push event)${COLORS.reset}
194
+ `);
195
+ process.exitCode = 0;
196
+ return;
197
+ }
198
+ const cwd = process.cwd();
199
+ const diffRange = resolveGitDiffRange({
200
+ eventName: process.env["GITHUB_EVENT_NAME"],
201
+ eventPath: process.env["GITHUB_EVENT_PATH"],
202
+ readEventFile: (path) => readFileSync(path, "utf-8")
203
+ });
204
+ let changedFiles;
205
+ try {
206
+ const proc = Bun.spawnSync(["git", "diff", "--name-only", `${diffRange.base}...${diffRange.head}`], { cwd });
207
+ if (proc.exitCode !== 0) {
208
+ process.exitCode = 0;
209
+ return;
210
+ }
211
+ changedFiles = proc.stdout.toString().trim().split(`
212
+ `).filter((line) => line.length > 0);
213
+ } catch {
214
+ process.exitCode = 0;
215
+ return;
216
+ }
217
+ if (process.env["GITHUB_EVENT_NAME"]) {
218
+ process.stdout.write(`${COLORS.dim}check-changeset diff basis: ${diffRange.label}${COLORS.reset}
219
+ `);
220
+ }
221
+ const changedPackages = getChangedPackagePaths(changedFiles);
222
+ if (changedPackages.length === 0) {
223
+ process.stdout.write(`${COLORS.green}No package source changes detected.${COLORS.reset}
224
+ `);
225
+ process.exitCode = 0;
226
+ return;
227
+ }
228
+ const changesetFiles = getChangedChangesetFiles(changedFiles);
229
+ const ignoredPackages = loadIgnoredPackages(cwd);
230
+ const releasablePackages = getReleasableChangedPackages(changedPackages, ignoredPackages);
231
+ const changesetAnalysis = analyzeChangedChangesets(cwd, changesetFiles, ignoredPackages);
232
+ const check = checkChangesetRequired(releasablePackages, changesetFiles, changesetAnalysis.coveredPackages);
233
+ const ignoredReferences = changesetAnalysis.ignoredReferences;
234
+ let hasErrors = false;
235
+ if (!check.ok) {
236
+ process.stderr.write(`${COLORS.red}Changeset coverage missing.${COLORS.reset} ` + `Every changed releasable package must be mentioned in at least one current PR changeset.
237
+
238
+ `);
239
+ process.stderr.write(`Packages missing changeset coverage:
240
+
241
+ `);
242
+ for (const pkg of check.missingFor) {
243
+ process.stderr.write(` ${COLORS.yellow}${toWorkspacePackageName(pkg)}${COLORS.reset}
244
+ `);
245
+ }
246
+ process.stderr.write(`
247
+ Run ${COLORS.blue}bun run changeset${COLORS.reset} for a custom changelog entry, ` + `or add ${COLORS.blue}release:none${COLORS.reset} to skip.
248
+ `);
249
+ hasErrors = true;
250
+ }
251
+ if (ignoredReferences.length > 0) {
252
+ process.stderr.write(`${COLORS.red}Invalid changeset package reference(s).${COLORS.reset}
253
+
254
+ `);
255
+ process.stderr.write(`Changesets must not reference packages listed in .changeset/config.json ignore:
256
+
257
+ `);
258
+ for (const reference of ignoredReferences) {
259
+ process.stderr.write(` ${COLORS.yellow}${reference.file}${COLORS.reset}
260
+ `);
261
+ for (const pkg of reference.packages) {
262
+ process.stderr.write(` - ${pkg}
263
+ `);
264
+ }
265
+ }
266
+ process.stderr.write(`
267
+ Update the affected changeset files to remove ignored packages before merging.
268
+ `);
269
+ hasErrors = true;
270
+ }
271
+ if (hasErrors) {
272
+ process.exitCode = 1;
273
+ return;
274
+ }
275
+ if (releasablePackages.length === 0) {
276
+ process.stdout.write(`${COLORS.green}Only ignored package source changes detected.${COLORS.reset}
277
+ `);
278
+ } else {
279
+ process.stdout.write(`${COLORS.green}Changesets cover ${releasablePackages.length} changed package(s).${COLORS.reset}
280
+ `);
281
+ }
282
+ process.exitCode = 0;
283
+ }
284
+
285
+ export { getReleasableChangedPackages, getChangedPackagePaths, getChangedChangesetFiles, checkChangesetRequired, parseIgnoredPackagesFromChangesetConfig, parseChangesetFrontmatterPackageNames, resolveGitDiffRange, findIgnoredPackageReferences, runCheckChangeset };
@@ -0,0 +1,85 @@
1
+ import { CheckTsDocOptions, CoverageLevel, DeclarationCoverage, PackageCoverage, TsDocCheckResult } from "./tooling-tj9p41vj.js";
2
+ import ts from "typescript";
3
+ /**
4
+ * Check whether a node is an exported declaration worth checking.
5
+ *
6
+ * Returns true for function, interface, type alias, class, enum, and variable
7
+ * declarations that carry the `export` keyword. Re-exports (`{ ... } from`)
8
+ * and `*` are excluded since TSDoc belongs at the definition site.
9
+ */
10
+ declare function isExportedDeclaration(node: ts.Node): boolean;
11
+ /**
12
+ * Extract the name of a declaration node.
13
+ *
14
+ * For variable statements, returns the name of the first variable declarator.
15
+ * Returns `undefined` for anonymous declarations (e.g., `function() {}`).
16
+ */
17
+ declare function getDeclarationName(node: ts.Node): string | undefined;
18
+ /**
19
+ * Determine the kind label for a declaration node.
20
+ *
21
+ * Maps AST node types to human-readable kind strings used in coverage reports.
22
+ */
23
+ declare function getDeclarationKind(node: ts.Node): string;
24
+ /**
25
+ * Classify a declaration's TSDoc coverage level.
26
+ *
27
+ * - `"documented"` -- has a JSDoc comment with a description. For interfaces
28
+ * and classes, all members must also have JSDoc comments.
29
+ * - `"partial"` -- the declaration has a JSDoc comment but some members
30
+ * (in interfaces/classes) lack documentation.
31
+ * - `"undocumented"` -- no JSDoc comment at all.
32
+ */
33
+ declare function classifyDeclaration(node: ts.Node, sourceFile: ts.SourceFile): CoverageLevel;
34
+ /**
35
+ * Analyze all exported declarations in a source file for TSDoc coverage.
36
+ *
37
+ * Walks top-level statements, filters to exported declarations, and
38
+ * classifies each for documentation coverage.
39
+ */
40
+ declare function analyzeSourceFile(sourceFile: ts.SourceFile): DeclarationCoverage[];
41
+ /**
42
+ * Calculate aggregate coverage statistics from declaration results.
43
+ *
44
+ * Partial documentation counts as half coverage in the percentage calculation.
45
+ * An empty array returns 100% (no declarations to check).
46
+ */
47
+ declare function calculateCoverage(declarations: readonly DeclarationCoverage[]): {
48
+ documented: number;
49
+ partial: number;
50
+ undocumented: number;
51
+ total: number;
52
+ percentage: number;
53
+ };
54
+ /** Discover packages with src/index.ts entry points. */
55
+ declare function discoverPackages(cwd: string): Array<{
56
+ name: string;
57
+ path: string;
58
+ entryPoint: string;
59
+ }>;
60
+ /**
61
+ * Collect source files referenced by re-exports in a barrel file.
62
+ *
63
+ * For `{ ... } from "./foo"` and `* from "./bar"`, resolves
64
+ * the module specifier to a source file in the program. Returns only files
65
+ * within the package (skips external modules).
66
+ */
67
+ declare function collectReExportedSourceFiles(sourceFile: ts.SourceFile, program: ts.Program, pkgPath: string): ts.SourceFile[];
68
+ /** Analyze a single package entry point, returning coverage data. */
69
+ declare function analyzePackage(pkg: {
70
+ name: string;
71
+ path: string;
72
+ entryPoint: string;
73
+ }, workspaceCwd: string): PackageCoverage;
74
+ /**
75
+ * Analyze TSDoc coverage across workspace packages.
76
+ *
77
+ * Pure function that discovers packages, analyzes TSDoc coverage on exported
78
+ * declarations, and returns the aggregated result. Does not print output or
79
+ * call `process.exit()`.
80
+ *
81
+ * @param options - Analysis options (paths, strict mode, coverage threshold)
82
+ * @returns Aggregated coverage result across all packages, or `null` if no packages found
83
+ */
84
+ declare function analyzeCheckTsdoc(options?: CheckTsDocOptions): TsDocCheckResult | null;
85
+ export { isExportedDeclaration, getDeclarationName, getDeclarationKind, classifyDeclaration, analyzeSourceFile, calculateCoverage, discoverPackages, collectReExportedSourceFiles, analyzePackage, analyzeCheckTsdoc };
@@ -0,0 +1,55 @@
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 CoverageSummary}. */
42
+ declare const coverageSummarySchema: ZodType<CoverageSummary>;
43
+ /** Zod schema for {@link PackageCoverage}. */
44
+ declare const packageCoverageSchema: ZodType<PackageCoverage>;
45
+ /** Zod schema for {@link TsDocCheckResult}. */
46
+ declare const tsDocCheckResultSchema: ZodType<TsDocCheckResult>;
47
+ /** Options for the check-tsdoc command. */
48
+ interface CheckTsDocOptions {
49
+ readonly strict?: boolean | undefined;
50
+ readonly json?: boolean | undefined;
51
+ readonly minCoverage?: number | undefined;
52
+ readonly cwd?: string | undefined;
53
+ readonly paths?: readonly string[] | undefined;
54
+ }
55
+ export { CoverageLevel, DeclarationCoverage, CoverageSummary, PackageCoverage, TsDocCheckResult, coverageLevelSchema, declarationCoverageSchema, coverageSummarySchema, packageCoverageSchema, tsDocCheckResultSchema, CheckTsDocOptions };
@@ -0,0 +1,13 @@
1
+ /** Options for the check-exports command */
2
+ interface CheckExportsOptions {
3
+ readonly json?: boolean;
4
+ }
5
+ /** Resolve whether to output JSON based on options and env */
6
+ declare function resolveJsonMode(options?: CheckExportsOptions): boolean;
7
+ /**
8
+ * Run check-exports across all workspace packages.
9
+ *
10
+ * Reads the bunup workspace config to discover packages and their * settings, then compares expected vs actual exports in each package.json.
11
+ */
12
+ declare function runCheckExports(options?: CheckExportsOptions): Promise<void>;
13
+ export { CheckExportsOptions, resolveJsonMode, runCheckExports };
package/lefthook.yml CHANGED
@@ -13,6 +13,10 @@ pre-commit:
13
13
  run: bun x ultracite fix {staged_files}
14
14
  stage_fixed: true
15
15
 
16
+ home-paths:
17
+ glob: "*.{js,jsx,json,jsonc,css,md,ts,tsx,yaml,yml}"
18
+ run: bun x @outfitter/tooling check-home-paths {staged_files}
19
+
16
20
  typecheck:
17
21
  glob: "*.{ts,tsx}"
18
22
  run: bun run typecheck
@@ -25,4 +29,4 @@ pre-push:
25
29
  # (*-tests, */tests, *_tests). Otherwise runs verify:ci (or a strict
26
30
  # fallback: typecheck/check-or-lint/build/test).
27
31
  # Requires: @outfitter/tooling as a dev dependency.
28
- run: bunx @outfitter/tooling pre-push
32
+ run: bun x @outfitter/tooling pre-push
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 (oxlint/oxfmt, typescript, lefthook, markdownlint)",
4
- "version": "0.3.4",
4
+ "version": "0.3.5",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -28,6 +28,12 @@
28
28
  "default": "./dist/cli/check.js"
29
29
  }
30
30
  },
31
+ "./cli/check-home-paths": {
32
+ "import": {
33
+ "types": "./dist/cli/check-home-paths.d.ts",
34
+ "default": "./dist/cli/check-home-paths.js"
35
+ }
36
+ },
31
37
  "./cli/check-markdown-links": {
32
38
  "import": {
33
39
  "types": "./dist/cli/check-markdown-links.d.ts",
@@ -74,7 +80,7 @@
74
80
  "build:registry": "bun run src/registry/build.ts",
75
81
  "sync:exports": "bun run scripts/sync-exports.ts",
76
82
  "sync:exports:check": "bun run scripts/sync-exports.ts --check",
77
- "build": "bun run build:registry && bun run sync:exports:check && cd ../.. && bunup --filter @outfitter/tooling",
83
+ "build": "bun run build:registry && bun run sync:exports:check && cd ../.. && bash ./scripts/run-bunup-with-lock.sh bunup --filter @outfitter/tooling",
78
84
  "prepack": "bun run sync:exports",
79
85
  "lint": "oxlint ./src",
80
86
  "lint:fix": "oxlint --fix ./src",
@@ -84,13 +90,13 @@
84
90
  "prepublishOnly": "bun ../../scripts/check-publish-manifest.ts"
85
91
  },
86
92
  "dependencies": {
87
- "@outfitter/cli": "0.5.3",
93
+ "@outfitter/cli": "1.0.0",
88
94
  "commander": "^14.0.2",
89
95
  "typescript": "^5.9.3",
90
96
  "zod": "^4.3.5"
91
97
  },
92
98
  "devDependencies": {
93
- "@outfitter/presets": "0.2.1",
99
+ "@outfitter/presets": "0.3.0",
94
100
  "@types/bun": "^1.3.9",
95
101
  "yaml": "^2.8.2"
96
102
  },
@@ -7,7 +7,7 @@
7
7
  "files": [
8
8
  {
9
9
  "path": ".claude/settings.json",
10
- "content": "{\n \"hooks\": {\n \"SessionStart\": [\n {\n \"matcher\": \"*\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"bash \\\"$CLAUDE_PROJECT_DIR/scripts/bootstrap.sh\\\"\",\n \"timeout\": 120\n }\n ]\n }\n ],\n \"Stop\": [\n {\n \"matcher\": \"*\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"bash \\\"$CLAUDE_PROJECT_DIR/.claude/hooks/format-code-on-stop.sh\\\"\",\n \"timeout\": 60\n }\n ]\n }\n ]\n },\n \"enabledPlugins\": {},\n \"extraKnownMarketplaces\": {}\n}\n"
10
+ "content": "{\n \"hooks\": {\n \"SessionStart\": [\n {\n \"matcher\": \"*\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"bash \\\"$CLAUDE_PROJECT_DIR/scripts/bootstrap.sh\\\"\",\n \"timeout\": 120\n }\n ]\n }\n ],\n \"Stop\": [\n {\n \"matcher\": \"*\",\n \"hooks\": [\n {\n \"type\": \"command\",\n \"command\": \"bash \\\"$CLAUDE_PROJECT_DIR/.claude/hooks/format-code-on-stop.sh\\\"\",\n \"timeout\": 60\n }\n ]\n }\n ]\n },\n \"enabledPlugins\": {\n \"outfitter@outfitter\": true,\n \"fieldguides@outfitter\": true\n },\n \"extraKnownMarketplaces\": {\n \"outfitter\": {\n \"source\": {\n \"source\": \"github\",\n \"repo\": \"outfitter-dev/outfitter\",\n \"sparsePaths\": [\".claude-plugin\", \"plugins\"]\n }\n }\n }\n}\n"
11
11
  },
12
12
  {
13
13
  "path": ".claude/hooks/format-code-on-stop.sh",
@@ -22,7 +22,7 @@
22
22
  "files": [
23
23
  {
24
24
  "path": ".oxlintrc.json",
25
- "content": "{\n \"$schema\": \"./node_modules/oxlint/configuration_schema.json\",\n \"plugins\": [\"eslint\"],\n \"jsPlugins\": [\n {\n \"name\": \"outfitter\",\n \"specifier\": \"@outfitter/oxlint-plugin\"\n }\n ],\n \"globals\": { \"Bun\": \"readonly\" },\n \"categories\": {\n \"correctness\": \"error\",\n \"suspicious\": \"error\",\n \"pedantic\": \"off\",\n \"perf\": \"off\",\n \"style\": \"off\",\n \"restriction\": \"off\"\n },\n \"ignorePatterns\": [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/.turbo/**\",\n \"**/*.gen.ts\"\n ],\n \"rules\": {\n \"no-void\": \"off\",\n \"outfitter/action-must-register\": \"warn\",\n \"outfitter/handler-must-return-result\": \"error\",\n \"outfitter/max-file-lines\": [\"error\", { \"warn\": 200, \"error\": 400 }],\n \"outfitter/no-console-in-packages\": \"error\",\n \"outfitter/no-cross-tier-import\": \"error\",\n \"outfitter/no-deep-relative-import\": \"warn\",\n \"outfitter/no-nested-barrel\": \"warn\",\n \"outfitter/no-process-env-in-packages\": \"warn\",\n \"outfitter/no-process-exit-in-packages\": \"error\",\n \"outfitter/no-throw-in-handler\": \"error\",\n \"outfitter/prefer-bun-api\": \"warn\",\n \"outfitter/snapshot-location\": \"warn\",\n \"outfitter/test-file-naming\": \"warn\",\n \"outfitter/use-error-taxonomy\": \"warn\"\n }\n}\n"
25
+ "content": "{\n \"$schema\": \"./node_modules/oxlint/configuration_schema.json\",\n \"plugins\": [\"eslint\"],\n \"jsPlugins\": [\n {\n \"name\": \"outfitter\",\n \"specifier\": \"@outfitter/oxlint-plugin\"\n }\n ],\n \"globals\": { \"Bun\": \"readonly\" },\n \"categories\": {\n \"correctness\": \"error\",\n \"suspicious\": \"error\",\n \"pedantic\": \"off\",\n \"perf\": \"off\",\n \"style\": \"off\",\n \"restriction\": \"off\"\n },\n \"ignorePatterns\": [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/.turbo/**\",\n \"**/*.gen.ts\",\n \"**/__tests__/fixtures/**\"\n ],\n \"rules\": {\n \"no-void\": \"off\",\n \"outfitter/action-must-register\": \"warn\",\n \"outfitter/handler-must-return-result\": \"error\",\n \"outfitter/max-file-lines\": [\"error\", { \"warn\": 200, \"error\": 400 }],\n \"outfitter/no-console-in-packages\": \"error\",\n \"outfitter/no-cross-tier-import\": \"error\",\n \"outfitter/no-deep-relative-import\": \"warn\",\n \"outfitter/no-nested-barrel\": \"warn\",\n \"outfitter/no-process-env-in-packages\": [\n \"warn\",\n { \"allowedPackages\": [\"config\"] }\n ],\n \"outfitter/no-process-exit-in-packages\": \"error\",\n \"outfitter/no-throw-in-handler\": \"error\",\n \"outfitter/prefer-bun-api\": \"warn\",\n \"outfitter/snapshot-location\": \"warn\",\n \"outfitter/test-file-naming\": \"warn\",\n \"outfitter/use-error-taxonomy\": \"warn\"\n }\n}\n"
26
26
  },
27
27
  {
28
28
  "path": ".oxfmtrc.jsonc",
@@ -30,7 +30,7 @@
30
30
  }
31
31
  ],
32
32
  "devDependencies": {
33
- "@outfitter/oxlint-plugin": "^0.1.0",
33
+ "@outfitter/oxlint-plugin": "^0.2.0",
34
34
  "ultracite": "7.2.3",
35
35
  "oxlint": "1.50.0",
36
36
  "oxfmt": "0.35.0"
@@ -42,11 +42,11 @@
42
42
  "files": [
43
43
  {
44
44
  "path": ".lefthook.yml",
45
- "content": "# Lefthook configuration preset for Outfitter projects\n# https://github.com/evilmartians/lefthook\n#\n# Usage: In your project's .lefthook.yml, add:\n# extends:\n# - node_modules/@outfitter/tooling/lefthook.yml\n\npre-commit:\n parallel: true\n commands:\n ultracite:\n glob: \"*.{js,jsx,ts,tsx,json,jsonc,css}\"\n run: bun x ultracite fix {staged_files}\n stage_fixed: true\n\n typecheck:\n glob: \"*.{ts,tsx}\"\n run: bun run typecheck\n\npre-push:\n parallel: false\n commands:\n verify:\n # TDD-aware: skips strict verification on explicit RED phase branches\n # (*-tests, */tests, *_tests). Otherwise runs verify:ci (or a strict\n # fallback: typecheck/check-or-lint/build/test).\n # Requires: @outfitter/tooling as a dev dependency.\n run: bunx @outfitter/tooling pre-push\n"
45
+ "content": "# Lefthook configuration preset for Outfitter projects\n# https://github.com/evilmartians/lefthook\n#\n# Usage: In your project's .lefthook.yml, add:\n# extends:\n# - node_modules/@outfitter/tooling/lefthook.yml\n\npre-commit:\n parallel: true\n commands:\n ultracite:\n glob: \"*.{js,jsx,ts,tsx,json,jsonc,css}\"\n run: bun x ultracite fix {staged_files}\n stage_fixed: true\n\n home-paths:\n glob: \"*.{js,jsx,json,jsonc,css,md,ts,tsx,yaml,yml}\"\n run: bun x @outfitter/tooling check-home-paths {staged_files}\n\n typecheck:\n glob: \"*.{ts,tsx}\"\n run: bun run typecheck\n\npre-push:\n parallel: false\n commands:\n verify:\n # TDD-aware: skips strict verification on explicit RED phase branches\n # (*-tests, */tests, *_tests). Otherwise runs verify:ci (or a strict\n # fallback: typecheck/check-or-lint/build/test).\n # Requires: @outfitter/tooling as a dev dependency.\n run: bun x @outfitter/tooling pre-push\n"
46
46
  }
47
47
  ],
48
48
  "devDependencies": {
49
- "@outfitter/tooling": "^0.3.4",
49
+ "@outfitter/tooling": "^0.3.5",
50
50
  "lefthook": "^2.1.1",
51
51
  "ultracite": "7.2.3"
52
52
  }