kantban-cli 0.1.45 → 0.1.47
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/dist/{chunk-MKKHLFA5.js → chunk-O47Q6CNM.js} +24 -8
- package/dist/chunk-O47Q6CNM.js.map +1 -0
- package/dist/{chunk-N4ZHMJD7.js → chunk-UKWQ6VUQ.js} +2 -2
- package/dist/{chunk-DAFLEMLK.js → chunk-VDCHOIQI.js} +10 -3
- package/dist/chunk-VDCHOIQI.js.map +1 -0
- package/dist/{cron-QUEYUVIX.js → cron-TLNKEKOW.js} +3 -3
- package/dist/index.js +3 -3
- package/dist/lib/gate-proxy-server.js +34 -4
- package/dist/lib/gate-proxy-server.js.map +1 -1
- package/dist/{pipeline-GOM4OKNV.js → pipeline-EUOOLKHN.js} +19 -5
- package/dist/pipeline-EUOOLKHN.js.map +1 -0
- package/dist/{pipeline-init-IGZZOOLK.js → pipeline-init-AUKPFJYE.js} +5 -2
- package/dist/pipeline-init-AUKPFJYE.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-DAFLEMLK.js.map +0 -1
- package/dist/chunk-MKKHLFA5.js.map +0 -1
- package/dist/pipeline-GOM4OKNV.js.map +0 -1
- package/dist/pipeline-init-IGZZOOLK.js.map +0 -1
- /package/dist/{chunk-N4ZHMJD7.js.map → chunk-UKWQ6VUQ.js.map} +0 -0
- /package/dist/{cron-QUEYUVIX.js.map → cron-TLNKEKOW.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseTimeout
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VDCHOIQI.js";
|
|
4
4
|
import {
|
|
5
5
|
normalizeEol,
|
|
6
6
|
shellArgs
|
|
@@ -13,20 +13,21 @@ async function runGate(gate, options = {}) {
|
|
|
13
13
|
const start = Date.now();
|
|
14
14
|
return new Promise((resolve) => {
|
|
15
15
|
const [shell, shellPrefix] = shellArgs();
|
|
16
|
+
let timedOut = false;
|
|
17
|
+
let timer;
|
|
16
18
|
const child = execFile(shell, [...shellPrefix, gate.run], {
|
|
17
|
-
timeout: timeoutMs,
|
|
18
19
|
cwd: options.cwd,
|
|
19
20
|
env: { ...process.env, ...options.env },
|
|
20
21
|
maxBuffer: options.maxBuffer ?? 1024 * 1024
|
|
21
22
|
// 1MB output cap
|
|
22
23
|
}, (error, stdout, stderr) => {
|
|
24
|
+
if (timer) clearTimeout(timer);
|
|
23
25
|
const duration_ms = Date.now() - start;
|
|
24
26
|
const output = normalizeEol((stdout + stderr).trim());
|
|
25
|
-
const timed_out = error?.killed === true;
|
|
26
27
|
const buffer_exceeded = error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
|
|
27
|
-
if (error) {
|
|
28
|
+
if (error || timedOut) {
|
|
28
29
|
let annotation = "";
|
|
29
|
-
if (
|
|
30
|
+
if (timedOut) annotation = `
|
|
30
31
|
[TIMED OUT after ${timeoutMs}ms]`;
|
|
31
32
|
else if (buffer_exceeded) annotation = `
|
|
32
33
|
[OUTPUT TRUNCATED \u2014 buffer limit exceeded]`;
|
|
@@ -37,8 +38,8 @@ async function runGate(gate, options = {}) {
|
|
|
37
38
|
duration_ms,
|
|
38
39
|
output: output + annotation,
|
|
39
40
|
stderr: normalizeEol(stderr.trim()),
|
|
40
|
-
exit_code: child.exitCode ?? (typeof error
|
|
41
|
-
timed_out:
|
|
41
|
+
exit_code: child.exitCode ?? (typeof error?.code === "number" ? error.code : timedOut ? 124 : 1),
|
|
42
|
+
timed_out: timedOut || buffer_exceeded
|
|
42
43
|
});
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
@@ -53,6 +54,21 @@ async function runGate(gate, options = {}) {
|
|
|
53
54
|
timed_out: false
|
|
54
55
|
});
|
|
55
56
|
});
|
|
57
|
+
timer = setTimeout(() => {
|
|
58
|
+
timedOut = true;
|
|
59
|
+
try {
|
|
60
|
+
child.kill("SIGKILL");
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
child.stdout?.destroy();
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
child.stderr?.destroy();
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}, timeoutMs);
|
|
56
72
|
});
|
|
57
73
|
}
|
|
58
74
|
function formatGateErrors(results) {
|
|
@@ -104,4 +120,4 @@ export {
|
|
|
104
120
|
formatGateErrors,
|
|
105
121
|
runGates
|
|
106
122
|
};
|
|
107
|
-
//# sourceMappingURL=chunk-
|
|
123
|
+
//# sourceMappingURL=chunk-O47Q6CNM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/gate-runner.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { parseTimeout } from './gate-config.js';\nimport { shellArgs, normalizeEol } from './platform.js';\nimport type { GateDefinition, GateResult } from '@kantban/types';\n\nexport interface RunOptions {\n timeoutMs?: number;\n totalTimeoutMs?: number;\n cwd?: string;\n env?: Record<string, string>;\n maxBuffer?: number;\n}\n\nexport async function runGate(\n gate: GateDefinition,\n options: RunOptions = {},\n): Promise<GateResult> {\n const timeoutMs = gate.timeout ? parseTimeout(gate.timeout) : (options.timeoutMs ?? 60_000);\n const start = Date.now();\n\n return new Promise<GateResult>((resolve) => {\n // SECURITY: gate.run is an operator-authored shell command from pipeline.gates.yaml.\n // It is executed with full process privileges via `sh -c`. This is intentional —\n // gates are trusted operator code, not user input. If gate config sources are ever\n // extended to accept user-supplied values, sanitization must be added here.\n const [shell, shellPrefix] = shellArgs();\n\n // We deliberately do NOT pass execFile's `timeout` option. Node's built-in\n // timeout sends SIGTERM to the child but keeps the callback pending until\n // stdio pipes close. If the gate forks a grandchild that inherits\n // stdout/stderr (e.g. `npm run dev:pipeline &`, a stray browser, a vite\n // dev server), those pipes never close and the callback never fires. We\n // manage the timeout ourselves and force-destroy the pipes on expiration\n // so the promise always resolves — which is what keeps the gate-proxy\n // MCP message queue from deadlocking behind a hung gate.\n let timedOut = false;\n let timer: NodeJS.Timeout | undefined;\n\n const child = execFile(shell, [...shellPrefix, gate.run], {\n cwd: options.cwd,\n env: { ...process.env, ...options.env },\n maxBuffer: options.maxBuffer ?? 1024 * 1024, // 1MB output cap\n }, (error, stdout, stderr) => {\n if (timer) clearTimeout(timer);\n const duration_ms = Date.now() - start;\n const output = normalizeEol((stdout + stderr).trim());\n const buffer_exceeded = (error as NodeJS.ErrnoException)?.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER';\n\n if (error || timedOut) {\n let annotation = '';\n if (timedOut) annotation = `\\n[TIMED OUT after ${timeoutMs}ms]`;\n else if (buffer_exceeded) annotation = `\\n[OUTPUT TRUNCATED — buffer limit exceeded]`;\n\n resolve({\n name: gate.name,\n passed: false,\n required: gate.required ?? true,\n duration_ms,\n output: output + annotation,\n stderr: normalizeEol(stderr.trim()),\n exit_code:\n child.exitCode\n ?? (typeof error?.code === 'number' ? error.code : timedOut ? 124 : 1),\n timed_out: timedOut || buffer_exceeded,\n });\n return;\n }\n\n resolve({\n name: gate.name,\n passed: true,\n required: gate.required ?? true,\n duration_ms,\n output,\n stderr: normalizeEol(stderr.trim()),\n exit_code: 0,\n timed_out: false,\n });\n });\n\n timer = setTimeout(() => {\n timedOut = true;\n try { child.kill('SIGKILL'); } catch { /* already exited */ }\n try { child.stdout?.destroy(); } catch { /* already destroyed */ }\n try { child.stderr?.destroy(); } catch { /* already destroyed */ }\n }, timeoutMs);\n });\n}\n\n/** Format gate failures into a structured error string for agent/CLI display. */\nexport function formatGateErrors(results: GateResult[]): string {\n const failures = results.filter((r) => !r.passed);\n if (failures.length === 0) return 'All gates passed.';\n\n return failures\n .map((r) => {\n const lines = [`Gate \"${r.name}\" (${r.required ? 'required' : 'advisory'}): FAILED`];\n lines.push(` Exit code: ${r.exit_code}`);\n if (r.timed_out) lines.push(' Timed out: yes');\n if (r.output) {\n const stderr = normalizeEol(r.output).split('\\n').slice(-20).join('\\n');\n lines.push(` Output:\\n ${stderr.replace(/\\n/g, '\\n ')}`);\n }\n return lines.join('\\n');\n })\n .join('\\n\\n');\n}\n\nexport async function runGates(\n gates: GateDefinition[],\n options: RunOptions = {},\n): Promise<GateResult[]> {\n const results: GateResult[] = [];\n const totalStart = Date.now();\n\n for (const gate of gates) {\n // Check total timeout\n if (options.totalTimeoutMs) {\n const elapsed = Date.now() - totalStart;\n if (elapsed >= options.totalTimeoutMs) {\n // Remaining gates get timed-out results\n results.push({\n name: gate.name,\n passed: false,\n required: gate.required ?? true,\n duration_ms: 0,\n output: '[SKIPPED — total timeout exceeded]',\n stderr: '',\n exit_code: -1,\n timed_out: true,\n });\n continue;\n }\n // Reduce per-gate timeout by elapsed time\n const remainingMs = options.totalTimeoutMs - elapsed;\n const gateTimeout = gate.timeout ? parseTimeout(gate.timeout) : (options.timeoutMs ?? 60_000);\n const effectiveTimeout = Math.min(gateTimeout, remainingMs);\n results.push(await runGate(gate, { ...options, timeoutMs: effectiveTimeout }));\n } else {\n results.push(await runGate(gate, options));\n }\n }\n\n return results;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VALID_BRANCH_RE
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VDCHOIQI.js";
|
|
4
4
|
import {
|
|
5
5
|
crossSpawnOptions,
|
|
6
6
|
defaultPath,
|
|
@@ -1195,4 +1195,4 @@ export {
|
|
|
1195
1195
|
cleanupGateProxyConfigs,
|
|
1196
1196
|
ClaudeProvider
|
|
1197
1197
|
};
|
|
1198
|
-
//# sourceMappingURL=chunk-
|
|
1198
|
+
//# sourceMappingURL=chunk-UKWQ6VUQ.js.map
|
|
@@ -3097,6 +3097,7 @@ var VerdictSchema = z51.object({
|
|
|
3097
3097
|
// src/lib/gate-config.ts
|
|
3098
3098
|
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
3099
3099
|
var VALID_BRANCH_RE = /^[a-zA-Z0-9][a-zA-Z0-9._\-/]*$/;
|
|
3100
|
+
var canonicalize = (s) => s.toLowerCase().replace(/[\s_-]+/g, "-");
|
|
3100
3101
|
function assertNoPrototypePollutionKeys(value, depth = 0) {
|
|
3101
3102
|
if (depth > 20 || value === null || typeof value !== "object") return;
|
|
3102
3103
|
for (const key of Object.keys(value)) {
|
|
@@ -3112,8 +3113,8 @@ function parseGateConfig(yamlContent) {
|
|
|
3112
3113
|
return GateConfigSchema.parse(raw);
|
|
3113
3114
|
}
|
|
3114
3115
|
function resolveGatesForColumn(config, columnName) {
|
|
3115
|
-
const
|
|
3116
|
-
const overrideKey = config.columns ? Object.keys(config.columns).find((k) => k
|
|
3116
|
+
const lookupKey = canonicalize(columnName);
|
|
3117
|
+
const overrideKey = config.columns ? Object.keys(config.columns).find((k) => canonicalize(k) === lookupKey) : void 0;
|
|
3117
3118
|
const override = overrideKey ? config.columns[overrideKey] : void 0;
|
|
3118
3119
|
if (!override) {
|
|
3119
3120
|
return [...config.default];
|
|
@@ -3128,6 +3129,11 @@ function resolveGatesForColumn(config, columnName) {
|
|
|
3128
3129
|
const merged = config.default.filter((g) => !overrideNames.has(g.name));
|
|
3129
3130
|
return [...merged, ...override.gates];
|
|
3130
3131
|
}
|
|
3132
|
+
function findUnmatchedColumnKeys(config, boardColumnNames) {
|
|
3133
|
+
if (!config.columns) return [];
|
|
3134
|
+
const canonicalBoard = new Set(boardColumnNames.map(canonicalize));
|
|
3135
|
+
return Object.keys(config.columns).filter((k) => !canonicalBoard.has(canonicalize(k)));
|
|
3136
|
+
}
|
|
3131
3137
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
3132
3138
|
function parseTimeout(timeout) {
|
|
3133
3139
|
if (!timeout) return DEFAULT_TIMEOUT_MS;
|
|
@@ -3144,6 +3150,7 @@ export {
|
|
|
3144
3150
|
VALID_BRANCH_RE,
|
|
3145
3151
|
parseGateConfig,
|
|
3146
3152
|
resolveGatesForColumn,
|
|
3153
|
+
findUnmatchedColumnKeys,
|
|
3147
3154
|
parseTimeout
|
|
3148
3155
|
};
|
|
3149
|
-
//# sourceMappingURL=chunk-
|
|
3156
|
+
//# sourceMappingURL=chunk-VDCHOIQI.js.map
|