@bensandee/tooling 0.62.3 → 0.64.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.
@@ -227,6 +227,21 @@ declare const ToolingConfigSchema: z.ZodObject<{
227
227
  tolerancePp: z.ZodOptional<z.ZodNumber>;
228
228
  gateOnPullRequests: z.ZodOptional<z.ZodBoolean>;
229
229
  }, z.core.$strict>]>>;
230
+ health: z.ZodOptional<z.ZodObject<{
231
+ enabled: z.ZodOptional<z.ZodBoolean>;
232
+ src: z.ZodOptional<z.ZodArray<z.ZodString>>;
233
+ exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
234
+ extensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
235
+ directoryFileLimit: z.ZodOptional<z.ZodNumber>;
236
+ fanInLimit: z.ZodOptional<z.ZodNumber>;
237
+ fanOutLimit: z.ZodOptional<z.ZodNumber>;
238
+ history: z.ZodOptional<z.ZodEnum<{
239
+ none: "none";
240
+ "orphan-branch": "orphan-branch";
241
+ }>>;
242
+ historyBranch: z.ZodOptional<z.ZodString>;
243
+ tolerance: z.ZodOptional<z.ZodNumber>;
244
+ }, z.core.$strip>>;
230
245
  deploy: z.ZodOptional<z.ZodObject<{
231
246
  dispatchRepo: z.ZodString;
232
247
  stack: z.ZodOptional<z.ZodString>;
@@ -1,2 +1,2 @@
1
- import { a as composeArgs, c as composePs, f as lintComposeFiles, i as getContainerHealth, l as composeUp, n as runDockerCheck, o as composeDown, r as checkHttpHealth, s as composeLogs, t as deriveProjectName, u as createRealExecutor } from "../project-name-DaOEqR1l.mjs";
1
+ import { a as composeArgs, c as composePs, f as lintComposeFiles, i as getContainerHealth, l as composeUp, n as runDockerCheck, o as composeDown, r as checkHttpHealth, s as composeLogs, t as deriveProjectName, u as createRealExecutor } from "../project-name-CHPvgMTP.mjs";
2
2
  export { checkHttpHealth, composeArgs, composeDown, composeLogs, composePs, composeUp, createRealExecutor, deriveProjectName, getContainerHealth, lintComposeFiles, runDockerCheck };
@@ -372,6 +372,42 @@ async function checkHttpHealth(executor, check) {
372
372
  //#region src/docker-check/check.ts
373
373
  const DEFAULT_TIMEOUT_MS = 12e4;
374
374
  const DEFAULT_POLL_INTERVAL_MS = 5e3;
375
+ /** Probe any not-yet-healthy HTTP endpoints, flipping them to healthy in `healthStatus`. */
376
+ async function updateHttpHealth(executor, healthChecks, healthStatus) {
377
+ for (const check of healthChecks) {
378
+ if (healthStatus.get(check.name)) continue;
379
+ if (await checkHttpHealth(executor, check)) {
380
+ healthStatus.set(check.name, true);
381
+ executor.log(`${check.name} is healthy!`);
382
+ }
383
+ }
384
+ }
385
+ /**
386
+ * Run the configured smoketest (if any). Returns a failure `CheckResult` when
387
+ * the smoketest fails (after dumping logs and cleaning up), or `null` on success
388
+ * or when no smoketest is configured.
389
+ */
390
+ function runSmoketestStep(executor, config, cleanup, startTime) {
391
+ if (!config.smoketest) return null;
392
+ const { compose } = config;
393
+ executor.log(`Running smoketest: ${config.smoketest.join(" ")}`);
394
+ try {
395
+ runSmoketest(executor, config);
396
+ executor.log("Smoketest passed!");
397
+ return null;
398
+ } catch (error) {
399
+ const message = error instanceof Error ? error.message : String(error);
400
+ executor.logError(`Smoketest failed: ${message}`);
401
+ for (const service of compose.services) composeLogs(executor, compose, service);
402
+ cleanup();
403
+ return {
404
+ success: false,
405
+ reason: "smoketest-failed",
406
+ message,
407
+ elapsedMs: executor.now() - startTime
408
+ };
409
+ }
410
+ }
375
411
  /** Run the full Docker check lifecycle. */
376
412
  async function runDockerCheck(executor, config) {
377
413
  const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
@@ -440,31 +476,10 @@ async function runDockerCheck(executor, config) {
440
476
  }
441
477
  if (status === "starting") allContainersHealthy = false;
442
478
  }
443
- for (const check of config.healthChecks) if (!healthStatus.get(check.name)) {
444
- if (await checkHttpHealth(executor, check)) {
445
- healthStatus.set(check.name, true);
446
- executor.log(`${check.name} is healthy!`);
447
- }
448
- }
479
+ await updateHttpHealth(executor, config.healthChecks, healthStatus);
449
480
  if (allContainersHealthy && [...healthStatus.values()].every(Boolean)) {
450
- if (config.smoketest) {
451
- executor.log(`Running smoketest: ${config.smoketest.join(" ")}`);
452
- try {
453
- runSmoketest(executor, config);
454
- executor.log("Smoketest passed!");
455
- } catch (error) {
456
- const message = error instanceof Error ? error.message : String(error);
457
- executor.logError(`Smoketest failed: ${message}`);
458
- for (const service of compose.services) composeLogs(executor, compose, service);
459
- cleanup();
460
- return {
461
- success: false,
462
- reason: "smoketest-failed",
463
- message,
464
- elapsedMs: executor.now() - startTime
465
- };
466
- }
467
- }
481
+ const smoketestFailure = runSmoketestStep(executor, config, cleanup, startTime);
482
+ if (smoketestFailure) return smoketestFailure;
468
483
  executor.log("Check successful! All systems operational.");
469
484
  cleanup();
470
485
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.62.3",
3
+ "version": "0.64.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,16 +36,18 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
+ "@bensandee/common": "0.1.4",
39
40
  "@clack/prompts": "^1.5.1",
40
41
  "citty": "^0.2.2",
42
+ "dependency-cruiser": "^17.4.3",
41
43
  "json5": "^2.2.3",
42
44
  "jsonc-parser": "^3.3.1",
43
45
  "picomatch": "^4.0.4",
44
46
  "yaml": "^2.9.0",
45
- "zod": "^4.4.3",
46
- "@bensandee/common": "0.1.3"
47
+ "zod": "^4.4.3"
47
48
  },
48
49
  "devDependencies": {
50
+ "@bensandee/config": "0.12.0",
49
51
  "@changesets/cli": "2.31.0",
50
52
  "@release-it/bumper": "7.0.6",
51
53
  "@types/node": "24.13.0",
@@ -54,8 +56,7 @@
54
56
  "prettier": "3.8.3",
55
57
  "release-it": "20.2.0",
56
58
  "tsdown": "0.22.2",
57
- "@bensandee/claude-hooks": "0.8.1",
58
- "@bensandee/config": "0.11.3"
59
+ "@bensandee/claude-hooks": "0.8.2"
59
60
  },
60
61
  "scripts": {
61
62
  "build": "tsdown",
@@ -259,6 +259,61 @@
259
259
  }
260
260
  ]
261
261
  },
