@outfitter/tooling 0.2.0 → 0.2.2

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/dist/cli/index.js CHANGED
@@ -13,7 +13,8 @@ function buildCheckCommand(options) {
13
13
  }
14
14
  async function runCheck(paths = []) {
15
15
  const cmd = buildCheckCommand({ paths });
16
- console.log(`Running: bun x ${cmd.join(" ")}`);
16
+ process.stdout.write(`Running: bun x ${cmd.join(" ")}
17
+ `);
17
18
  const proc = Bun.spawn(["bun", "x", ...cmd], {
18
19
  stdio: ["inherit", "inherit", "inherit"]
19
20
  });
@@ -31,7 +32,8 @@ function buildFixCommand(options) {
31
32
  }
32
33
  async function runFix(paths = []) {
33
34
  const cmd = buildFixCommand({ paths });
34
- console.log(`Running: bun x ${cmd.join(" ")}`);
35
+ process.stdout.write(`Running: bun x ${cmd.join(" ")}
36
+ `);
35
37
  const proc = Bun.spawn(["bun", "x", ...cmd], {
36
38
  stdio: ["inherit", "inherit", "inherit"]
37
39
  });
@@ -69,7 +71,15 @@ function detectFrameworks(pkg) {
69
71
  return ["--frameworks", ...detected];
70
72
  }
71
73
  function buildUltraciteCommand(options) {
72
- const cmd = ["ultracite", "init", "--linter", "biome", "--pm", "bun", "--quiet"];
74
+ const cmd = [
75
+ "ultracite",
76
+ "init",
77
+ "--linter",
78
+ "biome",
79
+ "--pm",
80
+ "bun",
81
+ "--quiet"
82
+ ];
73
83
  if (options.frameworks && options.frameworks.length > 0) {
74
84
  cmd.push("--frameworks", ...options.frameworks);
75
85
  }
@@ -79,14 +89,16 @@ async function runInit(cwd = process.cwd()) {
79
89
  const pkgPath = `${cwd}/package.json`;
80
90
  const pkgFile = Bun.file(pkgPath);
81
91
  if (!await pkgFile.exists()) {
82
- console.error("No package.json found in current directory");
92
+ process.stderr.write(`No package.json found in current directory
93
+ `);
83
94
  process.exit(1);
84
95
  }
85
96
  const pkg = await pkgFile.json();
86
97
  const frameworkFlags = detectFrameworks(pkg);
87
98
  const frameworks = frameworkFlags.length > 0 ? frameworkFlags.slice(1) : [];
88
99
  const cmd = buildUltraciteCommand({ frameworks });
89
- console.log(`Running: bun x ${cmd.join(" ")}`);
100
+ process.stdout.write(`Running: bun x ${cmd.join(" ")}
101
+ `);
90
102
  const proc = Bun.spawn(["bun", "x", ...cmd], {
91
103
  cwd,
92
104
  stdio: ["inherit", "inherit", "inherit"]
@@ -96,6 +108,8 @@ async function runInit(cwd = process.cwd()) {
96
108
  }
97
109
 
98
110
  // src/cli/pre-push.ts
111
+ import { existsSync, readFileSync } from "node:fs";
112
+ import { join } from "node:path";
99
113
  var COLORS = {
100
114
  reset: "\x1B[0m",
101
115
  red: "\x1B[31m",
@@ -104,18 +118,140 @@ var COLORS = {
104
118
  blue: "\x1B[34m"
105
119
  };
106
120
  function log(msg) {
107
- console.log(msg);
121
+ process.stdout.write(`${msg}
122
+ `);
108
123
  }
109
124
  function getCurrentBranch() {
110
125
  const result = Bun.spawnSync(["git", "rev-parse", "--abbrev-ref", "HEAD"]);
111
126
  return result.stdout.toString().trim();
112
127
  }
128
+ function runGit(args) {
129
+ try {
130
+ const result = Bun.spawnSync(["git", ...args], { stderr: "ignore" });
131
+ if (result.exitCode !== 0) {
132
+ return { ok: false, lines: [] };
133
+ }
134
+ return {
135
+ ok: true,
136
+ lines: result.stdout.toString().split(`
137
+ `).map((line) => line.trim()).filter(Boolean)
138
+ };
139
+ } catch {
140
+ return { ok: false, lines: [] };
141
+ }
142
+ }
113
143
  function isRedPhaseBranch(branch) {
114
144
  return branch.endsWith("-tests") || branch.endsWith("/tests") || branch.endsWith("_tests");
115
145
  }
116
146
  function isScaffoldBranch(branch) {
117
147
  return branch.endsWith("-scaffold") || branch.endsWith("/scaffold") || branch.endsWith("_scaffold");
118
148
  }
149
+ var TEST_PATH_PATTERNS = [
150
+ /(^|\/)__tests__\//,
151
+ /(^|\/)__snapshots__\//,
152
+ /\.(test|spec)\.[cm]?[jt]sx?$/,
153
+ /\.snap$/,
154
+ /(^|\/)(vitest|jest|bun)\.config\.[cm]?[jt]s$/,
155
+ /(^|\/)tsconfig\.test\.json$/,
156
+ /(^|\/)\.env\.test(\.|$)/
157
+ ];
158
+ function isTestOnlyPath(path) {
159
+ const normalized = path.replaceAll("\\", "/");
160
+ return TEST_PATH_PATTERNS.some((pattern) => pattern.test(normalized));
161
+ }
162
+ function areFilesTestOnly(paths) {
163
+ return paths.length > 0 && paths.every((path) => isTestOnlyPath(path));
164
+ }
165
+ function canBypassRedPhaseByChangedFiles(changedFiles) {
166
+ return changedFiles.deterministic && areFilesTestOnly(changedFiles.files);
167
+ }
168
+ function resolveBaseRef() {
169
+ const candidates = [
170
+ "origin/main",
171
+ "main",
172
+ "origin/trunk",
173
+ "trunk",
174
+ "origin/master",
175
+ "master"
176
+ ];
177
+ for (const candidate of candidates) {
178
+ const resolved = runGit(["rev-parse", "--verify", "--quiet", candidate]);
179
+ if (resolved.ok) {
180
+ return candidate;
181
+ }
182
+ }
183
+ return;
184
+ }
185
+ function changedFilesFromRange(range) {
186
+ const result = runGit(["diff", "--name-only", "--diff-filter=d", range]);
187
+ return {
188
+ ok: result.ok,
189
+ files: result.lines
190
+ };
191
+ }
192
+ function getChangedFilesForPush() {
193
+ const upstream = runGit([
194
+ "rev-parse",
195
+ "--abbrev-ref",
196
+ "--symbolic-full-name",
197
+ "@{upstream}"
198
+ ]);
199
+ if (upstream.ok && upstream.lines[0]) {
200
+ const rangeResult = changedFilesFromRange(`${upstream.lines[0]}...HEAD`);
201
+ if (rangeResult.ok) {
202
+ return {
203
+ files: rangeResult.files,
204
+ deterministic: true,
205
+ source: "upstream"
206
+ };
207
+ }
208
+ }
209
+ const baseRef = resolveBaseRef();
210
+ if (baseRef) {
211
+ const rangeResult = changedFilesFromRange(`${baseRef}...HEAD`);
212
+ if (rangeResult.ok) {
213
+ return {
214
+ files: rangeResult.files,
215
+ deterministic: true,
216
+ source: "baseRef"
217
+ };
218
+ }
219
+ }
220
+ return {
221
+ files: [],
222
+ deterministic: false,
223
+ source: "undetermined"
224
+ };
225
+ }
226
+ function maybeSkipForRedPhase(reason, branch) {
227
+ const changedFiles = getChangedFilesForPush();
228
+ if (!changedFiles.deterministic) {
229
+ log(`${COLORS.yellow}RED-phase bypass denied${COLORS.reset}: could not determine full push diff range`);
230
+ log("Running strict verification.");
231
+ log("");
232
+ return false;
233
+ }
234
+ if (!canBypassRedPhaseByChangedFiles(changedFiles)) {
235
+ log(`${COLORS.yellow}RED-phase bypass denied${COLORS.reset}: changed files are not test-only`);
236
+ if (changedFiles.files.length > 0) {
237
+ log(`Changed files (${changedFiles.source}): ${changedFiles.files.join(", ")}`);
238
+ } else {
239
+ log(`No changed files detected in ${changedFiles.source} range. Running strict verification.`);
240
+ }
241
+ log("");
242
+ return false;
243
+ }
244
+ if (reason === "branch") {
245
+ log(`${COLORS.yellow}TDD RED phase${COLORS.reset} detected: ${COLORS.blue}${branch}${COLORS.reset}`);
246
+ } else {
247
+ log(`${COLORS.yellow}Scaffold branch${COLORS.reset} with RED phase branch in context: ${COLORS.blue}${branch}${COLORS.reset}`);
248
+ }
249
+ log(`${COLORS.yellow}Skipping strict verification${COLORS.reset} - changed files are test-only`);
250
+ log(`Diff source: ${changedFiles.source}`);
251
+ log("");
252
+ log("Remember: GREEN phase (implementation) must make these tests pass!");
253
+ return true;
254
+ }
119
255
  function hasRedPhaseBranchInContext(currentBranch) {
120
256
  let branches = [];
121
257
  try {
@@ -145,44 +281,95 @@ function hasRedPhaseBranchInContext(currentBranch) {
145
281
  }
146
282
  return false;
147
283
  }
148
- function runTests() {
284
+ function createVerificationPlan(scripts) {
285
+ if (scripts["verify:ci"]) {
286
+ return { ok: true, scripts: ["verify:ci"], source: "verify:ci" };
287
+ }
288
+ const requiredScripts = ["typecheck", "build", "test"];
289
+ const missingRequired = requiredScripts.filter((name) => !scripts[name]);
290
+ const checkOrLint = scripts["check"] ? "check" : scripts["lint"] ? "lint" : undefined;
291
+ if (!checkOrLint || missingRequired.length > 0) {
292
+ const missing = checkOrLint ? missingRequired : [...missingRequired, "check|lint"];
293
+ return {
294
+ ok: false,
295
+ error: `Missing required scripts for strict pre-push verification: ${missing.join(", ")}`
296
+ };
297
+ }
298
+ return {
299
+ ok: true,
300
+ scripts: ["typecheck", checkOrLint, "build", "test"],
301
+ source: "fallback"
302
+ };
303
+ }
304
+ function readPackageScripts(cwd = process.cwd()) {
305
+ const packageJsonPath = join(cwd, "package.json");
306
+ if (!existsSync(packageJsonPath)) {
307
+ return {};
308
+ }
309
+ try {
310
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
311
+ const scripts = parsed.scripts ?? {};
312
+ const normalized = {};
313
+ for (const [name, value] of Object.entries(scripts)) {
314
+ if (typeof value === "string") {
315
+ normalized[name] = value;
316
+ }
317
+ }
318
+ return normalized;
319
+ } catch {
320
+ return {};
321
+ }
322
+ }
323
+ function runScript(scriptName) {
149
324
  log("");
150
- const result = Bun.spawnSync(["bun", "run", "test"], {
325
+ log(`Running: ${COLORS.blue}bun run ${scriptName}${COLORS.reset}`);
326
+ const result = Bun.spawnSync(["bun", "run", scriptName], {
151
327
  stdio: ["inherit", "inherit", "inherit"]
152
328
  });
153
329
  return result.exitCode === 0;
154
330
  }
155
331
  async function runPrePush(options = {}) {
156
- log(`${COLORS.blue}Pre-push test${COLORS.reset} (TDD-aware)`);
332
+ log(`${COLORS.blue}Pre-push verify${COLORS.reset} (TDD-aware)`);
157
333
  log("");
158
334
  const branch = getCurrentBranch();
159
335
  if (isRedPhaseBranch(branch)) {
160
- log(`${COLORS.yellow}TDD RED phase${COLORS.reset} detected: ${COLORS.blue}${branch}${COLORS.reset}`);
161
- log(`${COLORS.yellow}Skipping test execution${COLORS.reset} - tests are expected to fail in RED phase`);
162
- log("");
163
- log("Remember: GREEN phase (implementation) must make these tests pass!");
164
- process.exit(0);
336
+ if (maybeSkipForRedPhase("branch", branch)) {
337
+ process.exit(0);
338
+ }
165
339
  }
166
340
  if (isScaffoldBranch(branch)) {
167
341
  if (hasRedPhaseBranchInContext(branch)) {
168
- log(`${COLORS.yellow}Scaffold branch${COLORS.reset} with RED phase branch in context: ${COLORS.blue}${branch}${COLORS.reset}`);
169
- log(`${COLORS.yellow}Skipping test execution${COLORS.reset} - RED phase tests expected to fail`);
170
- log("");
171
- process.exit(0);
342
+ if (maybeSkipForRedPhase("context", branch)) {
343
+ process.exit(0);
344
+ }
172
345
  }
173
346
  }
174
347
  if (options.force) {
175
- log(`${COLORS.yellow}Force flag set${COLORS.reset} - skipping tests`);
348
+ log(`${COLORS.yellow}Force flag set${COLORS.reset} - skipping strict verification`);
176
349
  process.exit(0);
177
350
  }
178
- log(`Running tests for branch: ${COLORS.blue}${branch}${COLORS.reset}`);
179
- if (runTests()) {
351
+ const plan = createVerificationPlan(readPackageScripts());
352
+ if (!plan.ok) {
353
+ log(`${COLORS.red}Strict pre-push verification is not configured${COLORS.reset}`);
354
+ log(plan.error);
180
355
  log("");
181
- log(`${COLORS.green}All tests passed${COLORS.reset}`);
182
- process.exit(0);
356
+ log("Add one of:");
357
+ log(" - verify:ci");
358
+ log(" - typecheck + (check or lint) + build + test");
359
+ process.exit(1);
360
+ }
361
+ log(`Running strict verification for branch: ${COLORS.blue}${branch}${COLORS.reset}`);
362
+ if (plan.source === "verify:ci") {
363
+ log("Using `verify:ci` script.");
183
364
  } else {
365
+ log(`Using fallback scripts: ${plan.scripts.join(" -> ")}`);
366
+ }
367
+ for (const scriptName of plan.scripts) {
368
+ if (runScript(scriptName)) {
369
+ continue;
370
+ }
184
371
  log("");
185
- log(`${COLORS.red}Tests failed${COLORS.reset}`);
372
+ log(`${COLORS.red}Verification failed${COLORS.reset} on script: ${scriptName}`);
186
373
  log("");
187
374
  log("If this is intentional TDD RED phase work, name your branch:");
188
375
  log(" - feature-tests");
@@ -190,11 +377,14 @@ async function runPrePush(options = {}) {
190
377
  log(" - feature_tests");
191
378
  process.exit(1);
192
379
  }
380
+ log("");
381
+ log(`${COLORS.green}Strict verification passed${COLORS.reset}`);
382
+ process.exit(0);
193
383
  }
194
384
 
195
385
  // src/cli/upgrade-bun.ts
196
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
197
- import { join } from "node:path";
386
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
387
+ import { join as join2 } from "node:path";
198
388
  var COLORS2 = {
199
389
  reset: "\x1B[0m",
200
390
  red: "\x1B[31m",
@@ -203,16 +393,20 @@ var COLORS2 = {
203
393
  blue: "\x1B[34m"
204
394
  };
205
395
  function log2(msg) {
206
- console.log(msg);
396
+ process.stdout.write(`${msg}
397
+ `);
207
398
  }
208
399
  function info(msg) {
209
- console.log(`${COLORS2.blue}▸${COLORS2.reset} ${msg}`);
400
+ process.stdout.write(`${COLORS2.blue}▸${COLORS2.reset} ${msg}
401
+ `);
210
402
  }
211
403
  function success(msg) {
212
- console.log(`${COLORS2.green}✓${COLORS2.reset} ${msg}`);
404
+ process.stdout.write(`${COLORS2.green}✓${COLORS2.reset} ${msg}
405
+ `);
213
406
  }
214
407
  function warn(msg) {
215
- console.log(`${COLORS2.yellow}!${COLORS2.reset} ${msg}`);
408
+ process.stdout.write(`${COLORS2.yellow}!${COLORS2.reset} ${msg}
409
+ `);
216
410
  }
217
411
  async function fetchLatestVersion() {
218
412
  const response = await fetch("https://api.github.com/repos/oven-sh/bun/releases/latest");
@@ -228,13 +422,13 @@ function findPackageJsonFiles(dir) {
228
422
  const glob = new Bun.Glob("**/package.json");
229
423
  for (const path of glob.scanSync({ cwd: dir })) {
230
424
  if (!path.includes("node_modules")) {
231
- results.push(join(dir, path));
425
+ results.push(join2(dir, path));
232
426
  }
233
427
  }
234
428
  return results;
235
429
  }
236
430
  function updateEnginesBun(filePath, version) {
237
- const content = readFileSync(filePath, "utf-8");
431
+ const content = readFileSync2(filePath, "utf-8");
238
432
  const pattern = /"bun":\s*">=[\d.]+"/;
239
433
  if (!pattern.test(content)) {
240
434
  return false;
@@ -247,7 +441,7 @@ function updateEnginesBun(filePath, version) {
247
441
  return false;
248
442
  }
249
443
  function updateTypesBun(filePath, version) {
250
- const content = readFileSync(filePath, "utf-8");
444
+ const content = readFileSync2(filePath, "utf-8");
251
445
  const pattern = /"@types\/bun":\s*"\^[\d.]+"/;
252
446
  if (!pattern.test(content)) {
253
447
  return false;
@@ -261,14 +455,14 @@ function updateTypesBun(filePath, version) {
261
455
  }
262
456
  async function runUpgradeBun(targetVersion, options = {}) {
263
457
  const cwd = process.cwd();
264
- const bunVersionFile = join(cwd, ".bun-version");
458
+ const bunVersionFile = join2(cwd, ".bun-version");
265
459
  let version = targetVersion;
266
460
  if (!version) {
267
461
  info("Fetching latest Bun version...");
268
462
  version = await fetchLatestVersion();
269
463
  log2(`Latest version: ${version}`);
270
464
  }
271
- const currentVersion = existsSync(bunVersionFile) ? readFileSync(bunVersionFile, "utf-8").trim() : "unknown";
465
+ const currentVersion = existsSync2(bunVersionFile) ? readFileSync2(bunVersionFile, "utf-8").trim() : "unknown";
272
466
  log2(`Current version: ${currentVersion}`);
273
467
  if (currentVersion === version) {
274
468
  success(`Already on version ${version}`);
@@ -284,13 +478,13 @@ async function runUpgradeBun(targetVersion, options = {}) {
284
478
  info("Updating engines.bun...");
285
479
  for (const file of packageFiles) {
286
480
  if (updateEnginesBun(file, version)) {
287
- log2(` ${file.replace(cwd + "/", "")}`);
481
+ log2(` ${file.replace(`${cwd}/`, "")}`);
288
482
  }
289
483
  }
290
484
  info("Updating @types/bun...");
291
485
  for (const file of packageFiles) {
292
486
  if (updateTypesBun(file, version)) {
293
- log2(` ${file.replace(cwd + "/", "")}`);
487
+ log2(` ${file.replace(`${cwd}/`, "")}`);
294
488
  }
295
489
  }
296
490
  if (options.install !== false) {
@@ -347,7 +541,7 @@ program.command("fix").description("Fix linting issues (wraps ultracite)").argum
347
541
  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) => {
348
542
  await runUpgradeBun(version, options);
349
543
  });
350
- program.command("pre-push").description("TDD-aware pre-push test hook").option("-f, --force", "Skip tests entirely").action(async (options) => {
544
+ program.command("pre-push").description("TDD-aware pre-push strict verification hook").option("-f, --force", "Skip strict verification entirely").action(async (options) => {
351
545
  await runPrePush(options);
352
546
  });
353
547
  program.parse();
package/dist/cli/init.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  buildUltraciteCommand,
4
4
  detectFrameworks,
5
5
  runInit
6
- } from "../shared/@outfitter/tooling-xaxdr9da.js";
6
+ } from "../shared/@outfitter/tooling-mxwc1n8w.js";
7
7
  export {
8
8
  runInit,
9
9
  detectFrameworks,
@@ -1,3 +1,36 @@
1
+ /**
2
+ * Check if branch is a TDD RED phase branch
3
+ */
4
+ declare function isRedPhaseBranch(branch: string): boolean;
5
+ /**
6
+ * Check if branch is a scaffold branch
7
+ */
8
+ declare function isScaffoldBranch(branch: string): boolean;
9
+ declare function isTestOnlyPath(path: string): boolean;
10
+ declare function areFilesTestOnly(paths: readonly string[]): boolean;
11
+ interface PushChangedFiles {
12
+ readonly files: readonly string[];
13
+ readonly deterministic: boolean;
14
+ readonly source: "upstream" | "baseRef" | "undetermined";
15
+ }
16
+ declare function canBypassRedPhaseByChangedFiles(changedFiles: PushChangedFiles): boolean;
17
+ type ScriptMap = Readonly<Record<string, string | undefined>>;
18
+ type VerificationPlan = {
19
+ readonly ok: true;
20
+ readonly scripts: readonly string[];
21
+ readonly source: "verify:ci" | "fallback";
22
+ } | {
23
+ readonly ok: false;
24
+ readonly error: string;
25
+ };
26
+ /**
27
+ * Derive strict pre-push verification from package scripts.
28
+ *
29
+ * Priority:
30
+ * 1) `verify:ci`
31
+ * 2) fallback sequence: `typecheck`, `check|lint`, `build`, `test`
32
+ */
33
+ declare function createVerificationPlan(scripts: ScriptMap): VerificationPlan;
1
34
  interface PrePushOptions {
2
35
  force?: boolean;
3
36
  }
@@ -5,4 +38,4 @@ interface PrePushOptions {
5
38
  * Main pre-push command
6
39
  */
7
40
  declare function runPrePush(options?: PrePushOptions): Promise<void>;
8
- export { runPrePush, PrePushOptions };
41
+ export { runPrePush, isTestOnlyPath, isScaffoldBranch, isRedPhaseBranch, createVerificationPlan, canBypassRedPhaseByChangedFiles, areFilesTestOnly, VerificationPlan, PushChangedFiles, PrePushOptions };
@@ -1,7 +1,19 @@
1
1
  // @bun
2
2
  import {
3
+ areFilesTestOnly,
4
+ canBypassRedPhaseByChangedFiles,
5
+ createVerificationPlan,
6
+ isRedPhaseBranch,
7
+ isScaffoldBranch,
8
+ isTestOnlyPath,
3
9
  runPrePush
4
- } from "../shared/@outfitter/tooling-qm7jeg0d.js";
10
+ } from "../shared/@outfitter/tooling-8sd32ts6.js";
5
11
  export {
6
- runPrePush
12
+ runPrePush,
13
+ isTestOnlyPath,
14
+ isScaffoldBranch,
15
+ isRedPhaseBranch,
16
+ createVerificationPlan,
17
+ canBypassRedPhaseByChangedFiles,
18
+ areFilesTestOnly
7
19
  };
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  runUpgradeBun
4
- } from "../shared/@outfitter/tooling-75j500dv.js";
4
+ } from "../shared/@outfitter/tooling-9yzd08v1.js";
5
5
  export {
6
6
  runUpgradeBun
7
7
  };
@@ -0,0 +1,6 @@
1
+ import { RegistryBuildConfig } from "../shared/@outfitter/tooling-sjm8nebx";
2
+ /**
3
+ * Registry build configuration
4
+ */
5
+ declare const REGISTRY_CONFIG: RegistryBuildConfig;
6
+ export { REGISTRY_CONFIG };
@@ -2,8 +2,18 @@
2
2
  // @bun
3
3
 
4
4
  // packages/tooling/src/registry/build.ts
5
- import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
5
+ import {
6
+ existsSync,
7
+ mkdirSync,
8
+ readFileSync,
9
+ statSync,
10
+ writeFileSync
11
+ } from "fs";
6
12
  import { dirname, join } from "path";
13
+ function log(message) {
14
+ process.stdout.write(`${message}
15
+ `);
16
+ }
7
17
  function findRepoRoot(startDir) {
8
18
  let dir = startDir;
9
19
  while (dir !== "/") {
@@ -76,27 +86,30 @@ var REGISTRY_CONFIG = {
76
86
  blocks: {
77
87
  claude: {
78
88
  description: "Claude Code settings and hooks for automated formatting",
79
- files: [
80
- ".claude/settings.json",
81
- ".claude/hooks/format-code-on-stop.sh"
82
- ]
89
+ files: [".claude/settings.json", ".claude/hooks/format-code-on-stop.sh"]
83
90
  },
84
91
  biome: {
85
92
  description: "Biome linter/formatter configuration via Ultracite",
86
93
  files: ["packages/tooling/biome.json"],
87
94
  remap: { "packages/tooling/biome.json": "biome.json" },
88
- devDependencies: { ultracite: "^7.0.0" }
95
+ devDependencies: { ultracite: "^7.1.1" }
89
96
  },
90
97
  lefthook: {
91
98
  description: "Git hooks via Lefthook for pre-commit and pre-push",
92
99
  files: ["packages/tooling/lefthook.yml"],
93
100
  remap: { "packages/tooling/lefthook.yml": ".lefthook.yml" },
94
- devDependencies: { lefthook: "^2.0.0" }
101
+ devDependencies: {
102
+ "@outfitter/tooling": "^0.2.1",
103
+ lefthook: "^2.0.16",
104
+ ultracite: "^7.1.1"
105
+ }
95
106
  },
96
107
  markdownlint: {
97
108
  description: "Markdown linting configuration via markdownlint-cli2",
98
109
  files: ["packages/tooling/.markdownlint-cli2.jsonc"],
99
- remap: { "packages/tooling/.markdownlint-cli2.jsonc": ".markdownlint-cli2.jsonc" }
110
+ remap: {
111
+ "packages/tooling/.markdownlint-cli2.jsonc": ".markdownlint-cli2.jsonc"
112
+ }
100
113
  },
101
114
  bootstrap: {
102
115
  description: "Project bootstrap script for installing tools and dependencies",
@@ -113,16 +126,21 @@ function main() {
113
126
  const repoRoot = findRepoRoot(scriptDir);
114
127
  const outputDir = join(repoRoot, "packages/tooling/registry");
115
128
  const outputPath = join(outputDir, "registry.json");
116
- console.log(`Building registry from: ${repoRoot}`);
129
+ log(`Building registry from: ${repoRoot}`);
117
130
  if (!existsSync(outputDir)) {
118
131
  mkdirSync(outputDir, { recursive: true });
119
132
  }
120
133
  const registry = buildRegistry(REGISTRY_CONFIG, repoRoot);
121
- writeFileSync(outputPath, JSON.stringify(registry, null, 2) + `
134
+ writeFileSync(outputPath, `${JSON.stringify(registry, null, "\t")}
122
135
  `);
123
136
  const blockCount = Object.keys(registry.blocks).length;
124
137
  const fileCount = Object.values(registry.blocks).flatMap((b) => b.files ?? []).length;
125
- console.log(`\u2713 Generated ${outputPath}`);
126
- console.log(` ${blockCount} blocks, ${fileCount} files embedded`);
138
+ log(`\u2713 Generated ${outputPath}`);
139
+ log(` ${blockCount} blocks, ${fileCount} files embedded`);
140
+ }
141
+ if (import.meta.main) {
142
+ main();
127
143
  }
128
- main();
144
+ export {
145
+ REGISTRY_CONFIG
146
+ };
@@ -9,7 +9,8 @@ function buildCheckCommand(options) {
9
9
  }
10
10
  async function runCheck(paths = []) {
11
11
  const cmd = buildCheckCommand({ paths });
12
- console.log(`Running: bun x ${cmd.join(" ")}`);
12
+ process.stdout.write(`Running: bun x ${cmd.join(" ")}
13
+ `);
13
14
  const proc = Bun.spawn(["bun", "x", ...cmd], {
14
15
  stdio: ["inherit", "inherit", "inherit"]
15
16
  });