@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.
- package/README.md +56 -17
- package/dist/bin.mjs +276 -198
- package/dist/{check-DMDdHanG.mjs → check-Ceom_OgJ.mjs} +61 -11
- package/dist/docker-check/index.d.mts +2 -0
- package/dist/docker-check/index.mjs +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
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-
|
|
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 };
|