codeharness 0.6.1
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/bin/codeharness +9 -0
- package/dist/chunk-7ZD2ZNDU.js +540 -0
- package/dist/docker-CT57JGM7.js +33 -0
- package/dist/index.js +6104 -0
- package/package.json +39 -0
- package/ralph/AGENTS.md +38 -0
- package/ralph/bridge.sh +421 -0
- package/ralph/db_schema_gen.sh +109 -0
- package/ralph/doc_gardener.sh +352 -0
- package/ralph/drivers/claude-code.sh +160 -0
- package/ralph/exec_plans.sh +252 -0
- package/ralph/harness_status.sh +156 -0
- package/ralph/lib/circuit_breaker.sh +210 -0
- package/ralph/lib/date_utils.sh +60 -0
- package/ralph/lib/timeout_utils.sh +77 -0
- package/ralph/onboard.sh +83 -0
- package/ralph/ralph.sh +1006 -0
- package/ralph/retro.sh +298 -0
- package/ralph/validate_epic_docs.sh +129 -0
- package/ralph/verify_gates.sh +241 -0
package/bin/codeharness
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Self-contained CLI entry point for the codeharness plugin.
|
|
3
|
+
# Used by hooks via ${CLAUDE_PLUGIN_ROOT}/bin/codeharness
|
|
4
|
+
# so the CLI works without global npm install.
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
PLUGIN_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
8
|
+
|
|
9
|
+
exec node "$PLUGIN_ROOT/dist/index.js" "$@"
|
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/docker.ts
|
|
4
|
+
import { execFileSync } from "child_process";
|
|
5
|
+
import { writeFileSync } from "fs";
|
|
6
|
+
|
|
7
|
+
// src/lib/stack-path.ts
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { join, isAbsolute } from "path";
|
|
10
|
+
import { mkdirSync } from "fs";
|
|
11
|
+
function getStackDir() {
|
|
12
|
+
const xdgDataHome = process.env["XDG_DATA_HOME"];
|
|
13
|
+
if (xdgDataHome && xdgDataHome.trim() !== "" && isAbsolute(xdgDataHome)) {
|
|
14
|
+
return join(xdgDataHome, "codeharness", "stack");
|
|
15
|
+
}
|
|
16
|
+
return join(homedir(), ".codeharness", "stack");
|
|
17
|
+
}
|
|
18
|
+
function getComposeFilePath() {
|
|
19
|
+
return join(getStackDir(), "docker-compose.harness.yml");
|
|
20
|
+
}
|
|
21
|
+
function getOtelConfigPath() {
|
|
22
|
+
return join(getStackDir(), "otel-collector-config.yaml");
|
|
23
|
+
}
|
|
24
|
+
function ensureStackDir() {
|
|
25
|
+
mkdirSync(getStackDir(), { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/templates/docker-compose.ts
|
|
29
|
+
function dockerComposeCollectorOnlyTemplate() {
|
|
30
|
+
return `# Generated by codeharness \u2014 do not edit manually
|
|
31
|
+
name: codeharness-collector
|
|
32
|
+
|
|
33
|
+
services:
|
|
34
|
+
otel-collector:
|
|
35
|
+
image: otel/opentelemetry-collector-contrib:0.96.0
|
|
36
|
+
labels:
|
|
37
|
+
com.codeharness.stack: collector
|
|
38
|
+
ports:
|
|
39
|
+
- "4317:4317"
|
|
40
|
+
- "4318:4318"
|
|
41
|
+
volumes:
|
|
42
|
+
- ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro
|
|
43
|
+
restart: unless-stopped
|
|
44
|
+
|
|
45
|
+
networks:
|
|
46
|
+
default:
|
|
47
|
+
name: codeharness-collector-net
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
function dockerComposeTemplate(config) {
|
|
51
|
+
return `# Generated by codeharness \u2014 do not edit manually
|
|
52
|
+
name: codeharness-shared
|
|
53
|
+
|
|
54
|
+
services:
|
|
55
|
+
victoria-logs:
|
|
56
|
+
image: victoriametrics/victoria-logs:v1.15.0
|
|
57
|
+
labels:
|
|
58
|
+
com.codeharness.stack: shared
|
|
59
|
+
ports:
|
|
60
|
+
- "9428:9428"
|
|
61
|
+
volumes:
|
|
62
|
+
- victoria-logs-data:/vlogs
|
|
63
|
+
restart: unless-stopped
|
|
64
|
+
|
|
65
|
+
victoria-metrics:
|
|
66
|
+
image: victoriametrics/victoria-metrics:v1.106.1
|
|
67
|
+
labels:
|
|
68
|
+
com.codeharness.stack: shared
|
|
69
|
+
ports:
|
|
70
|
+
- "8428:8428"
|
|
71
|
+
volumes:
|
|
72
|
+
- victoria-metrics-data:/victoria-metrics-data
|
|
73
|
+
restart: unless-stopped
|
|
74
|
+
|
|
75
|
+
victoria-traces:
|
|
76
|
+
image: jaegertracing/all-in-one:1.56
|
|
77
|
+
labels:
|
|
78
|
+
com.codeharness.stack: shared
|
|
79
|
+
ports:
|
|
80
|
+
- "14268:14268"
|
|
81
|
+
- "16686:16686"
|
|
82
|
+
environment:
|
|
83
|
+
- COLLECTOR_OTLP_ENABLED=true
|
|
84
|
+
restart: unless-stopped
|
|
85
|
+
|
|
86
|
+
otel-collector:
|
|
87
|
+
image: otel/opentelemetry-collector-contrib:0.96.0
|
|
88
|
+
labels:
|
|
89
|
+
com.codeharness.stack: shared
|
|
90
|
+
ports:
|
|
91
|
+
- "4317:4317"
|
|
92
|
+
- "4318:4318"
|
|
93
|
+
volumes:
|
|
94
|
+
- ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro
|
|
95
|
+
depends_on:
|
|
96
|
+
- victoria-logs
|
|
97
|
+
- victoria-metrics
|
|
98
|
+
- victoria-traces
|
|
99
|
+
restart: unless-stopped
|
|
100
|
+
|
|
101
|
+
volumes:
|
|
102
|
+
victoria-logs-data:
|
|
103
|
+
victoria-metrics-data:
|
|
104
|
+
|
|
105
|
+
networks:
|
|
106
|
+
default:
|
|
107
|
+
name: codeharness-shared-net
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/templates/otel-config.ts
|
|
112
|
+
function otelCollectorRemoteTemplate(config) {
|
|
113
|
+
const logsUrl = config.logsUrl.replace(/\/+$/, "");
|
|
114
|
+
const metricsUrl = config.metricsUrl.replace(/\/+$/, "");
|
|
115
|
+
const tracesUrl = config.tracesUrl.replace(/\/+$/, "");
|
|
116
|
+
return `# Generated by codeharness \u2014 do not edit manually
|
|
117
|
+
receivers:
|
|
118
|
+
otlp:
|
|
119
|
+
protocols:
|
|
120
|
+
grpc:
|
|
121
|
+
endpoint: 0.0.0.0:4317
|
|
122
|
+
http:
|
|
123
|
+
endpoint: 0.0.0.0:4318
|
|
124
|
+
|
|
125
|
+
processors:
|
|
126
|
+
resource/default:
|
|
127
|
+
attributes:
|
|
128
|
+
- key: service.name
|
|
129
|
+
value: "unknown"
|
|
130
|
+
action: insert
|
|
131
|
+
|
|
132
|
+
exporters:
|
|
133
|
+
otlphttp/logs:
|
|
134
|
+
endpoint: ${logsUrl}/insert/opentelemetry
|
|
135
|
+
tls:
|
|
136
|
+
insecure: true
|
|
137
|
+
|
|
138
|
+
prometheusremotewrite:
|
|
139
|
+
endpoint: ${metricsUrl}/api/v1/write
|
|
140
|
+
tls:
|
|
141
|
+
insecure: true
|
|
142
|
+
|
|
143
|
+
otlp/traces:
|
|
144
|
+
endpoint: ${tracesUrl}
|
|
145
|
+
tls:
|
|
146
|
+
insecure: true
|
|
147
|
+
|
|
148
|
+
service:
|
|
149
|
+
pipelines:
|
|
150
|
+
logs:
|
|
151
|
+
receivers:
|
|
152
|
+
- otlp
|
|
153
|
+
processors:
|
|
154
|
+
- resource/default
|
|
155
|
+
exporters:
|
|
156
|
+
- otlphttp/logs
|
|
157
|
+
metrics:
|
|
158
|
+
receivers:
|
|
159
|
+
- otlp
|
|
160
|
+
processors:
|
|
161
|
+
- resource/default
|
|
162
|
+
exporters:
|
|
163
|
+
- prometheusremotewrite
|
|
164
|
+
traces:
|
|
165
|
+
receivers:
|
|
166
|
+
- otlp
|
|
167
|
+
processors:
|
|
168
|
+
- resource/default
|
|
169
|
+
exporters:
|
|
170
|
+
- otlp/traces
|
|
171
|
+
`;
|
|
172
|
+
}
|
|
173
|
+
function otelCollectorConfigTemplate() {
|
|
174
|
+
return `# Generated by codeharness \u2014 do not edit manually
|
|
175
|
+
receivers:
|
|
176
|
+
otlp:
|
|
177
|
+
protocols:
|
|
178
|
+
grpc:
|
|
179
|
+
endpoint: 0.0.0.0:4317
|
|
180
|
+
http:
|
|
181
|
+
endpoint: 0.0.0.0:4318
|
|
182
|
+
|
|
183
|
+
processors:
|
|
184
|
+
resource/default:
|
|
185
|
+
attributes:
|
|
186
|
+
- key: service.name
|
|
187
|
+
value: "unknown"
|
|
188
|
+
action: insert
|
|
189
|
+
|
|
190
|
+
exporters:
|
|
191
|
+
otlphttp/logs:
|
|
192
|
+
endpoint: http://victoria-logs:9428/insert/opentelemetry
|
|
193
|
+
tls:
|
|
194
|
+
insecure: true
|
|
195
|
+
|
|
196
|
+
prometheusremotewrite:
|
|
197
|
+
endpoint: http://victoria-metrics:8428/api/v1/write
|
|
198
|
+
tls:
|
|
199
|
+
insecure: true
|
|
200
|
+
|
|
201
|
+
otlp/traces:
|
|
202
|
+
endpoint: http://victoria-traces:14268
|
|
203
|
+
tls:
|
|
204
|
+
insecure: true
|
|
205
|
+
|
|
206
|
+
service:
|
|
207
|
+
pipelines:
|
|
208
|
+
logs:
|
|
209
|
+
receivers:
|
|
210
|
+
- otlp
|
|
211
|
+
processors:
|
|
212
|
+
- resource/default
|
|
213
|
+
exporters:
|
|
214
|
+
- otlphttp/logs
|
|
215
|
+
metrics:
|
|
216
|
+
receivers:
|
|
217
|
+
- otlp
|
|
218
|
+
processors:
|
|
219
|
+
- resource/default
|
|
220
|
+
exporters:
|
|
221
|
+
- prometheusremotewrite
|
|
222
|
+
traces:
|
|
223
|
+
receivers:
|
|
224
|
+
- otlp
|
|
225
|
+
processors:
|
|
226
|
+
- resource/default
|
|
227
|
+
exporters:
|
|
228
|
+
- otlp/traces
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/lib/docker.ts
|
|
233
|
+
function isDockerAvailable() {
|
|
234
|
+
try {
|
|
235
|
+
execFileSync("docker", ["--version"], { stdio: "pipe", timeout: 1e4 });
|
|
236
|
+
return true;
|
|
237
|
+
} catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function isDockerComposeAvailable() {
|
|
242
|
+
try {
|
|
243
|
+
execFileSync("docker", ["compose", "version"], { stdio: "pipe", timeout: 1e4 });
|
|
244
|
+
return true;
|
|
245
|
+
} catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function isStackRunning(composeFile) {
|
|
250
|
+
try {
|
|
251
|
+
const output = execFileSync("docker", ["compose", "-f", composeFile, "ps", "--format", "json"], {
|
|
252
|
+
stdio: "pipe",
|
|
253
|
+
timeout: 15e3
|
|
254
|
+
});
|
|
255
|
+
const text = output.toString().trim();
|
|
256
|
+
if (!text) return false;
|
|
257
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
258
|
+
for (const line of lines) {
|
|
259
|
+
const svc = JSON.parse(line);
|
|
260
|
+
if (svc.State !== "running") return false;
|
|
261
|
+
}
|
|
262
|
+
return lines.length > 0;
|
|
263
|
+
} catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function isSharedStackRunning() {
|
|
268
|
+
try {
|
|
269
|
+
const composeFile = getComposeFilePath();
|
|
270
|
+
const output = execFileSync("docker", ["compose", "-p", "codeharness-shared", "-f", composeFile, "ps", "--format", "json"], {
|
|
271
|
+
stdio: "pipe",
|
|
272
|
+
timeout: 15e3
|
|
273
|
+
});
|
|
274
|
+
const text = output.toString().trim();
|
|
275
|
+
if (!text) return false;
|
|
276
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
277
|
+
for (const line of lines) {
|
|
278
|
+
const svc = JSON.parse(line);
|
|
279
|
+
if (svc.State !== "running") return false;
|
|
280
|
+
}
|
|
281
|
+
return lines.length > 0;
|
|
282
|
+
} catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function startStack(composeFile) {
|
|
287
|
+
try {
|
|
288
|
+
execFileSync("docker", ["compose", "-f", composeFile, "up", "-d"], {
|
|
289
|
+
stdio: "pipe",
|
|
290
|
+
timeout: 3e4
|
|
291
|
+
});
|
|
292
|
+
const services = getRunningServices(composeFile);
|
|
293
|
+
return {
|
|
294
|
+
started: true,
|
|
295
|
+
services
|
|
296
|
+
};
|
|
297
|
+
} catch (err) {
|
|
298
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
299
|
+
return {
|
|
300
|
+
started: false,
|
|
301
|
+
services: [],
|
|
302
|
+
error: message
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function startSharedStack() {
|
|
307
|
+
try {
|
|
308
|
+
ensureStackDir();
|
|
309
|
+
const composeFile = getComposeFilePath();
|
|
310
|
+
const otelConfigFile = getOtelConfigPath();
|
|
311
|
+
const composeContent = dockerComposeTemplate({ shared: true });
|
|
312
|
+
writeFileSync(composeFile, composeContent, "utf-8");
|
|
313
|
+
const otelContent = otelCollectorConfigTemplate();
|
|
314
|
+
writeFileSync(otelConfigFile, otelContent, "utf-8");
|
|
315
|
+
execFileSync("docker", ["compose", "-p", "codeharness-shared", "-f", composeFile, "up", "-d"], {
|
|
316
|
+
stdio: "pipe",
|
|
317
|
+
timeout: 3e4
|
|
318
|
+
});
|
|
319
|
+
const services = getRunningServices(composeFile, "codeharness-shared");
|
|
320
|
+
return {
|
|
321
|
+
started: true,
|
|
322
|
+
services
|
|
323
|
+
};
|
|
324
|
+
} catch (err) {
|
|
325
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
326
|
+
return {
|
|
327
|
+
started: false,
|
|
328
|
+
services: [],
|
|
329
|
+
error: message
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function stopStack(composeFile) {
|
|
334
|
+
execFileSync("docker", ["compose", "-f", composeFile, "down", "-v"], {
|
|
335
|
+
stdio: "pipe",
|
|
336
|
+
timeout: 3e4
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function stopSharedStack() {
|
|
340
|
+
const composeFile = getComposeFilePath();
|
|
341
|
+
execFileSync("docker", ["compose", "-p", "codeharness-shared", "-f", composeFile, "down"], {
|
|
342
|
+
stdio: "pipe",
|
|
343
|
+
timeout: 3e4
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
function startCollectorOnly(logsUrl, metricsUrl, tracesUrl) {
|
|
347
|
+
try {
|
|
348
|
+
ensureStackDir();
|
|
349
|
+
const composeFile = getComposeFilePath();
|
|
350
|
+
const otelConfigFile = getOtelConfigPath();
|
|
351
|
+
const composeContent = dockerComposeCollectorOnlyTemplate();
|
|
352
|
+
writeFileSync(composeFile, composeContent, "utf-8");
|
|
353
|
+
const otelContent = otelCollectorRemoteTemplate({ logsUrl, metricsUrl, tracesUrl });
|
|
354
|
+
writeFileSync(otelConfigFile, otelContent, "utf-8");
|
|
355
|
+
execFileSync("docker", ["compose", "-p", "codeharness-collector", "-f", composeFile, "up", "-d"], {
|
|
356
|
+
stdio: "pipe",
|
|
357
|
+
timeout: 3e4
|
|
358
|
+
});
|
|
359
|
+
const services = getRunningServices(composeFile, "codeharness-collector");
|
|
360
|
+
return {
|
|
361
|
+
started: true,
|
|
362
|
+
services
|
|
363
|
+
};
|
|
364
|
+
} catch (err) {
|
|
365
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
366
|
+
return {
|
|
367
|
+
started: false,
|
|
368
|
+
services: [],
|
|
369
|
+
error: message
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function isCollectorRunning() {
|
|
374
|
+
try {
|
|
375
|
+
const composeFile = getComposeFilePath();
|
|
376
|
+
const output = execFileSync("docker", ["compose", "-p", "codeharness-collector", "-f", composeFile, "ps", "--format", "json"], {
|
|
377
|
+
stdio: "pipe",
|
|
378
|
+
timeout: 15e3
|
|
379
|
+
});
|
|
380
|
+
const text = output.toString().trim();
|
|
381
|
+
if (!text) return false;
|
|
382
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
383
|
+
for (const line of lines) {
|
|
384
|
+
const svc = JSON.parse(line);
|
|
385
|
+
if (svc.State !== "running") return false;
|
|
386
|
+
}
|
|
387
|
+
return lines.length > 0;
|
|
388
|
+
} catch {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function stopCollectorOnly() {
|
|
393
|
+
const composeFile = getComposeFilePath();
|
|
394
|
+
execFileSync("docker", ["compose", "-p", "codeharness-collector", "-f", composeFile, "down"], {
|
|
395
|
+
stdio: "pipe",
|
|
396
|
+
timeout: 3e4
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function getCollectorHealth(composeFile) {
|
|
400
|
+
const expectedServices = ["otel-collector"];
|
|
401
|
+
try {
|
|
402
|
+
const output = execFileSync("docker", ["compose", "-p", "codeharness-collector", "-f", composeFile, "ps", "--format", "json"], {
|
|
403
|
+
stdio: "pipe",
|
|
404
|
+
timeout: 15e3
|
|
405
|
+
});
|
|
406
|
+
const text = output.toString().trim();
|
|
407
|
+
const runningNames = /* @__PURE__ */ new Set();
|
|
408
|
+
if (text) {
|
|
409
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
410
|
+
for (const line of lines) {
|
|
411
|
+
const svc = JSON.parse(line);
|
|
412
|
+
if (svc.State === "running" && svc.Service) {
|
|
413
|
+
runningNames.add(svc.Service);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const services = expectedServices.map((name) => ({
|
|
418
|
+
name,
|
|
419
|
+
running: runningNames.has(name)
|
|
420
|
+
}));
|
|
421
|
+
const healthy = services.every((s) => s.running);
|
|
422
|
+
return {
|
|
423
|
+
healthy,
|
|
424
|
+
services,
|
|
425
|
+
remedy: healthy ? void 0 : `Restart: docker compose -p codeharness-collector -f ${composeFile} up -d`
|
|
426
|
+
};
|
|
427
|
+
} catch {
|
|
428
|
+
const services = expectedServices.map((name) => ({
|
|
429
|
+
name,
|
|
430
|
+
running: false
|
|
431
|
+
}));
|
|
432
|
+
return {
|
|
433
|
+
healthy: false,
|
|
434
|
+
services,
|
|
435
|
+
remedy: `Restart: docker compose -p codeharness-collector -f ${composeFile} up -d`
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async function checkRemoteEndpoint(url) {
|
|
440
|
+
try {
|
|
441
|
+
const controller = new AbortController();
|
|
442
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
443
|
+
try {
|
|
444
|
+
await fetch(url, { signal: controller.signal });
|
|
445
|
+
return { reachable: true };
|
|
446
|
+
} finally {
|
|
447
|
+
clearTimeout(timeout);
|
|
448
|
+
}
|
|
449
|
+
} catch (err) {
|
|
450
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
451
|
+
return { reachable: false, error: message };
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function getStackHealth(composeFile, projectName) {
|
|
455
|
+
const expectedServices = ["victoria-logs", "victoria-metrics", "victoria-traces", "otel-collector"];
|
|
456
|
+
try {
|
|
457
|
+
const args = projectName ? ["compose", "-p", projectName, "-f", composeFile, "ps", "--format", "json"] : ["compose", "-f", composeFile, "ps", "--format", "json"];
|
|
458
|
+
const output = execFileSync("docker", args, {
|
|
459
|
+
stdio: "pipe",
|
|
460
|
+
timeout: 15e3
|
|
461
|
+
});
|
|
462
|
+
const text = output.toString().trim();
|
|
463
|
+
const runningNames = /* @__PURE__ */ new Set();
|
|
464
|
+
if (text) {
|
|
465
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
466
|
+
for (const line of lines) {
|
|
467
|
+
const svc = JSON.parse(line);
|
|
468
|
+
if (svc.State === "running" && svc.Service) {
|
|
469
|
+
runningNames.add(svc.Service);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const services = expectedServices.map((name) => ({
|
|
474
|
+
name,
|
|
475
|
+
running: runningNames.has(name)
|
|
476
|
+
}));
|
|
477
|
+
const healthy = services.every((s) => s.running);
|
|
478
|
+
const remedyCmd = projectName ? `docker compose -p ${projectName} -f ${composeFile} up -d` : `docker compose -f ${composeFile} up -d`;
|
|
479
|
+
return {
|
|
480
|
+
healthy,
|
|
481
|
+
services,
|
|
482
|
+
remedy: healthy ? void 0 : `Restart: ${remedyCmd}`
|
|
483
|
+
};
|
|
484
|
+
} catch {
|
|
485
|
+
const remedyCmd = projectName ? `docker compose -p ${projectName} -f ${composeFile} up -d` : `docker compose -f ${composeFile} up -d`;
|
|
486
|
+
const services = expectedServices.map((name) => ({
|
|
487
|
+
name,
|
|
488
|
+
running: false
|
|
489
|
+
}));
|
|
490
|
+
return {
|
|
491
|
+
healthy: false,
|
|
492
|
+
services,
|
|
493
|
+
remedy: `Restart: ${remedyCmd}`
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function getRunningServices(composeFile, projectName) {
|
|
498
|
+
try {
|
|
499
|
+
const args = projectName ? ["compose", "-p", projectName, "-f", composeFile, "ps", "--format", "json"] : ["compose", "-f", composeFile, "ps", "--format", "json"];
|
|
500
|
+
const output = execFileSync("docker", args, {
|
|
501
|
+
stdio: "pipe",
|
|
502
|
+
timeout: 15e3
|
|
503
|
+
});
|
|
504
|
+
const text = output.toString().trim();
|
|
505
|
+
if (!text) return [];
|
|
506
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
507
|
+
const services = [];
|
|
508
|
+
for (const line of lines) {
|
|
509
|
+
const svc = JSON.parse(line);
|
|
510
|
+
const ports = (svc.Publishers ?? []).filter((p) => p.PublishedPort && p.PublishedPort > 0).map((p) => String(p.PublishedPort));
|
|
511
|
+
services.push({
|
|
512
|
+
name: svc.Service ?? "unknown",
|
|
513
|
+
status: svc.State ?? "unknown",
|
|
514
|
+
port: ports.join(",")
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
return services;
|
|
518
|
+
} catch {
|
|
519
|
+
return [];
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export {
|
|
524
|
+
getStackDir,
|
|
525
|
+
getComposeFilePath,
|
|
526
|
+
isDockerAvailable,
|
|
527
|
+
isDockerComposeAvailable,
|
|
528
|
+
isStackRunning,
|
|
529
|
+
isSharedStackRunning,
|
|
530
|
+
startStack,
|
|
531
|
+
startSharedStack,
|
|
532
|
+
stopStack,
|
|
533
|
+
stopSharedStack,
|
|
534
|
+
startCollectorOnly,
|
|
535
|
+
isCollectorRunning,
|
|
536
|
+
stopCollectorOnly,
|
|
537
|
+
getCollectorHealth,
|
|
538
|
+
checkRemoteEndpoint,
|
|
539
|
+
getStackHealth
|
|
540
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkRemoteEndpoint,
|
|
4
|
+
getCollectorHealth,
|
|
5
|
+
getStackHealth,
|
|
6
|
+
isCollectorRunning,
|
|
7
|
+
isDockerAvailable,
|
|
8
|
+
isDockerComposeAvailable,
|
|
9
|
+
isSharedStackRunning,
|
|
10
|
+
isStackRunning,
|
|
11
|
+
startCollectorOnly,
|
|
12
|
+
startSharedStack,
|
|
13
|
+
startStack,
|
|
14
|
+
stopCollectorOnly,
|
|
15
|
+
stopSharedStack,
|
|
16
|
+
stopStack
|
|
17
|
+
} from "./chunk-7ZD2ZNDU.js";
|
|
18
|
+
export {
|
|
19
|
+
checkRemoteEndpoint,
|
|
20
|
+
getCollectorHealth,
|
|
21
|
+
getStackHealth,
|
|
22
|
+
isCollectorRunning,
|
|
23
|
+
isDockerAvailable,
|
|
24
|
+
isDockerComposeAvailable,
|
|
25
|
+
isSharedStackRunning,
|
|
26
|
+
isStackRunning,
|
|
27
|
+
startCollectorOnly,
|
|
28
|
+
startSharedStack,
|
|
29
|
+
startStack,
|
|
30
|
+
stopCollectorOnly,
|
|
31
|
+
stopSharedStack,
|
|
32
|
+
stopStack
|
|
33
|
+
};
|