262
+ "health": {
263
+ "description": "Codebase-health gate configuration. Opt-in: set enabled true to gate structural metrics (crowded directories, circular deps, fan-in/out) against health-baseline.json.",
264
+ "type": "object",
265
+ "properties": {
266
+ "enabled": {
267
+ "type": "boolean"
268
+ },
269
+ "src": {
270
+ "type": "array",
271
+ "items": {
272
+ "type": "string"
273
+ }
274
+ },
275
+ "exclude": {
276
+ "type": "array",
277
+ "items": {
278
+ "type": "string"
279
+ }
280
+ },
281
+ "extensions": {
282
+ "type": "array",
283
+ "items": {
284
+ "type": "string"
285
+ }
286
+ },
287
+ "directoryFileLimit": {
288
+ "type": "integer",
289
+ "minimum": 0,
290
+ "maximum": 9007199254740991
291
+ },
292
+ "fanInLimit": {
293
+ "type": "integer",
294
+ "minimum": 0,
295
+ "maximum": 9007199254740991
296
+ },
297
+ "fanOutLimit": {
298
+ "type": "integer",
299
+ "minimum": 0,
300
+ "maximum": 9007199254740991
301
+ },
302
+ "history": {
303
+ "type": "string",
304
+ "enum": ["orphan-branch", "none"]
305
+ },
306
+ "historyBranch": {
307
+ "type": "string"
308
+ },
309
+ "tolerance": {
310
+ "type": "integer",
311
+ "minimum": 0,
312
+ "maximum": 9007199254740991
313
+ }
314
+ },
315
+ "additionalProperties": false
316
+ },
262
317
  "deploy": {
263
318
  "description": "Generate a deploy-dispatch step in the release workflow that triggers a workflow_dispatch event in another repo after a successful release. The resolved release token must have rights on `dispatchRepo`.",
264
319
  "type": "object",