kandev 0.10.0 → 0.12.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/dist/cli.js +12 -5
- package/dist/constants.js +1 -1
- package/dist/dev.js +2 -1
- package/dist/health.js +7 -3
- package/dist/run.js +49 -10
- package/dist/shared.js +3 -0
- package/dist/start.js +4 -7
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -15,10 +15,10 @@ function printHelp() {
|
|
|
15
15
|
console.log(`kandev launcher
|
|
16
16
|
|
|
17
17
|
Usage:
|
|
18
|
-
kandev run [--version <tag>] [--backend-port <port>] [--web-port <port>]
|
|
18
|
+
kandev run [--version <tag>] [--backend-port <port>] [--web-port <port>] [--verbose] [--debug]
|
|
19
19
|
kandev dev [--backend-port <port>] [--web-port <port>]
|
|
20
20
|
kandev start [--backend-port <port>] [--web-port <port>] [--verbose] [--debug]
|
|
21
|
-
kandev [--version <tag>] [--backend-port <port>] [--web-port <port>]
|
|
21
|
+
kandev [--version <tag>] [--backend-port <port>] [--web-port <port>] [--verbose] [--debug]
|
|
22
22
|
kandev --dev [--backend-port <port>] [--web-port <port>]
|
|
23
23
|
|
|
24
24
|
Examples:
|
|
@@ -29,6 +29,7 @@ Examples:
|
|
|
29
29
|
kandev start
|
|
30
30
|
kandev --version v0.1.0
|
|
31
31
|
kandev --backend-port 18080 --web-port 13000
|
|
32
|
+
kandev --debug
|
|
32
33
|
|
|
33
34
|
Options:
|
|
34
35
|
dev Use local repo for dev (make dev + next dev) if available.
|
|
@@ -38,8 +39,8 @@ Options:
|
|
|
38
39
|
--version Release tag to install (default: latest).
|
|
39
40
|
--backend-port Override backend port (or KANDEV_BACKEND_PORT env var).
|
|
40
41
|
--web-port Override web port (or KANDEV_WEB_PORT env var).
|
|
41
|
-
--verbose, -v Show info logs from backend + web
|
|
42
|
-
--debug Show debug logs + agent message dumps
|
|
42
|
+
--verbose, -v Show info logs from backend + web.
|
|
43
|
+
--debug Show debug logs + agent message dumps.
|
|
43
44
|
--help, -h Show help.
|
|
44
45
|
`);
|
|
45
46
|
}
|
|
@@ -144,7 +145,13 @@ async function main() {
|
|
|
144
145
|
return;
|
|
145
146
|
}
|
|
146
147
|
await (0, update_1.maybePromptForUpdate)(package_json_1.default.version, process.argv.slice(2));
|
|
147
|
-
await (0, run_1.runRelease)({
|
|
148
|
+
await (0, run_1.runRelease)({
|
|
149
|
+
version: raw.version,
|
|
150
|
+
backendPort,
|
|
151
|
+
webPort,
|
|
152
|
+
verbose: raw.verbose,
|
|
153
|
+
debug: raw.debug,
|
|
154
|
+
});
|
|
148
155
|
}
|
|
149
156
|
main().catch((err) => {
|
|
150
157
|
console.error(`[kandev] ${err instanceof Error ? err.message : String(err)}`);
|
package/dist/constants.js
CHANGED
|
@@ -16,7 +16,7 @@ exports.RANDOM_PORT_MIN = 10000;
|
|
|
16
16
|
exports.RANDOM_PORT_MAX = 60000;
|
|
17
17
|
exports.RANDOM_PORT_RETRIES = 10;
|
|
18
18
|
// Backend healthcheck timeout during startup.
|
|
19
|
-
exports.HEALTH_TIMEOUT_MS_RELEASE =
|
|
19
|
+
exports.HEALTH_TIMEOUT_MS_RELEASE = 45000;
|
|
20
20
|
exports.HEALTH_TIMEOUT_MS_DEV = 600000;
|
|
21
21
|
// Local user cache/data directories for release bundles and DB.
|
|
22
22
|
exports.CACHE_DIR = node_path_1.default.join(node_os_1.default.homedir(), ".kandev", "bin");
|
package/dist/dev.js
CHANGED
|
@@ -13,10 +13,11 @@ const shared_1 = require("./shared");
|
|
|
13
13
|
const web_1 = require("./web");
|
|
14
14
|
async function runDev({ repoRoot, backendPort, webPort }) {
|
|
15
15
|
const ports = await (0, shared_1.pickPorts)(backendPort, webPort);
|
|
16
|
-
const dbPath = node_path_1.default.join(repoRoot, "apps", "backend", "kandev.db");
|
|
16
|
+
const dbPath = process.env.KANDEV_DATABASE_PATH || node_path_1.default.join(repoRoot, "apps", "backend", "kandev.db");
|
|
17
17
|
const extra = {
|
|
18
18
|
KANDEV_DATABASE_PATH: dbPath,
|
|
19
19
|
KANDEV_MOCK_AGENT: process.env.KANDEV_MOCK_AGENT || "true",
|
|
20
|
+
KANDEV_DEBUG_PPROF_ENABLED: "true",
|
|
20
21
|
};
|
|
21
22
|
const backendEnv = (0, shared_1.buildBackendEnv)({ ports, extra });
|
|
22
23
|
const webEnv = (0, shared_1.buildWebEnv)({ ports, debug: true });
|
package/dist/health.js
CHANGED
|
@@ -28,12 +28,13 @@ function resolveHealthTimeoutMs(defaultMs) {
|
|
|
28
28
|
}
|
|
29
29
|
return Math.floor(parsed);
|
|
30
30
|
}
|
|
31
|
-
async function waitForHealth(baseUrl, proc, timeoutMs) {
|
|
31
|
+
async function waitForHealth(baseUrl, proc, timeoutMs, onFailure) {
|
|
32
32
|
const deadline = Date.now() + timeoutMs;
|
|
33
33
|
const healthUrl = `${baseUrl}/health`;
|
|
34
34
|
while (Date.now() < deadline) {
|
|
35
35
|
if (proc.exitCode !== null) {
|
|
36
|
-
|
|
36
|
+
onFailure?.();
|
|
37
|
+
throw new Error(`Backend exited (code ${proc.exitCode}) before healthcheck passed`);
|
|
37
38
|
}
|
|
38
39
|
try {
|
|
39
40
|
const res = await fetch(healthUrl);
|
|
@@ -46,7 +47,10 @@ async function waitForHealth(baseUrl, proc, timeoutMs) {
|
|
|
46
47
|
}
|
|
47
48
|
await delay(300);
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
+
onFailure?.();
|
|
51
|
+
throw new Error(`Backend healthcheck timed out after ${timeoutMs}ms at ${healthUrl}. ` +
|
|
52
|
+
`If your machine is slow on first run (antivirus scan, cold disk), ` +
|
|
53
|
+
`set KANDEV_HEALTH_TIMEOUT_MS=120000 and retry.`);
|
|
50
54
|
}
|
|
51
55
|
async function waitForUrlReady(url, proc, timeoutMs) {
|
|
52
56
|
const deadline = Date.now() + timeoutMs;
|
package/dist/run.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.findCachedRelease = findCachedRelease;
|
|
7
7
|
exports.cleanOldReleases = cleanOldReleases;
|
|
8
|
+
exports.attachRingBuffer = attachRingBuffer;
|
|
8
9
|
exports.runRelease = runRelease;
|
|
9
10
|
const node_child_process_1 = require("node:child_process");
|
|
10
11
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -76,7 +77,7 @@ function cleanOldReleases(currentTag) {
|
|
|
76
77
|
// Non-critical — don't fail the launch if cleanup errors.
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
async function prepareReleaseBundle({ version, backendPort, webPort, }) {
|
|
80
|
+
async function prepareReleaseBundle({ version, backendPort, webPort, verbose = false, debug = false, }) {
|
|
80
81
|
const platformDir = (0, platform_1.getPlatformDir)();
|
|
81
82
|
let tag;
|
|
82
83
|
let cacheDir;
|
|
@@ -122,13 +123,14 @@ async function prepareReleaseBundle({ version, backendPort, webPort, }) {
|
|
|
122
123
|
const actualWebPort = webPort ?? (await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_WEB_PORT));
|
|
123
124
|
const agentctlPort = await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_AGENTCTL_PORT);
|
|
124
125
|
const backendUrl = `http://localhost:${actualBackendPort}`;
|
|
125
|
-
const
|
|
126
|
+
const showOutput = verbose || debug;
|
|
127
|
+
const logLevel = process.env.KANDEV_LOG_LEVEL?.trim() || (debug ? "debug" : verbose ? "info" : "warn");
|
|
126
128
|
node_fs_1.default.mkdirSync(constants_1.DATA_DIR, { recursive: true });
|
|
127
129
|
const dbPath = node_path_1.default.join(constants_1.DATA_DIR, "kandev.db");
|
|
128
130
|
// Note: Release mode doesn't configure MCP server ports as it uses
|
|
129
131
|
// the bundled configuration. Only backend and agentctl ports are set.
|
|
130
132
|
// Log level defaults to warn for clean output and can be overridden
|
|
131
|
-
// via KANDEV_LOG_LEVEL.
|
|
133
|
+
// via KANDEV_LOG_LEVEL or --verbose/--debug flags.
|
|
132
134
|
const backendEnv = {
|
|
133
135
|
...process.env,
|
|
134
136
|
KANDEV_SERVER_PORT: String(actualBackendPort),
|
|
@@ -136,11 +138,15 @@ async function prepareReleaseBundle({ version, backendPort, webPort, }) {
|
|
|
136
138
|
KANDEV_AGENT_STANDALONE_PORT: String(agentctlPort),
|
|
137
139
|
KANDEV_DATABASE_PATH: dbPath,
|
|
138
140
|
KANDEV_LOG_LEVEL: logLevel,
|
|
141
|
+
...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true", KANDEV_DEBUG_PPROF_ENABLED: "true" } : {}),
|
|
139
142
|
};
|
|
140
143
|
const webEnv = {
|
|
141
144
|
...process.env,
|
|
142
145
|
KANDEV_API_BASE_URL: backendUrl,
|
|
143
146
|
PORT: String(actualWebPort),
|
|
147
|
+
// Ensure Next.js standalone server binds to 127.0.0.1 so localhost health checks work.
|
|
148
|
+
// Without this, HOSTNAME from the host environment can cause binding issues.
|
|
149
|
+
HOSTNAME: "127.0.0.1",
|
|
144
150
|
};
|
|
145
151
|
webEnv.NODE_ENV = "production";
|
|
146
152
|
return {
|
|
@@ -155,8 +161,24 @@ async function prepareReleaseBundle({ version, backendPort, webPort, }) {
|
|
|
155
161
|
agentctlPort,
|
|
156
162
|
dbPath,
|
|
157
163
|
logLevel,
|
|
164
|
+
showOutput,
|
|
158
165
|
};
|
|
159
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Attach a ring buffer to a readable stream, keeping roughly the last `maxChars`
|
|
169
|
+
* characters. Note: the limit is measured in JS string length (UTF-16 code units),
|
|
170
|
+
* not bytes — fine for log output which is overwhelmingly ASCII.
|
|
171
|
+
*/
|
|
172
|
+
function attachRingBuffer(stream, maxChars = 64 * 1024) {
|
|
173
|
+
let buf = "";
|
|
174
|
+
stream?.on("data", (chunk) => {
|
|
175
|
+
buf += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
176
|
+
if (buf.length > maxChars) {
|
|
177
|
+
buf = buf.slice(buf.length - maxChars);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return () => buf;
|
|
181
|
+
}
|
|
160
182
|
function launchReleaseApps(prepared) {
|
|
161
183
|
const releaseSource = prepared.requestedVersion
|
|
162
184
|
? `(requested: ${prepared.requestedVersion})`
|
|
@@ -176,25 +198,42 @@ function launchReleaseApps(prepared) {
|
|
|
176
198
|
});
|
|
177
199
|
const supervisor = (0, process_1.createProcessSupervisor)();
|
|
178
200
|
supervisor.attachSignalHandlers();
|
|
201
|
+
// Start backend: ignore stdin, always show stderr immediately.
|
|
202
|
+
// In verbose/debug mode stream stdout live; otherwise capture it into a ring
|
|
203
|
+
// buffer so we can dump the last few KB if the healthcheck fails (users were
|
|
204
|
+
// previously seeing opaque "timed out" errors with no backend context).
|
|
179
205
|
const backendProc = (0, node_child_process_1.spawn)(prepared.backendBin, [], {
|
|
180
206
|
cwd: node_path_1.default.dirname(prepared.backendBin),
|
|
181
207
|
env: prepared.backendEnv,
|
|
182
|
-
stdio: "inherit",
|
|
208
|
+
stdio: prepared.showOutput ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "inherit"],
|
|
183
209
|
});
|
|
184
210
|
supervisor.children.push(backendProc);
|
|
211
|
+
const readBuffered = prepared.showOutput ? () => "" : attachRingBuffer(backendProc.stdout);
|
|
212
|
+
let dumped = false;
|
|
213
|
+
const dumpBackendLogs = () => {
|
|
214
|
+
if (dumped)
|
|
215
|
+
return;
|
|
216
|
+
dumped = true;
|
|
217
|
+
const buffered = readBuffered();
|
|
218
|
+
if (buffered.trim().length === 0)
|
|
219
|
+
return;
|
|
220
|
+
console.error("[kandev] --- backend stdout (last captured output) ---");
|
|
221
|
+
console.error(buffered.trimEnd());
|
|
222
|
+
console.error("[kandev] --- end backend stdout ---");
|
|
223
|
+
};
|
|
185
224
|
(0, shared_1.attachBackendExitHandler)(backendProc, supervisor);
|
|
186
225
|
const webServerPath = (0, bundle_1.resolveWebServerPath)(prepared.bundleDir);
|
|
187
226
|
if (!webServerPath) {
|
|
188
227
|
throw new Error("Web server entry (server.js) not found in bundle");
|
|
189
228
|
}
|
|
190
|
-
return { supervisor, backendProc, webServerPath };
|
|
229
|
+
return { supervisor, backendProc, webServerPath, dumpBackendLogs };
|
|
191
230
|
}
|
|
192
|
-
async function runRelease({ version, backendPort, webPort }) {
|
|
193
|
-
const prepared = await prepareReleaseBundle({ version, backendPort, webPort });
|
|
194
|
-
const { supervisor, backendProc, webServerPath } = launchReleaseApps(prepared);
|
|
231
|
+
async function runRelease({ version, backendPort, webPort, verbose = false, debug = false, }) {
|
|
232
|
+
const prepared = await prepareReleaseBundle({ version, backendPort, webPort, verbose, debug });
|
|
233
|
+
const { supervisor, backendProc, webServerPath, dumpBackendLogs } = launchReleaseApps(prepared);
|
|
195
234
|
const healthTimeoutMs = (0, health_1.resolveHealthTimeoutMs)(constants_1.HEALTH_TIMEOUT_MS_RELEASE);
|
|
196
235
|
console.log("[kandev] starting backend...");
|
|
197
|
-
await (0, health_1.waitForHealth)(prepared.backendUrl, backendProc, healthTimeoutMs);
|
|
236
|
+
await (0, health_1.waitForHealth)(prepared.backendUrl, backendProc, healthTimeoutMs, dumpBackendLogs);
|
|
198
237
|
console.log(`[kandev] backend ready at ${prepared.backendUrl}`);
|
|
199
238
|
const webUrl = `http://localhost:${prepared.webPort}`;
|
|
200
239
|
console.log("[kandev] starting web...");
|
|
@@ -205,7 +244,7 @@ async function runRelease({ version, backendPort, webPort }) {
|
|
|
205
244
|
env: prepared.webEnv,
|
|
206
245
|
supervisor,
|
|
207
246
|
label: "web",
|
|
208
|
-
quiet:
|
|
247
|
+
quiet: !prepared.showOutput,
|
|
209
248
|
});
|
|
210
249
|
await (0, health_1.waitForUrlReady)(webUrl, webProc, healthTimeoutMs);
|
|
211
250
|
console.log("[kandev] ready at " + prepared.backendUrl);
|
package/dist/shared.js
CHANGED
|
@@ -68,6 +68,9 @@ function buildWebEnv(options) {
|
|
|
68
68
|
// In production, the backend reverse-proxies Next.js so the client uses same-origin.
|
|
69
69
|
NEXT_PUBLIC_KANDEV_API_PORT: String(ports.backendPort),
|
|
70
70
|
PORT: String(ports.webPort),
|
|
71
|
+
// Ensure Next.js standalone server binds to 127.0.0.1 so localhost health checks work.
|
|
72
|
+
// Without this, HOSTNAME from the host environment can cause binding issues.
|
|
73
|
+
HOSTNAME: "127.0.0.1",
|
|
71
74
|
};
|
|
72
75
|
if (production) {
|
|
73
76
|
env.NODE_ENV = "production";
|
package/dist/start.js
CHANGED
|
@@ -81,7 +81,7 @@ async function runStart({ repoRoot, backendPort, webPort, verbose = false, debug
|
|
|
81
81
|
logLevel,
|
|
82
82
|
extra: {
|
|
83
83
|
KANDEV_DATABASE_PATH: dbPath,
|
|
84
|
-
...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true" } : {}),
|
|
84
|
+
...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true", KANDEV_DEBUG_PPROF_ENABLED: "true" } : {}),
|
|
85
85
|
},
|
|
86
86
|
});
|
|
87
87
|
const webEnv = (0, shared_1.buildWebEnv)({ ports, production: true, debug });
|
|
@@ -93,17 +93,14 @@ async function runStart({ repoRoot, backendPort, webPort, verbose = false, debug
|
|
|
93
93
|
});
|
|
94
94
|
const supervisor = (0, process_1.createProcessSupervisor)();
|
|
95
95
|
supervisor.attachSignalHandlers();
|
|
96
|
-
// Start backend
|
|
96
|
+
// Start backend: ignore stdin, show stdout only in verbose/debug mode, always show stderr
|
|
97
|
+
// Stderr is always inherited to ensure error messages are visible immediately (no pipe buffering)
|
|
97
98
|
const backendProc = (0, node_child_process_1.spawn)(backendBin, [], {
|
|
98
99
|
cwd: node_path_1.default.dirname(backendBin),
|
|
99
100
|
env: backendEnv,
|
|
100
|
-
stdio: showOutput ? ["ignore", "inherit", "inherit"] : ["ignore", "
|
|
101
|
+
stdio: showOutput ? ["ignore", "inherit", "inherit"] : ["ignore", "ignore", "inherit"],
|
|
101
102
|
});
|
|
102
103
|
supervisor.children.push(backendProc);
|
|
103
|
-
// Forward stderr only (warnings/errors) when quiet
|
|
104
|
-
if (!showOutput) {
|
|
105
|
-
backendProc.stderr?.pipe(process.stderr);
|
|
106
|
-
}
|
|
107
104
|
(0, shared_1.attachBackendExitHandler)(backendProc, supervisor);
|
|
108
105
|
const healthTimeoutMs = (0, health_1.resolveHealthTimeoutMs)(constants_1.HEALTH_TIMEOUT_MS_RELEASE);
|
|
109
106
|
console.log("[kandev] starting backend...");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kandev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "NPX launcher for Kandev",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"dist"
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"tar": "^7.5.
|
|
17
|
+
"tar": "^7.5.11",
|
|
18
18
|
"tree-kill": "^1.2.2"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|