kandev 0.65.0 → 0.67.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/shared.js DELETED
@@ -1,214 +0,0 @@
1
- "use strict";
2
- /**
3
- * Shared utilities for CLI commands (dev, start, run).
4
- *
5
- * This module extracts common patterns used across different launch modes
6
- * to reduce duplication and ensure consistent behavior.
7
- */
8
- var __importDefault = (this && this.__importDefault) || function (mod) {
9
- return (mod && mod.__esModule) ? mod : { "default": mod };
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.pickPorts = pickPorts;
13
- exports.buildBackendEnv = buildBackendEnv;
14
- exports.buildWebEnv = buildWebEnv;
15
- exports.listHostNetworkAddresses = listHostNetworkAddresses;
16
- exports.logStartupInfo = logStartupInfo;
17
- exports.networkUrlsForPort = networkUrlsForPort;
18
- exports.attachBackendExitHandler = attachBackendExitHandler;
19
- const node_os_1 = __importDefault(require("node:os"));
20
- const constants_1 = require("./constants");
21
- const ports_1 = require("./ports");
22
- /**
23
- * Picks available ports for all services, using provided values or finding free ports.
24
- *
25
- * @param backendPort - Optional preferred backend port
26
- * @param webPort - Optional preferred web port
27
- * @returns Resolved ports for all services
28
- */
29
- async function pickPorts(backendPort, webPort) {
30
- const resolvedBackendPort = backendPort ?? (await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_BACKEND_PORT));
31
- const resolvedWebPort = webPort ?? (await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_WEB_PORT));
32
- const agentctlPort = await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_AGENTCTL_PORT);
33
- return {
34
- backendPort: resolvedBackendPort,
35
- webPort: resolvedWebPort,
36
- agentctlPort,
37
- backendUrl: `http://localhost:${resolvedBackendPort}`,
38
- };
39
- }
40
- /**
41
- * Builds environment variables for the backend process.
42
- *
43
- * @param options - Configuration options for the backend environment
44
- * @returns Environment object for the backend process
45
- */
46
- function buildBackendEnv(options) {
47
- const { ports, logLevel, extra } = options;
48
- return {
49
- ...process.env,
50
- KANDEV_SERVER_PORT: String(ports.backendPort),
51
- KANDEV_WEB_INTERNAL_URL: `http://localhost:${ports.webPort}`,
52
- KANDEV_AGENT_STANDALONE_PORT: String(ports.agentctlPort),
53
- ...(logLevel ? { KANDEV_LOG_LEVEL: logLevel } : {}),
54
- ...extra,
55
- };
56
- }
57
- /**
58
- * Builds environment variables for the web process.
59
- *
60
- * @param options - Configuration options for the web environment
61
- * @returns Environment object for the web process
62
- */
63
- function buildWebEnv(options) {
64
- const { ports, production = false, debug = false } = options;
65
- const env = {
66
- ...process.env,
67
- // Server-side: full localhost URL for SSR fetches (Next.js SSR → Go backend)
68
- KANDEV_API_BASE_URL: ports.backendUrl,
69
- PORT: String(ports.webPort),
70
- // Ensure Next.js standalone server binds to 127.0.0.1 so localhost health checks work.
71
- // Without this, HOSTNAME from the host environment can cause binding issues.
72
- HOSTNAME: "127.0.0.1",
73
- };
74
- if (production) {
75
- env.NODE_ENV = "production";
76
- // Explicitly unset so a host-level NEXT_PUBLIC_KANDEV_API_PORT (from a .env
77
- // file, Docker env, or CI variable) cannot leak through the process.env
78
- // spread above and reintroduce the cross-origin URL problem.
79
- delete env.NEXT_PUBLIC_KANDEV_API_PORT;
80
- }
81
- else {
82
- // Dev mode only: browser hits the web port directly, so the client needs to
83
- // know the backend port for API calls. In production the Go backend
84
- // reverse-proxies Next.js on a single port, so the client uses same-origin
85
- // and this var must NOT be set — otherwise the client builds cross-origin
86
- // URLs like `https://host:38429/...` that aren't reachable behind a
87
- // reverse proxy / ingress / Cloudflare tunnel.
88
- env.NEXT_PUBLIC_KANDEV_API_PORT = String(ports.backendPort);
89
- // Auto-allow the host's own LAN / VPN addresses so a dev hitting the dev
90
- // server from another device on the same network (Tailscale, LAN IP, WSL
91
- // mirrored mode, etc.) passes Next.js's allowedDevOrigins check and HMR
92
- // works. The user can still extend the list via NEXT_ALLOWED_DEV_ORIGINS.
93
- // Skip the assignment when there's nothing to add — keeps the env clean
94
- // for the loopback-only case.
95
- const merged = mergeAllowedDevOrigins(process.env.NEXT_ALLOWED_DEV_ORIGINS, listHostNetworkAddresses());
96
- if (merged)
97
- env.NEXT_ALLOWED_DEV_ORIGINS = merged;
98
- }
99
- if (debug) {
100
- env.KANDEV_DEBUG = "true";
101
- env.NEXT_PUBLIC_KANDEV_DEBUG = "true";
102
- }
103
- return env;
104
- }
105
- /**
106
- * Returns the host's non-loopback, non-internal IPv4/IPv6 addresses. Used to
107
- * auto-populate Next.js `allowedDevOrigins` so the dev server accepts
108
- * connections from LAN / Tailscale / SSH-forwarded clients.
109
- */
110
- function listHostNetworkAddresses() {
111
- const v4 = [];
112
- const v6 = [];
113
- const seen = new Set();
114
- const interfaces = node_os_1.default.networkInterfaces();
115
- for (const addrs of Object.values(interfaces)) {
116
- if (!addrs)
117
- continue;
118
- for (const addr of addrs) {
119
- if (addr.internal)
120
- continue;
121
- // Skip link-local IPv6 (fe80::/10) and link-local IPv4 (169.254.0.0/16,
122
- // RFC 3927) — neither is reachable from a remote machine, and the
123
- // 169.254 range in particular is what Hyper-V assigns to its phantom
124
- // WSL adapter, which clutters the startup output. The regex covers the
125
- // full /10 (fe80::–febf::); OS stacks only assign fe80::/64 in practice
126
- // but a stricter check is the same effort and removes the surprise.
127
- if (addr.family === "IPv6" && /^fe[89ab]/i.test(addr.address))
128
- continue;
129
- if (addr.family === "IPv4" && addr.address.startsWith("169.254."))
130
- continue;
131
- if (seen.has(addr.address))
132
- continue;
133
- seen.add(addr.address);
134
- if (addr.family === "IPv4")
135
- v4.push(addr.address);
136
- else
137
- v6.push(addr.address);
138
- }
139
- }
140
- // IPv4 first — LAN + Tailscale IPv4 are what people usually want.
141
- return [...v4, ...v6];
142
- }
143
- function mergeAllowedDevOrigins(existing, extra) {
144
- const set = new Set();
145
- if (existing) {
146
- for (const s of existing.split(",")) {
147
- const trimmed = s.trim();
148
- if (trimmed)
149
- set.add(trimmed);
150
- }
151
- }
152
- for (const s of extra)
153
- set.add(s);
154
- return [...set].join(",");
155
- }
156
- /**
157
- * Logs a unified startup info block to the console.
158
- *
159
- * Shows only the URL the user actually opens — start/run modes have the Go
160
- * backend reverse-proxy Next.js on a single port, dev mode hits Next.js
161
- * directly. The other port and the agentctl port are internal plumbing and
162
- * would only mislead. Below the URL, lists the same port on each non-loopback
163
- * interface (LAN, Tailscale) so a user opening the app remotely sees the
164
- * right address.
165
- */
166
- function logStartupInfo(options) {
167
- const { header, ports, primary = "backend", dbPath, logLevel } = options;
168
- const primaryPort = primary === "web" ? ports.webPort : ports.backendPort;
169
- const primaryUrl = `http://localhost:${primaryPort}`;
170
- const networkHosts = listHostNetworkAddresses();
171
- console.log(`[kandev] ${header}`);
172
- console.log("[kandev] url:", primaryUrl);
173
- for (const url of networkUrlsForPort(primaryPort, networkHosts)) {
174
- console.log("[kandev] network:", url);
175
- }
176
- console.log("[kandev] mcp:", `${ports.backendUrl}/mcp`);
177
- if (dbPath) {
178
- console.log("[kandev] db:", dbPath);
179
- }
180
- if (logLevel) {
181
- console.log("[kandev] log level:", logLevel);
182
- }
183
- }
184
- /**
185
- * Builds `http://<host>:<port>` URLs from a list of host addresses, wrapping
186
- * IPv6 addresses in brackets per RFC 3986.
187
- */
188
- function networkUrlsForPort(port, hosts) {
189
- return hosts.map((host) => {
190
- const formatted = host.includes(":") ? `[${host}]` : host;
191
- return `http://${formatted}:${port}`;
192
- });
193
- }
194
- /**
195
- * Attaches a standardized exit handler to a backend process.
196
- *
197
- * When the backend exits, this handler logs the exit reason and triggers
198
- * a graceful shutdown of all supervised processes. If the process was
199
- * killed by a signal, it exits with code 0; otherwise it uses the
200
- * process exit code (defaulting to 1).
201
- *
202
- * @param backendProc - The backend child process
203
- * @param supervisor - The process supervisor managing child processes
204
- */
205
- function attachBackendExitHandler(backendProc, supervisor, options = {}) {
206
- backendProc.on("exit", (code, signal) => {
207
- console.error(`[kandev] backend exited (code=${code}, signal=${signal})`);
208
- if (options.shouldShutdown && !options.shouldShutdown()) {
209
- return;
210
- }
211
- const exitCode = signal ? 0 : (code ?? 1);
212
- void supervisor.shutdown("backend exit").then(() => process.exit(exitCode));
213
- });
214
- }
package/dist/start.js DELETED
@@ -1,188 +0,0 @@
1
- "use strict";
2
- /**
3
- * Production start command for running local builds.
4
- *
5
- * This module implements the `kandev start` command, which runs the locally
6
- * built backend binary and web app in production mode. Unlike `kandev dev`
7
- * which uses hot-reloading, this runs the optimized production builds.
8
- *
9
- * Prerequisites:
10
- * - Backend must be built: `make build-backend`
11
- * - Web app must be built: `make build-web`
12
- * - Or simply: `make build` (builds both)
13
- */
14
- var __importDefault = (this && this.__importDefault) || function (mod) {
15
- return (mod && mod.__esModule) ? mod : { "default": mod };
16
- };
17
- Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.resolveStandaloneServerPath = resolveStandaloneServerPath;
19
- exports.runStart = runStart;
20
- const node_fs_1 = __importDefault(require("node:fs"));
21
- const node_path_1 = __importDefault(require("node:path"));
22
- const constants_1 = require("./constants");
23
- const health_1 = require("./health");
24
- const platform_1 = require("./platform");
25
- const process_1 = require("./process");
26
- const shared_1 = require("./shared");
27
- const backend_1 = require("./supervisor/backend");
28
- const web_1 = require("./web");
29
- /**
30
- * Locates the standalone Next.js `server.js` inside `apps/web/.next/standalone/`.
31
- *
32
- * Normally this sits at `.next/standalone/web/server.js`, but Turbopack may
33
- * place it under a deeper path (e.g. `.next/standalone/Users/.../web/server.js`)
34
- * when it detects the wrong project root (typically caused by a stray
35
- * `package-lock.json` in a parent directory). We look for any `web/server.js`
36
- * below the standalone directory so `kandev start` keeps working.
37
- *
38
- * @returns absolute path to `server.js`, or null if it cannot be found.
39
- */
40
- function resolveStandaloneServerPath(repoRoot) {
41
- const standaloneDir = node_path_1.default.join(repoRoot, "apps", "web", ".next", "standalone");
42
- const expected = node_path_1.default.join(standaloneDir, "web", "server.js");
43
- if (node_fs_1.default.existsSync(expected))
44
- return expected;
45
- if (!node_fs_1.default.existsSync(standaloneDir))
46
- return null;
47
- return findWebServerJs(standaloneDir);
48
- }
49
- // Walks `dir` manually instead of relying on `Dirent.parentPath` (Node 20.12+),
50
- // so the CLI keeps working on older Node 20.x runtimes installed via npx.
51
- function findWebServerJs(dir) {
52
- const stack = [dir];
53
- while (stack.length > 0) {
54
- const current = stack.pop();
55
- let entries;
56
- try {
57
- entries = node_fs_1.default.readdirSync(current, { withFileTypes: true });
58
- }
59
- catch {
60
- continue;
61
- }
62
- for (const entry of entries) {
63
- if (entry.isDirectory() && entry.name !== "node_modules") {
64
- stack.push(node_path_1.default.join(current, entry.name));
65
- }
66
- else if (entry.isFile() && entry.name === "server.js" && node_path_1.default.basename(current) === "web") {
67
- return node_path_1.default.join(current, entry.name);
68
- }
69
- }
70
- }
71
- return null;
72
- }
73
- /**
74
- * Runs the application in production mode using local builds.
75
- *
76
- * This function:
77
- * 1. Validates that build artifacts exist
78
- * 2. Picks available ports for all services
79
- * 3. Starts the backend binary (with warn log level for clean output)
80
- * 4. Starts the web app via `pnpm start`
81
- * 5. Waits for the backend to be healthy before announcing readiness
82
- *
83
- * @param options - Configuration for the start command
84
- * @throws Error if backend binary or web build is not found
85
- */
86
- async function runStart({ repoRoot, backendPort, webPort, verbose = false, debug = false, headless = false, }) {
87
- const ports = await (0, shared_1.pickPorts)(backendPort, webPort);
88
- const backendBin = node_path_1.default.join(repoRoot, "apps", "backend", "bin", (0, platform_1.getBinaryName)("kandev"));
89
- if (!node_fs_1.default.existsSync(backendBin)) {
90
- throw new Error("Backend binary not found. Run `make build` first.");
91
- }
92
- // Check for standalone build (Next.js standalone output)
93
- const webServerPath = resolveStandaloneServerPath(repoRoot);
94
- if (!webServerPath) {
95
- const standaloneDir = node_path_1.default.join(repoRoot, "apps", "web", ".next", "standalone");
96
- if (!node_fs_1.default.existsSync(standaloneDir)) {
97
- throw new Error("Web standalone build not found. Run `make build` first.");
98
- }
99
- throw new Error(`Web standalone build is missing server.js under ${standaloneDir}. ` +
100
- "This can happen when Next.js/Turbopack detects a different project root " +
101
- "(e.g. a stray package-lock.json in a parent directory). " +
102
- "Remove the stray lockfile and re-run `make build`.");
103
- }
104
- const webStandaloneDir = node_path_1.default.dirname(webServerPath);
105
- const webStaticDir = node_path_1.default.join(repoRoot, "apps", "web", ".next", "static");
106
- const standaloneStaticDir = node_path_1.default.join(webStandaloneDir, ".next", "static");
107
- if (node_fs_1.default.existsSync(webStaticDir) && !node_fs_1.default.existsSync(standaloneStaticDir)) {
108
- node_fs_1.default.mkdirSync(node_path_1.default.dirname(standaloneStaticDir), { recursive: true });
109
- try {
110
- node_fs_1.default.symlinkSync(webStaticDir, standaloneStaticDir, "junction");
111
- }
112
- catch (err) {
113
- console.warn(`[kandev] failed to link Next.js static assets: ${err instanceof Error ? err.message : String(err)}`);
114
- }
115
- }
116
- // Link public directory (fonts, images, etc.) into standalone output
117
- const webPublicDir = node_path_1.default.join(repoRoot, "apps", "web", "public");
118
- const standalonePublicDir = node_path_1.default.join(webStandaloneDir, "public");
119
- if (node_fs_1.default.existsSync(webPublicDir) && !node_fs_1.default.existsSync(standalonePublicDir)) {
120
- try {
121
- node_fs_1.default.symlinkSync(webPublicDir, standalonePublicDir, "junction");
122
- }
123
- catch (err) {
124
- console.warn(`[kandev] failed to link public assets: ${err instanceof Error ? err.message : String(err)}`);
125
- }
126
- }
127
- // Production mode: use warn log level for clean output unless verbose/debug
128
- const showOutput = verbose || debug;
129
- node_fs_1.default.mkdirSync((0, constants_1.resolveDataDir)(), { recursive: true });
130
- // The data dir holds the SQLite DB; keep it owner-only even if it pre-existed
131
- // with a looser umask-derived mode.
132
- node_fs_1.default.chmodSync((0, constants_1.resolveDataDir)(), 0o700);
133
- const dbPath = (0, constants_1.resolveDatabasePath)();
134
- const logLevel = process.env.KANDEV_LOG_LEVEL?.trim() || (debug ? "debug" : verbose ? "info" : "warn");
135
- const backendEnv = (0, shared_1.buildBackendEnv)({
136
- ports,
137
- logLevel,
138
- extra: {
139
- KANDEV_DATABASE_PATH: dbPath,
140
- ...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true", KANDEV_DEBUG_PPROF_ENABLED: "true" } : {}),
141
- },
142
- });
143
- const webEnv = (0, shared_1.buildWebEnv)({ ports, production: true, debug });
144
- (0, shared_1.logStartupInfo)({
145
- header: "start mode: using local build",
146
- ports,
147
- dbPath,
148
- logLevel,
149
- });
150
- const supervisor = (0, process_1.createProcessSupervisor)();
151
- supervisor.attachSignalHandlers();
152
- // Start backend: ignore stdin, show stdout only in verbose/debug mode, always show stderr
153
- // Stderr is always inherited to ensure error messages are visible immediately (no pipe buffering)
154
- const backend = await (0, backend_1.launchRestartableBackend)({
155
- command: backendBin,
156
- args: [],
157
- cwd: node_path_1.default.dirname(backendBin),
158
- env: backendEnv,
159
- homeDir: (0, constants_1.resolveKandevHomeDir)(),
160
- ports,
161
- mode: "start",
162
- stdio: showOutput ? ["ignore", "inherit", "inherit"] : ["ignore", "ignore", "inherit"],
163
- supervisor,
164
- });
165
- const healthTimeoutMs = (0, health_1.resolveHealthTimeoutMs)(constants_1.HEALTH_TIMEOUT_MS_RELEASE);
166
- console.log("[kandev] starting backend...");
167
- await (0, health_1.waitForHealth)(ports.backendUrl, backend.proc, healthTimeoutMs);
168
- console.log(`[kandev] backend ready at ${ports.backendUrl}`);
169
- // Use standalone server.js directly (not pnpm start)
170
- const webUrl = `http://localhost:${ports.webPort}`;
171
- console.log("[kandev] starting web...");
172
- const webProc = (0, web_1.launchWebApp)({
173
- command: "node",
174
- args: [webServerPath],
175
- cwd: webStandaloneDir,
176
- env: webEnv,
177
- supervisor,
178
- label: "web",
179
- quiet: !showOutput,
180
- });
181
- await (0, health_1.waitForUrlReady)(webUrl, webProc, healthTimeoutMs);
182
- console.log("[kandev] open: " + ports.backendUrl);
183
- if (headless) {
184
- console.log(`[kandev] ready (headless) at ${ports.backendUrl}`);
185
- return;
186
- }
187
- (0, web_1.openBrowser)(ports.backendUrl);
188
- }
@@ -1,83 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.launchRestartableBackend = launchRestartableBackend;
7
- exports.shouldUseSupervisor = shouldUseSupervisor;
8
- exports.withSupervisorEnv = withSupervisorEnv;
9
- const node_child_process_1 = require("node:child_process");
10
- const node_path_1 = __importDefault(require("node:path"));
11
- const shared_1 = require("../shared");
12
- const manifest_1 = require("./manifest");
13
- const paths_1 = require("./paths");
14
- const child_1 = require("./child");
15
- const control_1 = require("./control");
16
- async function launchRestartableBackend({ command, args, cwd, env, homeDir, ports, mode, stdio, supervisor, }) {
17
- if (!shouldUseSupervisor(env)) {
18
- const proc = (0, node_child_process_1.spawn)(command, args, { cwd, env, stdio });
19
- supervisor.children.push(proc);
20
- (0, shared_1.attachBackendExitHandler)(proc, supervisor);
21
- return { proc, control: null, env };
22
- }
23
- const supervisorEnv = withSupervisorEnv(env, homeDir);
24
- const manifest = (0, manifest_1.buildLaunchManifest)({
25
- backend_executable: resolveExecutable(command),
26
- argv: args,
27
- cwd,
28
- env: supervisorEnv,
29
- home_dir: homeDir,
30
- port: ports.backendPort,
31
- mode,
32
- });
33
- (0, manifest_1.writeLaunchManifest)(manifest, supervisorEnv.KANDEV_SUPERVISOR_MANIFEST);
34
- const child = (0, child_1.createRestartableChild)(manifest, { stdio, extraEnv: supervisorEnv });
35
- let restarting = false;
36
- const attachExit = (proc) => {
37
- (0, shared_1.attachBackendExitHandler)(proc, supervisor, {
38
- shouldShutdown: () => !restarting,
39
- });
40
- };
41
- const proc = child.start();
42
- supervisor.children.push(proc);
43
- attachExit(proc);
44
- const control = await (0, control_1.startControlServer)(supervisorEnv.KANDEV_SUPERVISOR_SOCKET, async () => {
45
- restarting = true;
46
- try {
47
- const next = await child.restart();
48
- supervisor.children.push(next);
49
- attachExit(next);
50
- }
51
- finally {
52
- restarting = false;
53
- }
54
- });
55
- return { proc, control, env: supervisorEnv };
56
- }
57
- function shouldUseSupervisor(env = process.env) {
58
- return env.KANDEV_NO_SUPERVISOR !== "true";
59
- }
60
- function withSupervisorEnv(env, homeDir) {
61
- (0, paths_1.prepareSupervisorDir)(homeDir);
62
- return {
63
- ...env,
64
- KANDEV_SUPERVISOR_SOCKET: (0, paths_1.socketPath)(homeDir),
65
- KANDEV_SUPERVISOR_MANIFEST: (0, paths_1.manifestPath)(homeDir),
66
- KANDEV_RESTART_ADAPTER: "supervisor",
67
- };
68
- }
69
- function resolveExecutable(command) {
70
- if (node_path_1.default.isAbsolute(command))
71
- return command;
72
- const found = (0, node_child_process_1.spawnSync)(process.platform === "win32" ? "where" : "which", [command], {
73
- encoding: "utf8",
74
- });
75
- const first = found.stdout
76
- ?.split(/\r?\n/)
77
- .map((line) => line.trim())
78
- .find(Boolean);
79
- if (!first) {
80
- throw new Error(`Unable to resolve executable ${command}`);
81
- }
82
- return first;
83
- }
@@ -1,64 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createRestartableChild = createRestartableChild;
7
- const node_child_process_1 = require("node:child_process");
8
- const tree_kill_1 = __importDefault(require("tree-kill"));
9
- const RESTART_TIMEOUT_MS = 10000;
10
- function createRestartableChild(manifest, options = {}) {
11
- let child = null;
12
- const start = () => {
13
- child = (0, node_child_process_1.spawn)(manifest.backend_executable, manifest.argv, {
14
- cwd: manifest.cwd,
15
- env: {
16
- ...process.env,
17
- ...manifest.env,
18
- ...options.extraEnv,
19
- },
20
- stdio: options.stdio ?? "inherit",
21
- });
22
- return child;
23
- };
24
- const stop = async () => {
25
- if (!child?.pid || child.exitCode !== null)
26
- return;
27
- await terminate(child, RESTART_TIMEOUT_MS);
28
- };
29
- const restart = async () => {
30
- await stop();
31
- return start();
32
- };
33
- return {
34
- current: () => child,
35
- start,
36
- restart,
37
- stop,
38
- };
39
- }
40
- function terminate(proc, timeoutMs) {
41
- return new Promise((resolve) => {
42
- const pid = proc.pid;
43
- if (!pid) {
44
- resolve();
45
- return;
46
- }
47
- let done = false;
48
- const finish = () => {
49
- if (done)
50
- return;
51
- done = true;
52
- clearTimeout(timeout);
53
- resolve();
54
- };
55
- const timeout = setTimeout(() => {
56
- (0, tree_kill_1.default)(pid, process.platform === "win32" ? undefined : "SIGKILL", finish);
57
- }, timeoutMs);
58
- proc.once("exit", finish);
59
- (0, tree_kill_1.default)(pid, process.platform === "win32" ? undefined : "SIGTERM", (err) => {
60
- if (err)
61
- finish();
62
- });
63
- });
64
- }
@@ -1,97 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.startControlServer = startControlServer;
7
- exports.requestRestart = requestRestart;
8
- const node_net_1 = __importDefault(require("node:net"));
9
- const node_fs_1 = __importDefault(require("node:fs"));
10
- function startControlServer(socket, onRestart) {
11
- let restartInProgress = false;
12
- const scheduleRestart = () => {
13
- if (restartInProgress)
14
- return false;
15
- restartInProgress = true;
16
- setTimeout(() => {
17
- void onRestart().finally(() => {
18
- restartInProgress = false;
19
- });
20
- }, 0);
21
- return true;
22
- };
23
- if (process.platform !== "win32" && node_fs_1.default.existsSync(socket)) {
24
- node_fs_1.default.unlinkSync(socket);
25
- }
26
- const server = node_net_1.default.createServer((conn) => {
27
- let buf = "";
28
- conn.setEncoding("utf8");
29
- conn.on("data", (chunk) => {
30
- buf += chunk;
31
- if (!buf.includes("\n"))
32
- return;
33
- const line = buf.slice(0, buf.indexOf("\n"));
34
- void handleControlLine(line, scheduleRestart)
35
- .then((resp) => {
36
- conn.end(`${JSON.stringify(resp)}\n`);
37
- })
38
- .catch((err) => {
39
- conn.end(`${JSON.stringify({
40
- accepted: false,
41
- message: err instanceof Error ? err.message : String(err),
42
- })}\n`);
43
- });
44
- });
45
- });
46
- return new Promise((resolve, reject) => {
47
- server.once("error", reject);
48
- server.listen(socket, () => {
49
- server.off("error", reject);
50
- resolve({
51
- close: () => new Promise((closeResolve, closeReject) => {
52
- server.close((err) => {
53
- if (process.platform !== "win32" && node_fs_1.default.existsSync(socket)) {
54
- node_fs_1.default.unlinkSync(socket);
55
- }
56
- if (err)
57
- closeReject(err);
58
- else
59
- closeResolve();
60
- });
61
- }),
62
- });
63
- });
64
- });
65
- }
66
- function requestRestart(socket) {
67
- return new Promise((resolve, reject) => {
68
- const conn = node_net_1.default.createConnection(socket);
69
- let buf = "";
70
- conn.setEncoding("utf8");
71
- conn.on("connect", () => {
72
- conn.write(`${JSON.stringify({ action: "restart" })}\n`);
73
- });
74
- conn.on("data", (chunk) => {
75
- buf += chunk;
76
- });
77
- conn.on("error", reject);
78
- conn.on("end", () => {
79
- try {
80
- resolve(JSON.parse(buf.trim()));
81
- }
82
- catch (err) {
83
- reject(err);
84
- }
85
- });
86
- });
87
- }
88
- async function handleControlLine(line, scheduleRestart) {
89
- const req = JSON.parse(line);
90
- if (req.action !== "restart") {
91
- return { accepted: false, message: `unsupported action ${String(req.action)}` };
92
- }
93
- if (!scheduleRestart()) {
94
- return { accepted: false, message: "Restart already in progress" };
95
- }
96
- return { accepted: true, message: "Restart accepted" };
97
- }