@bensandee/tooling 0.50.1 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -20
- package/dist/bin.mjs +119 -94
- package/dist/{check-hutCTIXe.mjs → check-COzp4S_Q.mjs} +25 -0
- package/dist/docker-check/index.d.mts +5 -1
- package/dist/docker-check/index.mjs +1 -1
- package/package.json +3 -3
- package/tooling.schema.json +11 -0
package/README.md
CHANGED
|
@@ -126,10 +126,11 @@ The generated release workflow exposes these as optional `workflow_dispatch` inp
|
|
|
126
126
|
|
|
127
127
|
### Docker
|
|
128
128
|
|
|
129
|
-
| Command | Description
|
|
130
|
-
| ------------------------ |
|
|
131
|
-
| `tooling docker:build` | Build Docker images for discovered Docker packages.
|
|
132
|
-
| `tooling docker:publish` | Build, tag, and push Docker images to a registry.
|
|
129
|
+
| Command | Description |
|
|
130
|
+
| ------------------------ | ------------------------------------------------------------------- |
|
|
131
|
+
| `tooling docker:build` | Build Docker images for discovered Docker packages. |
|
|
132
|
+
| `tooling docker:publish` | Build, tag, and push Docker images to a registry. |
|
|
133
|
+
| `tooling docker:check` | Start a Compose stack, run health checks, run smoketest, tear down. |
|
|
133
134
|
|
|
134
135
|
Docker packages are discovered automatically. Any package with a `Dockerfile` or `docker/Dockerfile` is a Docker package. Image names are derived as `{root-package-name}-{package-name}`, build context defaults to `.` (project root). For single-package repos, `Dockerfile` or `docker/Dockerfile` at the project root is checked.
|
|
135
136
|
|
|
@@ -255,9 +256,32 @@ import type {
|
|
|
255
256
|
|
|
256
257
|
## Docker check
|
|
257
258
|
|
|
258
|
-
|
|
259
|
+
`docker:check` verifies that a Docker Compose stack starts correctly, passes health checks, and (optionally) survives a smoketest command. The lifecycle is: build images, compose up, poll health checks, run smoketest, compose down.
|
|
259
260
|
|
|
260
|
-
###
|
|
261
|
+
### Smoketests
|
|
262
|
+
|
|
263
|
+
A smoketest is a command that runs against the live Docker stack after all containers are healthy and HTTP health checks pass. This is useful for running integration tests, API contract checks, or any validation that requires the full stack to be running.
|
|
264
|
+
|
|
265
|
+
**Auto-detection:** If the root `package.json` has a `"test:smoke"` script, it is automatically used as the smoketest command — no configuration needed.
|
|
266
|
+
|
|
267
|
+
**Manual configuration:** For non-conventional command names, configure via `.tooling.json`:
|
|
268
|
+
|
|
269
|
+
```json
|
|
270
|
+
{
|
|
271
|
+
"dockerCheck": {
|
|
272
|
+
"smoketest": ["pnpm", "test:integration"],
|
|
273
|
+
"smoketestCwd": "."
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
If the smoketest command exits non-zero, `docker:check` dumps container logs and fails with reason `"smoketest-failed"`.
|
|
279
|
+
|
|
280
|
+
### Library API
|
|
281
|
+
|
|
282
|
+
The `@bensandee/tooling/docker-check` export provides utilities for checking Docker Compose stacks programmatically.
|
|
283
|
+
|
|
284
|
+
#### Quick start
|
|
261
285
|
|
|
262
286
|
```ts
|
|
263
287
|
import { createRealExecutor, runDockerCheck } from "@bensandee/tooling/docker-check";
|
|
@@ -269,7 +293,7 @@ const config: CheckConfig = {
|
|
|
269
293
|
composeFiles: ["docker-compose.yaml"],
|
|
270
294
|
services: ["api", "db"],
|
|
271
295
|
},
|
|
272
|
-
buildCommand: "pnpm image:build",
|
|
296
|
+
buildCommand: ["pnpm", "image:build"],
|
|
273
297
|
healthChecks: [
|
|
274
298
|
{
|
|
275
299
|
name: "API",
|
|
@@ -277,6 +301,7 @@ const config: CheckConfig = {
|
|
|
277
301
|
validate: async (res) => res.ok,
|
|
278
302
|
},
|
|
279
303
|
],
|
|
304
|
+
smoketest: ["pnpm", "test:smoke"],
|
|
280
305
|
timeoutMs: 120_000,
|
|
281
306
|
pollIntervalMs: 5_000,
|
|
282
307
|
};
|
|
@@ -287,24 +312,24 @@ if (!result.success) {
|
|
|
287
312
|
}
|
|
288
313
|
```
|
|
289
314
|
|
|
290
|
-
|
|
315
|
+
#### Exports
|
|
291
316
|
|
|
292
|
-
| Export | Description
|
|
293
|
-
| -------------------------------------- |
|
|
294
|
-
| `runDockerCheck(executor, config)` | Full lifecycle: build, compose up, health check polling, teardown |
|
|
295
|
-
| `createRealExecutor()` | Production executor (real shell, fetch, timers)
|
|
296
|
-
| `composeUp(executor, config)` | Start compose services
|
|
297
|
-
| `composeDown(executor, config)` | Stop and remove compose services
|
|
298
|
-
| `composeLogs(executor, config)` | Stream compose logs
|
|
299
|
-
| `composePs(executor, config)` | List running containers
|
|
300
|
-
| `checkHttpHealth(executor, check)` | Run a single HTTP health check
|
|
301
|
-
| `getContainerHealth(executor, config)` | Check container-level health status
|
|
317
|
+
| Export | Description |
|
|
318
|
+
| -------------------------------------- | ---------------------------------------------------------------------------- |
|
|
319
|
+
| `runDockerCheck(executor, config)` | Full lifecycle: build, compose up, health check polling, smoketest, teardown |
|
|
320
|
+
| `createRealExecutor()` | Production executor (real shell, fetch, timers) |
|
|
321
|
+
| `composeUp(executor, config)` | Start compose services |
|
|
322
|
+
| `composeDown(executor, config)` | Stop and remove compose services |
|
|
323
|
+
| `composeLogs(executor, config)` | Stream compose logs |
|
|
324
|
+
| `composePs(executor, config)` | List running containers |
|
|
325
|
+
| `checkHttpHealth(executor, check)` | Run a single HTTP health check |
|
|
326
|
+
| `getContainerHealth(executor, config)` | Check container-level health status |
|
|
302
327
|
|
|
303
|
-
|
|
328
|
+
#### Types
|
|
304
329
|
|
|
305
330
|
| Type | Description |
|
|
306
331
|
| --------------------- | ------------------------------------------------------------------------------------------ |
|
|
307
|
-
| `CheckConfig` | Full check config (compose settings, build command, health checks, timeouts)
|
|
332
|
+
| `CheckConfig` | Full check config (compose settings, build command, health checks, smoketest, timeouts) |
|
|
308
333
|
| `ComposeConfig` | Docker Compose settings (cwd, compose files, env file, services) |
|
|
309
334
|
| `HttpHealthCheck` | Health check definition (name, URL, validate function) |
|
|
310
335
|
| `CheckResult` | Result: `{ success: true, elapsedMs }` or `{ success: false, reason, message, elapsedMs }` |
|
package/dist/bin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { d as debug, f as debugExec, g as note, h as log, l as createRealExecutor$1, m as redact, p as isEnvVerbose, t as runDockerCheck, u as isExecSyncError } from "./check-
|
|
2
|
+
import { d as debug, f as debugExec, g as note, h as log, l as createRealExecutor$1, m as redact, p as isEnvVerbose, t as runDockerCheck, u as isExecSyncError } from "./check-COzp4S_Q.mjs";
|
|
3
3
|
import { defineCommand, runMain } from "citty";
|
|
4
4
|
import * as p from "@clack/prompts";
|
|
5
5
|
import { isCancel, select } from "@clack/prompts";
|
|
@@ -590,6 +590,8 @@ const DockerCheckConfigSchema = z.object({
|
|
|
590
590
|
healthChecks: z.array(DeclarativeHealthCheckSchema).optional().meta({ description: "Health check definitions" }),
|
|
591
591
|
buildCommand: z.array(z.string()).optional().meta({ description: "Command to build images before checking (e.g. [\"pnpm\", \"image:build\"])" }),
|
|
592
592
|
buildCwd: z.string().optional().meta({ description: "Working directory for build command" }),
|
|
593
|
+
smoketest: z.array(z.string()).optional().meta({ description: "Command to run as a smoketest after health checks pass (e.g. [\"pnpm\", \"test:smoke\"])" }),
|
|
594
|
+
smoketestCwd: z.string().optional().meta({ description: "Working directory for smoketest command" }),
|
|
593
595
|
timeoutMs: z.number().int().positive().optional().meta({ description: "Overall timeout in milliseconds" }),
|
|
594
596
|
pollIntervalMs: z.number().int().positive().optional().meta({ description: "Poll interval in milliseconds" })
|
|
595
597
|
});
|
|
@@ -663,13 +665,22 @@ const MONOREPO_IGNORED_KEYS = new Set(["setupVitest", "projectType"]);
|
|
|
663
665
|
function saveToolingConfig(ctx, config) {
|
|
664
666
|
const defaults = computeDefaults(config.targetDir);
|
|
665
667
|
const isMonorepo = config.structure === "monorepo";
|
|
668
|
+
const existing = ctx.exists(CONFIG_FILE) ? ctx.read(CONFIG_FILE) : void 0;
|
|
669
|
+
const overrideKeySet = new Set(OVERRIDE_KEYS);
|
|
670
|
+
const preserved = {};
|
|
671
|
+
if (existing) {
|
|
672
|
+
const result = ToolingConfigSchema.safeParse(JSON.parse(existing));
|
|
673
|
+
if (result.success) {
|
|
674
|
+
for (const [key, value] of Object.entries(result.data)) if (key !== "$schema" && !overrideKeySet.has(key)) preserved[key] = value;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
666
677
|
const overrides = { $schema: "node_modules/@bensandee/tooling/tooling.schema.json" };
|
|
667
678
|
for (const key of OVERRIDE_KEYS) {
|
|
668
679
|
if (isMonorepo && MONOREPO_IGNORED_KEYS.has(key)) continue;
|
|
669
680
|
if (config[key] !== defaults[key]) overrides[key] = config[key];
|
|
670
681
|
}
|
|
682
|
+
Object.assign(overrides, preserved);
|
|
671
683
|
const content = JSON.stringify(overrides, null, 2) + "\n";
|
|
672
|
-
const existing = ctx.exists(CONFIG_FILE) ? ctx.read(CONFIG_FILE) : void 0;
|
|
673
684
|
if (existing !== void 0 && contentEqual(CONFIG_FILE, existing, content)) return {
|
|
674
685
|
filePath: CONFIG_FILE,
|
|
675
686
|
action: "skipped",
|
|
@@ -1360,15 +1371,15 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1360
1371
|
"@release-it/bumper": "7.0.5",
|
|
1361
1372
|
"@types/node": "24.12.0",
|
|
1362
1373
|
"commit-and-tag-version": "12.7.1",
|
|
1363
|
-
"knip": "6.3.
|
|
1364
|
-
"lefthook": "2.1.
|
|
1365
|
-
"oxfmt": "0.
|
|
1366
|
-
"oxlint": "1.
|
|
1367
|
-
"prettier": "3.8.
|
|
1374
|
+
"knip": "6.3.1",
|
|
1375
|
+
"lefthook": "2.1.5",
|
|
1376
|
+
"oxfmt": "0.44.0",
|
|
1377
|
+
"oxlint": "1.59.0",
|
|
1378
|
+
"prettier": "3.8.2",
|
|
1368
1379
|
"release-it": "19.2.4",
|
|
1369
1380
|
"tsdown": "0.21.7",
|
|
1370
1381
|
"typescript": "6.0.2",
|
|
1371
|
-
"vitest": "4.1.
|
|
1382
|
+
"vitest": "4.1.4",
|
|
1372
1383
|
"pnpm": "10.33.0"
|
|
1373
1384
|
}["@types/node"],
|
|
1374
1385
|
tsdown: {
|
|
@@ -1376,15 +1387,15 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1376
1387
|
"@release-it/bumper": "7.0.5",
|
|
1377
1388
|
"@types/node": "24.12.0",
|
|
1378
1389
|
"commit-and-tag-version": "12.7.1",
|
|
1379
|
-
"knip": "6.3.
|
|
1380
|
-
"lefthook": "2.1.
|
|
1381
|
-
"oxfmt": "0.
|
|
1382
|
-
"oxlint": "1.
|
|
1383
|
-
"prettier": "3.8.
|
|
1390
|
+
"knip": "6.3.1",
|
|
1391
|
+
"lefthook": "2.1.5",
|
|
1392
|
+
"oxfmt": "0.44.0",
|
|
1393
|
+
"oxlint": "1.59.0",
|
|
1394
|
+
"prettier": "3.8.2",
|
|
1384
1395
|
"release-it": "19.2.4",
|
|
1385
1396
|
"tsdown": "0.21.7",
|
|
1386
1397
|
"typescript": "6.0.2",
|
|
1387
|
-
"vitest": "4.1.
|
|
1398
|
+
"vitest": "4.1.4",
|
|
1388
1399
|
"pnpm": "10.33.0"
|
|
1389
1400
|
}["tsdown"],
|
|
1390
1401
|
typescript: {
|
|
@@ -1392,15 +1403,15 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1392
1403
|
"@release-it/bumper": "7.0.5",
|
|
1393
1404
|
"@types/node": "24.12.0",
|
|
1394
1405
|
"commit-and-tag-version": "12.7.1",
|
|
1395
|
-
"knip": "6.3.
|
|
1396
|
-
"lefthook": "2.1.
|
|
1397
|
-
"oxfmt": "0.
|
|
1398
|
-
"oxlint": "1.
|
|
1399
|
-
"prettier": "3.8.
|
|
1406
|
+
"knip": "6.3.1",
|
|
1407
|
+
"lefthook": "2.1.5",
|
|
1408
|
+
"oxfmt": "0.44.0",
|
|
1409
|
+
"oxlint": "1.59.0",
|
|
1410
|
+
"prettier": "3.8.2",
|
|
1400
1411
|
"release-it": "19.2.4",
|
|
1401
1412
|
"tsdown": "0.21.7",
|
|
1402
1413
|
"typescript": "6.0.2",
|
|
1403
|
-
"vitest": "4.1.
|
|
1414
|
+
"vitest": "4.1.4",
|
|
1404
1415
|
"pnpm": "10.33.0"
|
|
1405
1416
|
}["typescript"],
|
|
1406
1417
|
vitest: {
|
|
@@ -1408,15 +1419,15 @@ const PER_PACKAGE_DEV_DEPS = {
|
|
|
1408
1419
|
"@release-it/bumper": "7.0.5",
|
|
1409
1420
|
"@types/node": "24.12.0",
|
|
1410
1421
|
"commit-and-tag-version": "12.7.1",
|
|
1411
|
-
"knip": "6.3.
|
|
1412
|
-
"lefthook": "2.1.
|
|
1413
|
-
"oxfmt": "0.
|
|
1414
|
-
"oxlint": "1.
|
|
1415
|
-
"prettier": "3.8.
|
|
1422
|
+
"knip": "6.3.1",
|
|
1423
|
+
"lefthook": "2.1.5",
|
|
1424
|
+
"oxfmt": "0.44.0",
|
|
1425
|
+
"oxlint": "1.59.0",
|
|
1426
|
+
"prettier": "3.8.2",
|
|
1416
1427
|
"release-it": "19.2.4",
|
|
1417
1428
|
"tsdown": "0.21.7",
|
|
1418
1429
|
"typescript": "6.0.2",
|
|
1419
|
-
"vitest": "4.1.
|
|
1430
|
+
"vitest": "4.1.4",
|
|
1420
1431
|
"pnpm": "10.33.0"
|
|
1421
1432
|
}["vitest"]
|
|
1422
1433
|
};
|
|
@@ -1427,15 +1438,15 @@ const ROOT_DEV_DEPS = {
|
|
|
1427
1438
|
"@release-it/bumper": "7.0.5",
|
|
1428
1439
|
"@types/node": "24.12.0",
|
|
1429
1440
|
"commit-and-tag-version": "12.7.1",
|
|
1430
|
-
"knip": "6.3.
|
|
1431
|
-
"lefthook": "2.1.
|
|
1432
|
-
"oxfmt": "0.
|
|
1433
|
-
"oxlint": "1.
|
|
1434
|
-
"prettier": "3.8.
|
|
1441
|
+
"knip": "6.3.1",
|
|
1442
|
+
"lefthook": "2.1.5",
|
|
1443
|
+
"oxfmt": "0.44.0",
|
|
1444
|
+
"oxlint": "1.59.0",
|
|
1445
|
+
"prettier": "3.8.2",
|
|
1435
1446
|
"release-it": "19.2.4",
|
|
1436
1447
|
"tsdown": "0.21.7",
|
|
1437
1448
|
"typescript": "6.0.2",
|
|
1438
|
-
"vitest": "4.1.
|
|
1449
|
+
"vitest": "4.1.4",
|
|
1439
1450
|
"pnpm": "10.33.0"
|
|
1440
1451
|
}["knip"],
|
|
1441
1452
|
lefthook: {
|
|
@@ -1443,15 +1454,15 @@ const ROOT_DEV_DEPS = {
|
|
|
1443
1454
|
"@release-it/bumper": "7.0.5",
|
|
1444
1455
|
"@types/node": "24.12.0",
|
|
1445
1456
|
"commit-and-tag-version": "12.7.1",
|
|
1446
|
-
"knip": "6.3.
|
|
1447
|
-
"lefthook": "2.1.
|
|
1448
|
-
"oxfmt": "0.
|
|
1449
|
-
"oxlint": "1.
|
|
1450
|
-
"prettier": "3.8.
|
|
1457
|
+
"knip": "6.3.1",
|
|
1458
|
+
"lefthook": "2.1.5",
|
|
1459
|
+
"oxfmt": "0.44.0",
|
|
1460
|
+
"oxlint": "1.59.0",
|
|
1461
|
+
"prettier": "3.8.2",
|
|
1451
1462
|
"release-it": "19.2.4",
|
|
1452
1463
|
"tsdown": "0.21.7",
|
|
1453
1464
|
"typescript": "6.0.2",
|
|
1454
|
-
"vitest": "4.1.
|
|
1465
|
+
"vitest": "4.1.4",
|
|
1455
1466
|
"pnpm": "10.33.0"
|
|
1456
1467
|
}["lefthook"],
|
|
1457
1468
|
oxlint: {
|
|
@@ -1459,15 +1470,15 @@ const ROOT_DEV_DEPS = {
|
|
|
1459
1470
|
"@release-it/bumper": "7.0.5",
|
|
1460
1471
|
"@types/node": "24.12.0",
|
|
1461
1472
|
"commit-and-tag-version": "12.7.1",
|
|
1462
|
-
"knip": "6.3.
|
|
1463
|
-
"lefthook": "2.1.
|
|
1464
|
-
"oxfmt": "0.
|
|
1465
|
-
"oxlint": "1.
|
|
1466
|
-
"prettier": "3.8.
|
|
1473
|
+
"knip": "6.3.1",
|
|
1474
|
+
"lefthook": "2.1.5",
|
|
1475
|
+
"oxfmt": "0.44.0",
|
|
1476
|
+
"oxlint": "1.59.0",
|
|
1477
|
+
"prettier": "3.8.2",
|
|
1467
1478
|
"release-it": "19.2.4",
|
|
1468
1479
|
"tsdown": "0.21.7",
|
|
1469
1480
|
"typescript": "6.0.2",
|
|
1470
|
-
"vitest": "4.1.
|
|
1481
|
+
"vitest": "4.1.4",
|
|
1471
1482
|
"pnpm": "10.33.0"
|
|
1472
1483
|
}["oxlint"]
|
|
1473
1484
|
};
|
|
@@ -1500,15 +1511,15 @@ function addReleaseDeps(deps, config) {
|
|
|
1500
1511
|
"@release-it/bumper": "7.0.5",
|
|
1501
1512
|
"@types/node": "24.12.0",
|
|
1502
1513
|
"commit-and-tag-version": "12.7.1",
|
|
1503
|
-
"knip": "6.3.
|
|
1504
|
-
"lefthook": "2.1.
|
|
1505
|
-
"oxfmt": "0.
|
|
1506
|
-
"oxlint": "1.
|
|
1507
|
-
"prettier": "3.8.
|
|
1514
|
+
"knip": "6.3.1",
|
|
1515
|
+
"lefthook": "2.1.5",
|
|
1516
|
+
"oxfmt": "0.44.0",
|
|
1517
|
+
"oxlint": "1.59.0",
|
|
1518
|
+
"prettier": "3.8.2",
|
|
1508
1519
|
"release-it": "19.2.4",
|
|
1509
1520
|
"tsdown": "0.21.7",
|
|
1510
1521
|
"typescript": "6.0.2",
|
|
1511
|
-
"vitest": "4.1.
|
|
1522
|
+
"vitest": "4.1.4",
|
|
1512
1523
|
"pnpm": "10.33.0"
|
|
1513
1524
|
}["release-it"];
|
|
1514
1525
|
if (config.structure === "monorepo") deps["@release-it/bumper"] = {
|
|
@@ -1516,15 +1527,15 @@ function addReleaseDeps(deps, config) {
|
|
|
1516
1527
|
"@release-it/bumper": "7.0.5",
|
|
1517
1528
|
"@types/node": "24.12.0",
|
|
1518
1529
|
"commit-and-tag-version": "12.7.1",
|
|
1519
|
-
"knip": "6.3.
|
|
1520
|
-
"lefthook": "2.1.
|
|
1521
|
-
"oxfmt": "0.
|
|
1522
|
-
"oxlint": "1.
|
|
1523
|
-
"prettier": "3.8.
|
|
1530
|
+
"knip": "6.3.1",
|
|
1531
|
+
"lefthook": "2.1.5",
|
|
1532
|
+
"oxfmt": "0.44.0",
|
|
1533
|
+
"oxlint": "1.59.0",
|
|
1534
|
+
"prettier": "3.8.2",
|
|
1524
1535
|
"release-it": "19.2.4",
|
|
1525
1536
|
"tsdown": "0.21.7",
|
|
1526
1537
|
"typescript": "6.0.2",
|
|
1527
|
-
"vitest": "4.1.
|
|
1538
|
+
"vitest": "4.1.4",
|
|
1528
1539
|
"pnpm": "10.33.0"
|
|
1529
1540
|
}["@release-it/bumper"];
|
|
1530
1541
|
break;
|
|
@@ -1534,15 +1545,15 @@ function addReleaseDeps(deps, config) {
|
|
|
1534
1545
|
"@release-it/bumper": "7.0.5",
|
|
1535
1546
|
"@types/node": "24.12.0",
|
|
1536
1547
|
"commit-and-tag-version": "12.7.1",
|
|
1537
|
-
"knip": "6.3.
|
|
1538
|
-
"lefthook": "2.1.
|
|
1539
|
-
"oxfmt": "0.
|
|
1540
|
-
"oxlint": "1.
|
|
1541
|
-
"prettier": "3.8.
|
|
1548
|
+
"knip": "6.3.1",
|
|
1549
|
+
"lefthook": "2.1.5",
|
|
1550
|
+
"oxfmt": "0.44.0",
|
|
1551
|
+
"oxlint": "1.59.0",
|
|
1552
|
+
"prettier": "3.8.2",
|
|
1542
1553
|
"release-it": "19.2.4",
|
|
1543
1554
|
"tsdown": "0.21.7",
|
|
1544
1555
|
"typescript": "6.0.2",
|
|
1545
|
-
"vitest": "4.1.
|
|
1556
|
+
"vitest": "4.1.4",
|
|
1546
1557
|
"pnpm": "10.33.0"
|
|
1547
1558
|
}["commit-and-tag-version"];
|
|
1548
1559
|
break;
|
|
@@ -1552,15 +1563,15 @@ function addReleaseDeps(deps, config) {
|
|
|
1552
1563
|
"@release-it/bumper": "7.0.5",
|
|
1553
1564
|
"@types/node": "24.12.0",
|
|
1554
1565
|
"commit-and-tag-version": "12.7.1",
|
|
1555
|
-
"knip": "6.3.
|
|
1556
|
-
"lefthook": "2.1.
|
|
1557
|
-
"oxfmt": "0.
|
|
1558
|
-
"oxlint": "1.
|
|
1559
|
-
"prettier": "3.8.
|
|
1566
|
+
"knip": "6.3.1",
|
|
1567
|
+
"lefthook": "2.1.5",
|
|
1568
|
+
"oxfmt": "0.44.0",
|
|
1569
|
+
"oxlint": "1.59.0",
|
|
1570
|
+
"prettier": "3.8.2",
|
|
1560
1571
|
"release-it": "19.2.4",
|
|
1561
1572
|
"tsdown": "0.21.7",
|
|
1562
1573
|
"typescript": "6.0.2",
|
|
1563
|
-
"vitest": "4.1.
|
|
1574
|
+
"vitest": "4.1.4",
|
|
1564
1575
|
"pnpm": "10.33.0"
|
|
1565
1576
|
}["@changesets/cli"];
|
|
1566
1577
|
break;
|
|
@@ -1585,22 +1596,22 @@ async function generatePackageJson(ctx) {
|
|
|
1585
1596
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1586
1597
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1587
1598
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.2";
|
|
1588
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1599
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.51.0";
|
|
1589
1600
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
|
|
1590
1601
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
1591
1602
|
"@changesets/cli": "2.30.0",
|
|
1592
1603
|
"@release-it/bumper": "7.0.5",
|
|
1593
1604
|
"@types/node": "24.12.0",
|
|
1594
1605
|
"commit-and-tag-version": "12.7.1",
|
|
1595
|
-
"knip": "6.3.
|
|
1596
|
-
"lefthook": "2.1.
|
|
1597
|
-
"oxfmt": "0.
|
|
1598
|
-
"oxlint": "1.
|
|
1599
|
-
"prettier": "3.8.
|
|
1606
|
+
"knip": "6.3.1",
|
|
1607
|
+
"lefthook": "2.1.5",
|
|
1608
|
+
"oxfmt": "0.44.0",
|
|
1609
|
+
"oxlint": "1.59.0",
|
|
1610
|
+
"prettier": "3.8.2",
|
|
1600
1611
|
"release-it": "19.2.4",
|
|
1601
1612
|
"tsdown": "0.21.7",
|
|
1602
1613
|
"typescript": "6.0.2",
|
|
1603
|
-
"vitest": "4.1.
|
|
1614
|
+
"vitest": "4.1.4",
|
|
1604
1615
|
"pnpm": "10.33.0"
|
|
1605
1616
|
}["oxfmt"];
|
|
1606
1617
|
if (ctx.config.formatter === "prettier") devDeps["prettier"] = {
|
|
@@ -1608,15 +1619,15 @@ async function generatePackageJson(ctx) {
|
|
|
1608
1619
|
"@release-it/bumper": "7.0.5",
|
|
1609
1620
|
"@types/node": "24.12.0",
|
|
1610
1621
|
"commit-and-tag-version": "12.7.1",
|
|
1611
|
-
"knip": "6.3.
|
|
1612
|
-
"lefthook": "2.1.
|
|
1613
|
-
"oxfmt": "0.
|
|
1614
|
-
"oxlint": "1.
|
|
1615
|
-
"prettier": "3.8.
|
|
1622
|
+
"knip": "6.3.1",
|
|
1623
|
+
"lefthook": "2.1.5",
|
|
1624
|
+
"oxfmt": "0.44.0",
|
|
1625
|
+
"oxlint": "1.59.0",
|
|
1626
|
+
"prettier": "3.8.2",
|
|
1616
1627
|
"release-it": "19.2.4",
|
|
1617
1628
|
"tsdown": "0.21.7",
|
|
1618
1629
|
"typescript": "6.0.2",
|
|
1619
|
-
"vitest": "4.1.
|
|
1630
|
+
"vitest": "4.1.4",
|
|
1620
1631
|
"pnpm": "10.33.0"
|
|
1621
1632
|
}["prettier"];
|
|
1622
1633
|
addReleaseDeps(devDeps, ctx.config);
|
|
@@ -1702,15 +1713,15 @@ async function generatePackageJson(ctx) {
|
|
|
1702
1713
|
"@release-it/bumper": "7.0.5",
|
|
1703
1714
|
"@types/node": "24.12.0",
|
|
1704
1715
|
"commit-and-tag-version": "12.7.1",
|
|
1705
|
-
"knip": "6.3.
|
|
1706
|
-
"lefthook": "2.1.
|
|
1707
|
-
"oxfmt": "0.
|
|
1708
|
-
"oxlint": "1.
|
|
1709
|
-
"prettier": "3.8.
|
|
1716
|
+
"knip": "6.3.1",
|
|
1717
|
+
"lefthook": "2.1.5",
|
|
1718
|
+
"oxfmt": "0.44.0",
|
|
1719
|
+
"oxlint": "1.59.0",
|
|
1720
|
+
"prettier": "3.8.2",
|
|
1710
1721
|
"release-it": "19.2.4",
|
|
1711
1722
|
"tsdown": "0.21.7",
|
|
1712
1723
|
"typescript": "6.0.2",
|
|
1713
|
-
"vitest": "4.1.
|
|
1724
|
+
"vitest": "4.1.4",
|
|
1714
1725
|
"pnpm": "10.33.0"
|
|
1715
1726
|
}["pnpm"]}`
|
|
1716
1727
|
};
|
|
@@ -2159,7 +2170,7 @@ const REQUIRED_ENTRIES = [
|
|
|
2159
2170
|
const OPTIONAL_ENTRIES = [
|
|
2160
2171
|
".tooling-migrate.md",
|
|
2161
2172
|
".tooling-archived/",
|
|
2162
|
-
".claude/hooks/bst-hook.audit.local.jsonl"
|
|
2173
|
+
".claude/hooks/bst-hook.audit.local.jsonl*"
|
|
2163
2174
|
];
|
|
2164
2175
|
const ALL_ENTRIES = [...REQUIRED_ENTRIES, ...OPTIONAL_ENTRIES];
|
|
2165
2176
|
/** Normalize a gitignore entry for comparison: strip leading `/` and trailing `/`. */
|
|
@@ -3359,7 +3370,7 @@ async function runGenerators(ctx) {
|
|
|
3359
3370
|
function generateMigratePrompt(results, config, detected) {
|
|
3360
3371
|
const sections = [];
|
|
3361
3372
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3362
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
3373
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.51.0 repo:sync\` on ${timestamp}_\n`);
|
|
3363
3374
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
3364
3375
|
if (changed.length > 0) {
|
|
3365
3376
|
const items = changed.map((r) => {
|
|
@@ -5520,6 +5531,12 @@ function generateCheckOverlay(services) {
|
|
|
5520
5531
|
}
|
|
5521
5532
|
return stringify({ services: serviceOverrides });
|
|
5522
5533
|
}
|
|
5534
|
+
/** Detect a smoketest command from a `test:smoke` script in the root package.json. */
|
|
5535
|
+
function detectSmoketest(cwd) {
|
|
5536
|
+
try {
|
|
5537
|
+
if (parsePackageJson(readFileSync(path.join(cwd, "package.json"), "utf-8"))?.scripts?.["test:smoke"]) return ["pnpm", "test:smoke"];
|
|
5538
|
+
} catch (_error) {}
|
|
5539
|
+
}
|
|
5523
5540
|
/** Auto-detect compose config from conventional file locations. */
|
|
5524
5541
|
function computeCheckDefaults(cwd) {
|
|
5525
5542
|
const { dir, files } = detectComposeFiles(cwd);
|
|
@@ -5529,13 +5546,15 @@ function computeCheckDefaults(cwd) {
|
|
|
5529
5546
|
const envFile = detectCheckEnvFile(dir);
|
|
5530
5547
|
const services = parseComposeServices(dir, files);
|
|
5531
5548
|
const healthChecks = deriveHealthChecks(services);
|
|
5549
|
+
const smoketest = detectSmoketest(cwd);
|
|
5532
5550
|
return {
|
|
5533
5551
|
composeCwd: dir !== cwd ? dir : void 0,
|
|
5534
5552
|
composeFiles: files,
|
|
5535
5553
|
checkOverlay,
|
|
5536
5554
|
envFile,
|
|
5537
5555
|
services: services.map((s) => s.name),
|
|
5538
|
-
healthChecks: healthChecks.length > 0 ? healthChecks : void 0
|
|
5556
|
+
healthChecks: healthChecks.length > 0 ? healthChecks : void 0,
|
|
5557
|
+
smoketest
|
|
5539
5558
|
};
|
|
5540
5559
|
}
|
|
5541
5560
|
/** Create a DockerFileReader backed by the real filesystem. */
|
|
@@ -5596,11 +5615,15 @@ const dockerCheckCommand = defineCommand({
|
|
|
5596
5615
|
},
|
|
5597
5616
|
async run({ args }) {
|
|
5598
5617
|
const cwd = process.cwd();
|
|
5599
|
-
|
|
5618
|
+
const toolingConfig = loadToolingConfig(cwd);
|
|
5619
|
+
if (toolingConfig?.dockerCheck === false) {
|
|
5600
5620
|
log.info("Docker check is disabled in .tooling.json");
|
|
5601
5621
|
return;
|
|
5602
5622
|
}
|
|
5603
|
-
const defaults =
|
|
5623
|
+
const defaults = {
|
|
5624
|
+
...computeCheckDefaults(cwd),
|
|
5625
|
+
...toolingConfig?.dockerCheck
|
|
5626
|
+
};
|
|
5604
5627
|
if (!defaults.composeFiles || defaults.composeFiles.length === 0) throw new FatalError("No compose files found. Expected docker-compose.yaml or compose.yaml in ./ or docker/.");
|
|
5605
5628
|
if (!defaults.checkOverlay) {
|
|
5606
5629
|
const composeCwd = defaults.composeCwd ?? cwd;
|
|
@@ -5639,6 +5662,8 @@ const dockerCheckCommand = defineCommand({
|
|
|
5639
5662
|
buildCommand: defaults.buildCommand,
|
|
5640
5663
|
buildCwd: defaults.buildCwd,
|
|
5641
5664
|
healthChecks: defaults.healthChecks ? toHttpHealthChecks(defaults.healthChecks) : [],
|
|
5665
|
+
smoketest: defaults.smoketest,
|
|
5666
|
+
smoketestCwd: defaults.smoketestCwd,
|
|
5642
5667
|
timeoutMs: args.timeout ? Number.parseInt(args.timeout, 10) : defaults.timeoutMs,
|
|
5643
5668
|
pollIntervalMs: args["poll-interval"] ? Number.parseInt(args["poll-interval"], 10) : defaults.pollIntervalMs,
|
|
5644
5669
|
verbose: args.verbose === true || isEnvVerbose()
|
|
@@ -6450,7 +6475,7 @@ const webhookSendCommand = defineCommand({
|
|
|
6450
6475
|
const main = defineCommand({
|
|
6451
6476
|
meta: {
|
|
6452
6477
|
name: "bst",
|
|
6453
|
-
version: "0.
|
|
6478
|
+
version: "0.51.0",
|
|
6454
6479
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
6455
6480
|
},
|
|
6456
6481
|
subCommands: {
|
|
@@ -6471,7 +6496,7 @@ const main = defineCommand({
|
|
|
6471
6496
|
"webhook:send": webhookSendCommand
|
|
6472
6497
|
}
|
|
6473
6498
|
});
|
|
6474
|
-
console.log(`@bensandee/tooling v0.
|
|
6499
|
+
console.log(`@bensandee/tooling v0.51.0`);
|
|
6475
6500
|
async function run() {
|
|
6476
6501
|
await runMain(main);
|
|
6477
6502
|
process.exit(process.exitCode ?? 0);
|
|
@@ -128,6 +128,13 @@ function buildImages(executor, config) {
|
|
|
128
128
|
if (!file) return;
|
|
129
129
|
executor.execInherit(file, args, { cwd: config.buildCwd ?? config.compose.cwd });
|
|
130
130
|
}
|
|
131
|
+
/** Run the smoketest command against the running stack. */
|
|
132
|
+
function runSmoketest(executor, config) {
|
|
133
|
+
if (!config.smoketest) return;
|
|
134
|
+
const [file, ...args] = config.smoketest;
|
|
135
|
+
if (!file) return;
|
|
136
|
+
executor.execInherit(file, args, { cwd: config.smoketestCwd ?? config.compose.cwd });
|
|
137
|
+
}
|
|
131
138
|
/** Start the compose stack in detached mode. */
|
|
132
139
|
function composeUp(executor, config) {
|
|
133
140
|
executor.execInherit("docker", [
|
|
@@ -263,6 +270,24 @@ async function runDockerCheck(executor, config) {
|
|
|
263
270
|
}
|
|
264
271
|
}
|
|
265
272
|
if (allContainersHealthy && [...healthStatus.values()].every(Boolean)) {
|
|
273
|
+
if (config.smoketest) {
|
|
274
|
+
executor.log(`Running smoketest: ${config.smoketest.join(" ")}`);
|
|
275
|
+
try {
|
|
276
|
+
runSmoketest(executor, config);
|
|
277
|
+
executor.log("Smoketest passed!");
|
|
278
|
+
} catch (error) {
|
|
279
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
280
|
+
executor.logError(`Smoketest failed: ${message}`);
|
|
281
|
+
for (const service of compose.services) composeLogs(executor, compose, service);
|
|
282
|
+
cleanup();
|
|
283
|
+
return {
|
|
284
|
+
success: false,
|
|
285
|
+
reason: "smoketest-failed",
|
|
286
|
+
message,
|
|
287
|
+
elapsedMs: executor.now() - startTime
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
266
291
|
executor.log("Check successful! All systems operational.");
|
|
267
292
|
cleanup();
|
|
268
293
|
return {
|
|
@@ -68,6 +68,10 @@ interface CheckConfig {
|
|
|
68
68
|
buildCwd?: string;
|
|
69
69
|
/** HTTP health checks to poll. All must pass for success. */
|
|
70
70
|
healthChecks: HttpHealthCheck[];
|
|
71
|
+
/** Optional smoketest command to run after all health checks pass (e.g. ["pnpm", "test:smoke"]). */
|
|
72
|
+
smoketest?: string[];
|
|
73
|
+
/** Working directory for the smoketest command (defaults to compose.cwd). */
|
|
74
|
+
smoketestCwd?: string;
|
|
71
75
|
/** Maximum time to wait for all checks to pass, in ms. Default: 120000. */
|
|
72
76
|
timeoutMs?: number;
|
|
73
77
|
/** Interval between polling attempts, in ms. Default: 5000. */
|
|
@@ -81,7 +85,7 @@ type CheckResult = {
|
|
|
81
85
|
elapsedMs: number;
|
|
82
86
|
} | {
|
|
83
87
|
success: false;
|
|
84
|
-
reason: "timeout" | "unhealthy-container" | "error";
|
|
88
|
+
reason: "timeout" | "unhealthy-container" | "smoketest-failed" | "error";
|
|
85
89
|
message: string;
|
|
86
90
|
elapsedMs: number;
|
|
87
91
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as composeDown, c as composeUp, i as composeArgs, l as createRealExecutor, n as checkHttpHealth, o as composeLogs, r as getContainerHealth, s as composePs, t as runDockerCheck } from "../check-
|
|
1
|
+
import { a as composeDown, c as composeUp, i as composeArgs, l as createRealExecutor, n as checkHttpHealth, o as composeLogs, r as getContainerHealth, s as composePs, t as runDockerCheck } from "../check-COzp4S_Q.mjs";
|
|
2
2
|
export { checkHttpHealth, composeArgs, composeDown, composeLogs, composePs, composeUp, createRealExecutor, getContainerHealth, runDockerCheck };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bensandee/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.0",
|
|
4
4
|
"description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@clack/prompts": "^1.2.0",
|
|
40
|
-
"citty": "^0.2.
|
|
40
|
+
"citty": "^0.2.2",
|
|
41
41
|
"json5": "^2.2.3",
|
|
42
42
|
"jsonc-parser": "^3.3.1",
|
|
43
43
|
"picomatch": "^4.0.4",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@types/node": "24.12.0",
|
|
52
52
|
"@types/picomatch": "4.0.3",
|
|
53
53
|
"commit-and-tag-version": "12.7.1",
|
|
54
|
-
"prettier": "3.8.
|
|
54
|
+
"prettier": "3.8.2",
|
|
55
55
|
"release-it": "19.2.4",
|
|
56
56
|
"tsdown": "0.21.7",
|
|
57
57
|
"@bensandee/claude-hooks": "0.7.0",
|
package/tooling.schema.json
CHANGED
|
@@ -144,6 +144,17 @@
|
|
|
144
144
|
"description": "Working directory for build command",
|
|
145
145
|
"type": "string"
|
|
146
146
|
},
|
|
147
|
+
"smoketest": {
|
|
148
|
+
"description": "Command to run as a smoketest after health checks pass (e.g. [\"pnpm\", \"test:smoke\"])",
|
|
149
|
+
"type": "array",
|
|
150
|
+
"items": {
|
|
151
|
+
"type": "string"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"smoketestCwd": {
|
|
155
|
+
"description": "Working directory for smoketest command",
|
|
156
|
+
"type": "string"
|
|
157
|
+
},
|
|
147
158
|
"timeoutMs": {
|
|
148
159
|
"description": "Overall timeout in milliseconds",
|
|
149
160
|
"type": "integer",
|