@bensandee/tooling 0.32.0 → 0.34.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.
@@ -1,5 +1,40 @@
1
+ import * as clack from "@clack/prompts";
1
2
  import { execSync } from "node:child_process";
2
3
  import { z } from "zod";
4
+ //#region src/utils/log.ts
5
+ const out = (msg) => console.log(msg);
6
+ const isCI = Boolean(process.env["CI"]);
7
+ const log = isCI ? {
8
+ info: out,
9
+ warn: (msg) => out(`[warn] ${msg}`),
10
+ error: (msg) => out(`[error] ${msg}`),
11
+ success: (msg) => out(`✓ ${msg}`)
12
+ } : clack.log;
13
+ function note(body, title) {
14
+ if (isCI) {
15
+ if (title) out(`--- ${title} ---`);
16
+ out(body);
17
+ } else clack.note(body, title);
18
+ }
19
+ //#endregion
20
+ //#region src/utils/debug.ts
21
+ /** Check if verbose/debug mode is enabled via environment variables. */
22
+ function isEnvVerbose() {
23
+ return process.env["TOOLING_DEBUG"] === "true";
24
+ }
25
+ /** Log a debug message when verbose mode is enabled. */
26
+ function debug(config, message) {
27
+ if (config.verbose) log.info(`[debug] ${message}`);
28
+ }
29
+ /** Log the result of an exec call when verbose mode is enabled. */
30
+ function debugExec(config, label, result) {
31
+ if (!config.verbose) return;
32
+ const lines = [`[debug] ${label} (exit code ${String(result.exitCode)})`];
33
+ if (result.stdout.trim()) lines.push(` stdout: ${result.stdout.trim()}`);
34
+ if (result.stderr.trim()) lines.push(` stderr: ${result.stderr.trim()}`);
35
+ log.info(lines.join("\n"));
36
+ }
37
+ //#endregion
3
38
  //#region src/utils/exec.ts
4
39
  /** Type guard for `execSync` errors that carry stdout/stderr/status. */
5
40
  function isExecSyncError(err) {
@@ -144,15 +179,25 @@ async function runDockerCheck(executor, config) {
144
179
  const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
145
180
  const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
146
181
  const { compose } = config;
182
+ const vc = { verbose: config.verbose ?? false };
183
+ debug(vc, `Compose files: ${compose.composeFiles.join(", ")}`);
184
+ debug(vc, `Services: ${compose.services.join(", ")}`);
185
+ debug(vc, `Health checks: ${config.healthChecks.map((c) => c.name).join(", ") || "(none)"}`);
186
+ debug(vc, `Timeout: ${String(timeoutMs)}ms, poll interval: ${String(pollIntervalMs)}ms`);
147
187
  const cleanup = () => composeDown(executor, compose);
148
- const disposeInt = executor.onSignal("SIGINT", () => {
149
- cleanup();
150
- process.exit(1);
151
- });
152
- const disposeTerm = executor.onSignal("SIGTERM", () => {
188
+ let cleaningUp = false;
189
+ const gracefulShutdown = () => {
190
+ if (cleaningUp) {
191
+ executor.log("Cleanup in progress, please wait...");
192
+ return;
193
+ }
194
+ cleaningUp = true;
195
+ executor.log("Interrupted — shutting down compose stack...");
153
196
  cleanup();
154
197
  process.exit(1);
155
- });
198
+ };
199
+ const disposeInt = executor.onSignal("SIGINT", gracefulShutdown);
200
+ const disposeTerm = executor.onSignal("SIGTERM", gracefulShutdown);
156
201
  try {
157
202
  if (config.buildCommand) {
158
203
  executor.log("Building images...");
@@ -162,12 +207,15 @@ async function runDockerCheck(executor, config) {
162
207
  composeUp(executor, compose);
163
208
  executor.log(`Waiting for stack to be healthy (max ${timeoutMs / 1e3}s)...`);
164
209
  const startTime = executor.now();
210
+ let lastStatusLogTime = startTime;
165
211
  const healthStatus = new Map(config.healthChecks.map((c) => [c.name, false]));
166
212
  while (executor.now() - startTime < timeoutMs) {
167
213
  const containers = composePs(executor, compose);
214
+ debug(vc, `compose ps: ${containers.length} container(s)`);
168
215
  let allContainersHealthy = true;
169
216
  for (const service of compose.services) {
170
217
  const status = getContainerHealth(containers, service);
218
+ debug(vc, `Container ${service}: ${status || "(no healthcheck)"}`);
171
219
  if (status === "unhealthy") {
172
220
  executor.logError(`Container ${service} is unhealthy`);
173
221
  composeLogs(executor, compose, service);
@@ -195,10 +243,12 @@ async function runDockerCheck(executor, config) {
195
243
  elapsedMs: executor.now() - startTime
196
244
  };
197
245
  }
198
- const elapsed = Math.floor((executor.now() - startTime) / 1e3);
199
- if (elapsed > 0 && elapsed % 5 === 0) {
200
- const statuses = [...healthStatus.entries()].map(([name, ok]) => `${name}=${ok ? "OK" : "Pending"}`).join(", ");
201
- executor.log(`Waiting... (${elapsed}s elapsed). ${statuses}`);
246
+ const now = executor.now();
247
+ if (now - lastStatusLogTime >= 5e3) {
248
+ lastStatusLogTime = now;
249
+ const elapsed = Math.floor((now - startTime) / 1e3);
250
+ const parts = [compose.services.map((s) => `${s}=${getContainerHealth(containers, s)}`).join(", "), [...healthStatus.entries()].map(([name, ok]) => `${name}=${ok ? "OK" : "Pending"}`).join(", ")].filter(Boolean).join(" | ");
251
+ executor.log(`Waiting... (${elapsed}s elapsed). ${parts}`);
202
252
  }
203
253
  await executor.sleep(pollIntervalMs);
204
254
  }
@@ -225,4 +275,4 @@ async function runDockerCheck(executor, config) {
225
275
  }
226
276
  }
227
277
  //#endregion
228
- export { composeDown as a, composeUp as c, composeCommand as i, createRealExecutor as l, checkHttpHealth as n, composeLogs as o, getContainerHealth as r, composePs as s, runDockerCheck as t, isExecSyncError as u };
278
+ export { composeDown as a, composeUp as c, debug as d, debugExec as f, note as h, composeCommand as i, createRealExecutor as l, log as m, checkHttpHealth as n, composeLogs as o, isEnvVerbose as p, getContainerHealth as r, composePs as s, runDockerCheck as t, isExecSyncError as u };
@@ -67,6 +67,8 @@ interface CheckConfig {
67
67
  timeoutMs?: number;
68
68
  /** Interval between polling attempts, in ms. Default: 5000. */
69
69
  pollIntervalMs?: number;
70
+ /** If true, emit detailed debug logging. */
71
+ verbose?: boolean;
70
72
  }
71
73
  /** Result of the docker check run. */
72
74
  type CheckResult = {
@@ -1,2 +1,2 @@
1
- import { a as composeDown, c as composeUp, i as composeCommand, l as createRealExecutor, n as checkHttpHealth, o as composeLogs, r as getContainerHealth, s as composePs, t as runDockerCheck } from "../check-DMDdHanG.mjs";
1
+ import { a as composeDown, c as composeUp, i as composeCommand, l as createRealExecutor, n as checkHttpHealth, o as composeLogs, r as getContainerHealth, s as composePs, t as runDockerCheck } from "../check-Ceom_OgJ.mjs";
2
2
  export { checkHttpHealth, composeCommand, 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.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "bin": {
6
6
  "bst": "./dist/bin.mjs"