@runa-ai/runa-cli 0.5.64 → 0.5.65
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/commands/dev/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev/helpers/stale-process-detector.d.ts +71 -0
- package/dist/commands/dev/helpers/stale-process-detector.d.ts.map +1 -0
- package/dist/commands/dev/machine.d.ts +5 -2
- package/dist/commands/dev/machine.d.ts.map +1 -1
- package/dist/commands/dev/types.d.ts +2 -0
- package/dist/commands/dev/types.d.ts.map +1 -1
- package/dist/index.js +154 -28
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../../src/commands/dev/commands/dev.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../../src/commands/dev/commands/dev.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8OpC,eAAO,MAAM,UAAU,SAuBnB,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI HINT: Stale Process Detection & Recovery
|
|
3
|
+
*
|
|
4
|
+
* Purpose: Detect and recover stale node/next processes holding ports
|
|
5
|
+
* Pattern: lsof → classify → health check → terminate if stale
|
|
6
|
+
*
|
|
7
|
+
* Security:
|
|
8
|
+
* - Uses secureExeca('lsof', ...) for safe binary execution
|
|
9
|
+
* - Only auto-kills node/next processes (never foreign like postgres)
|
|
10
|
+
* - Health check prevents killing healthy running servers
|
|
11
|
+
*
|
|
12
|
+
* Flow:
|
|
13
|
+
* Port EADDRINUSE
|
|
14
|
+
* -> lsof to identify process
|
|
15
|
+
* -> No process: port-free (race condition resolved)
|
|
16
|
+
* -> Non-node (postgres etc): foreign-process (error, no auto-kill)
|
|
17
|
+
* -> node/next:
|
|
18
|
+
* -> HTTP health check
|
|
19
|
+
* -> Healthy: healthy-running (error, don't kill)
|
|
20
|
+
* -> Not responding: stale -> auto-kill -> stale-killed
|
|
21
|
+
*/
|
|
22
|
+
export type StaleRecoveryResult = {
|
|
23
|
+
status: 'port-free';
|
|
24
|
+
} | {
|
|
25
|
+
status: 'stale-killed';
|
|
26
|
+
pid: number;
|
|
27
|
+
name: string;
|
|
28
|
+
} | {
|
|
29
|
+
status: 'healthy-running';
|
|
30
|
+
pid: number;
|
|
31
|
+
name: string;
|
|
32
|
+
} | {
|
|
33
|
+
status: 'foreign-process';
|
|
34
|
+
pid: number;
|
|
35
|
+
name: string;
|
|
36
|
+
} | {
|
|
37
|
+
status: 'detection-failed';
|
|
38
|
+
reason: string;
|
|
39
|
+
};
|
|
40
|
+
interface ProcessInfo {
|
|
41
|
+
pid: number;
|
|
42
|
+
name: string;
|
|
43
|
+
command: string;
|
|
44
|
+
}
|
|
45
|
+
interface StaleLogger {
|
|
46
|
+
info: (msg: string) => void;
|
|
47
|
+
warn: (msg: string) => void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Find process listening on a given port via lsof.
|
|
51
|
+
* Returns null if no process found or lsof fails.
|
|
52
|
+
*/
|
|
53
|
+
export declare function findProcessOnPort(port: number): Promise<ProcessInfo | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Check if a process is a node/next process safe to auto-kill.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isNodeOrNextProcess(name: string, command: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Check if a server on the given port is responding to HTTP requests.
|
|
60
|
+
* A healthy response means the server is actively serving (don't kill it).
|
|
61
|
+
*/
|
|
62
|
+
export declare function isServerHealthy(port: number): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Orchestrate stale process detection and recovery.
|
|
65
|
+
*
|
|
66
|
+
* Called when port is in use but no PID file exists.
|
|
67
|
+
* Identifies the process, classifies it, and recovers if safe.
|
|
68
|
+
*/
|
|
69
|
+
export declare function detectAndRecoverStaleProcess(port: number, logger: StaleLogger): Promise<StaleRecoveryResult>;
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=stale-process-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stale-process-detector.d.ts","sourceRoot":"","sources":["../../../../src/commands/dev/helpers/stale-process-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AASH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,MAAM,EAAE,iBAAiB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,MAAM,EAAE,iBAAiB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,UAAU,WAAW;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAaD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAsCjF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAc1E;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgBpE;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,mBAAmB,CAAC,CAoC9B"}
|
|
@@ -32,6 +32,9 @@ interface ProcessCheckInput {
|
|
|
32
32
|
replace: boolean;
|
|
33
33
|
skipApp: boolean;
|
|
34
34
|
}
|
|
35
|
+
interface ProcessCheckOutput {
|
|
36
|
+
staleProcessRecovered: boolean;
|
|
37
|
+
}
|
|
35
38
|
interface AppStartInput {
|
|
36
39
|
repoRoot: string;
|
|
37
40
|
appDir: string;
|
|
@@ -59,7 +62,7 @@ export declare const devMachine: import("xstate").StateMachine<DevContext, {
|
|
|
59
62
|
type: "error.platform";
|
|
60
63
|
error: Error;
|
|
61
64
|
}, {
|
|
62
|
-
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<import("../build/actors/setup.js").DepsInstallOutput, import("../build/actors/setup.js").SetupInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<import("../build/actors/setup.js").EnvCheckOutput, import("../build/actors/setup.js").SetupInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<import("../build/actors/setup.js").SupabaseStartOutput, import("../build/actors/setup.js").SetupInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<
|
|
65
|
+
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<import("../build/actors/setup.js").DepsInstallOutput, import("../build/actors/setup.js").SetupInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<import("../build/actors/setup.js").EnvCheckOutput, import("../build/actors/setup.js").SetupInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<import("../build/actors/setup.js").SupabaseStartOutput, import("../build/actors/setup.js").SetupInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<ProcessCheckOutput, ProcessCheckInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<AppStartOutput, AppStartInput, import("xstate").EventObject>> | import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<void, ShutdownInput, import("xstate").EventObject>> | undefined;
|
|
63
66
|
}, {
|
|
64
67
|
src: "depsInstall";
|
|
65
68
|
logic: import("xstate").PromiseActorLogic<import("../build/actors/setup.js").DepsInstallOutput, import("../build/actors/setup.js").SetupInput, import("xstate").EventObject>;
|
|
@@ -74,7 +77,7 @@ export declare const devMachine: import("xstate").StateMachine<DevContext, {
|
|
|
74
77
|
id: string | undefined;
|
|
75
78
|
} | {
|
|
76
79
|
src: "processCheck";
|
|
77
|
-
logic: import("xstate").PromiseActorLogic<
|
|
80
|
+
logic: import("xstate").PromiseActorLogic<ProcessCheckOutput, ProcessCheckInput, import("xstate").EventObject>;
|
|
78
81
|
id: string | undefined;
|
|
79
82
|
} | {
|
|
80
83
|
src: "appStart";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../../../src/commands/dev/machine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAMH,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../../../src/commands/dev/machine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAMH,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAC;AAW1D,OAAO,KAAK,EAAE,UAAU,EAAY,QAAQ,EAAa,MAAM,YAAY,CAAC;AAgG5E,UAAU,iBAAiB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAiCD,UAAU,kBAAkB;IAC1B,qBAAqB,EAAE,OAAO,CAAC;CAChC;AAwFD,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CACnC;AAED,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,IAAI,CAAC;CAC5B;AA8CD,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;CACb;AAoBD,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAcD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuNrB,CAAC;AAMH,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC;AAC3C,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,UAAU,CAAC,CAAC;AAM1D;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAa1D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAEzD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAE7D"}
|
|
@@ -24,6 +24,8 @@ export interface DevContext {
|
|
|
24
24
|
appPid: number | null;
|
|
25
25
|
/** Whether Supabase was started */
|
|
26
26
|
supabaseStarted: boolean;
|
|
27
|
+
/** Whether a stale process was recovered during port check */
|
|
28
|
+
staleProcessRecovered: boolean;
|
|
27
29
|
/** Start time for duration tracking */
|
|
28
30
|
startTime: number;
|
|
29
31
|
/** Error message if failed */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/commands/dev/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,QAAQ,IAAI,SAAS,EACrB,SAAS,IAAI,UAAU,EACvB,cAAc,IAAI,eAAe,EAClC,MAAM,eAAe,CAAC;AAGvB,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC;AACjC,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC;AACnC,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC;AAM7C;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,2BAA2B;IAC3B,KAAK,EAAE,QAAQ,CAAC;IAChB,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,WAAW,EAAE,OAAO,CAAC;IACrB,qCAAqC;IACrC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,mCAAmC;IACnC,eAAe,EAAE,OAAO,CAAC;IACzB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAMD,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAM7C;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAQ3D"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/commands/dev/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,QAAQ,IAAI,SAAS,EACrB,SAAS,IAAI,UAAU,EACvB,cAAc,IAAI,eAAe,EAClC,MAAM,eAAe,CAAC;AAGvB,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC;AACjC,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC;AACnC,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC;AAM7C;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,2BAA2B;IAC3B,KAAK,EAAE,QAAQ,CAAC;IAChB,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,WAAW,EAAE,OAAO,CAAC;IACrB,qCAAqC;IACrC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,mCAAmC;IACnC,eAAe,EAAE,OAAO,CAAC;IACzB,8DAA8D;IAC9D,qBAAqB,EAAE,OAAO,CAAC;IAC/B,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAMD,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAM7C;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAQ3D"}
|
package/dist/index.js
CHANGED
|
@@ -1161,7 +1161,7 @@ var CLI_VERSION, HAS_ADMIN_COMMAND;
|
|
|
1161
1161
|
var init_version = __esm({
|
|
1162
1162
|
"src/version.ts"() {
|
|
1163
1163
|
init_esm_shims();
|
|
1164
|
-
CLI_VERSION = "0.5.
|
|
1164
|
+
CLI_VERSION = "0.5.65";
|
|
1165
1165
|
HAS_ADMIN_COMMAND = false;
|
|
1166
1166
|
}
|
|
1167
1167
|
});
|
|
@@ -8370,6 +8370,88 @@ var guards2 = {
|
|
|
8370
8370
|
shouldSkipApp
|
|
8371
8371
|
};
|
|
8372
8372
|
|
|
8373
|
+
// src/commands/dev/helpers/stale-process-detector.ts
|
|
8374
|
+
init_esm_shims();
|
|
8375
|
+
var SAFE_TO_KILL_NAMES = ["node", "next-server", "next-router-worker", "tsx", "ts-node"];
|
|
8376
|
+
async function findProcessOnPort(port) {
|
|
8377
|
+
try {
|
|
8378
|
+
const result = await secureExeca("lsof", ["-i", `:${port}`, "-sTCP:LISTEN", "-P", "-n"], {
|
|
8379
|
+
reject: false
|
|
8380
|
+
});
|
|
8381
|
+
const stdout = String(result.stdout ?? "");
|
|
8382
|
+
if (result.exitCode !== 0 || !stdout) {
|
|
8383
|
+
return null;
|
|
8384
|
+
}
|
|
8385
|
+
const lines = stdout.trim().split("\n");
|
|
8386
|
+
if (lines.length < 2) return null;
|
|
8387
|
+
const dataLine = lines[1];
|
|
8388
|
+
const parts = dataLine.split(/\s+/);
|
|
8389
|
+
if (parts.length < 2) return null;
|
|
8390
|
+
const name = parts[0];
|
|
8391
|
+
const pid = parseInt(parts[1], 10);
|
|
8392
|
+
if (Number.isNaN(pid) || pid <= 0) return null;
|
|
8393
|
+
let command = "";
|
|
8394
|
+
try {
|
|
8395
|
+
const psResult = await secureExeca("lsof", ["-p", `${pid}`, "-Fn"], { reject: false });
|
|
8396
|
+
command = String(psResult.stdout ?? "");
|
|
8397
|
+
} catch {
|
|
8398
|
+
}
|
|
8399
|
+
return { pid, name, command };
|
|
8400
|
+
} catch {
|
|
8401
|
+
return null;
|
|
8402
|
+
}
|
|
8403
|
+
}
|
|
8404
|
+
function isNodeOrNextProcess(name, command) {
|
|
8405
|
+
const lowerName = name.toLowerCase();
|
|
8406
|
+
if (!SAFE_TO_KILL_NAMES.some((safe) => lowerName === safe)) {
|
|
8407
|
+
return false;
|
|
8408
|
+
}
|
|
8409
|
+
if (lowerName === "node") {
|
|
8410
|
+
return command.toLowerCase().includes("next");
|
|
8411
|
+
}
|
|
8412
|
+
return true;
|
|
8413
|
+
}
|
|
8414
|
+
async function isServerHealthy(port) {
|
|
8415
|
+
try {
|
|
8416
|
+
const controller = new AbortController();
|
|
8417
|
+
const timeout = setTimeout(() => controller.abort(), 2e3);
|
|
8418
|
+
const response = await fetch(`http://localhost:${port}/`, {
|
|
8419
|
+
signal: controller.signal,
|
|
8420
|
+
method: "GET"
|
|
8421
|
+
});
|
|
8422
|
+
clearTimeout(timeout);
|
|
8423
|
+
return response.status > 0;
|
|
8424
|
+
} catch {
|
|
8425
|
+
return false;
|
|
8426
|
+
}
|
|
8427
|
+
}
|
|
8428
|
+
async function detectAndRecoverStaleProcess(port, logger16) {
|
|
8429
|
+
const processInfo = await findProcessOnPort(port);
|
|
8430
|
+
if (!processInfo) {
|
|
8431
|
+
return { status: "port-free" };
|
|
8432
|
+
}
|
|
8433
|
+
const { pid, name, command } = processInfo;
|
|
8434
|
+
logger16.info(`Found process on port ${port}: ${name} (PID: ${pid})`);
|
|
8435
|
+
if (!isNodeOrNextProcess(name, command)) {
|
|
8436
|
+
return { status: "foreign-process", pid, name };
|
|
8437
|
+
}
|
|
8438
|
+
const healthy = await isServerHealthy(port);
|
|
8439
|
+
if (healthy) {
|
|
8440
|
+
return { status: "healthy-running", pid, name };
|
|
8441
|
+
}
|
|
8442
|
+
logger16.info(`Stale process detected (${name}, PID: ${pid}). Recovering...`);
|
|
8443
|
+
try {
|
|
8444
|
+
await terminateAppProcessByPid({ pid, logger: logger16 });
|
|
8445
|
+
logger16.info(`Stale process terminated (PID: ${pid})`);
|
|
8446
|
+
return { status: "stale-killed", pid, name };
|
|
8447
|
+
} catch (error) {
|
|
8448
|
+
return {
|
|
8449
|
+
status: "detection-failed",
|
|
8450
|
+
reason: `Failed to terminate PID ${pid}: ${error instanceof Error ? error.message : String(error)}`
|
|
8451
|
+
};
|
|
8452
|
+
}
|
|
8453
|
+
}
|
|
8454
|
+
|
|
8373
8455
|
// src/commands/dev/types.ts
|
|
8374
8456
|
init_esm_shims();
|
|
8375
8457
|
function createOutput2(context) {
|
|
@@ -8488,36 +8570,68 @@ function checkPortAvailable(port) {
|
|
|
8488
8570
|
server.listen(port);
|
|
8489
8571
|
});
|
|
8490
8572
|
}
|
|
8491
|
-
var processCheckActor = fromPromise(
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
pidFileContent =
|
|
8496
|
-
} catch {
|
|
8497
|
-
}
|
|
8498
|
-
if (pidFileContent) {
|
|
8499
|
-
const pid = parseInt(pidFileContent, 10);
|
|
8500
|
-
if (!Number.isNaN(pid) && isProcessAlive(pid)) {
|
|
8501
|
-
if (input3.replace) {
|
|
8502
|
-
await terminateAppProcessByPid({
|
|
8503
|
-
pid,
|
|
8504
|
-
logger: { info: console.log, warn: console.warn }
|
|
8505
|
-
});
|
|
8506
|
-
} else {
|
|
8507
|
-
throw new Error(
|
|
8508
|
-
`runa dev is already running (PID: ${pid}). Use --replace to restart, or stop the existing process first.`
|
|
8509
|
-
);
|
|
8510
|
-
}
|
|
8511
|
-
}
|
|
8573
|
+
var processCheckActor = fromPromise(
|
|
8574
|
+
async ({ input: input3 }) => {
|
|
8575
|
+
const pidFile = path12__default.join(input3.repoRoot, input3.tmpDir, "app.pid");
|
|
8576
|
+
let staleProcessRecovered = false;
|
|
8577
|
+
let pidFileContent = null;
|
|
8512
8578
|
try {
|
|
8513
|
-
|
|
8579
|
+
pidFileContent = readFileSync(pidFile, "utf-8").trim();
|
|
8514
8580
|
} catch {
|
|
8515
8581
|
}
|
|
8582
|
+
if (pidFileContent) {
|
|
8583
|
+
const pid = parseInt(pidFileContent, 10);
|
|
8584
|
+
if (!Number.isNaN(pid) && isProcessAlive(pid)) {
|
|
8585
|
+
if (input3.replace) {
|
|
8586
|
+
await terminateAppProcessByPid({
|
|
8587
|
+
pid,
|
|
8588
|
+
logger: { info: console.log, warn: console.warn }
|
|
8589
|
+
});
|
|
8590
|
+
} else {
|
|
8591
|
+
throw new Error(
|
|
8592
|
+
`runa dev is already running (PID: ${pid}). Use --replace to restart, or stop the existing process first.`
|
|
8593
|
+
);
|
|
8594
|
+
}
|
|
8595
|
+
}
|
|
8596
|
+
try {
|
|
8597
|
+
unlinkSync(pidFile);
|
|
8598
|
+
} catch {
|
|
8599
|
+
}
|
|
8600
|
+
}
|
|
8601
|
+
if (!input3.skipApp) {
|
|
8602
|
+
try {
|
|
8603
|
+
await checkPortAvailable(input3.port);
|
|
8604
|
+
} catch (portError) {
|
|
8605
|
+
if (portError instanceof Error && portError.message.includes("already in use")) {
|
|
8606
|
+
const logger16 = { info: console.log, warn: console.warn };
|
|
8607
|
+
const result = await detectAndRecoverStaleProcess(input3.port, logger16);
|
|
8608
|
+
switch (result.status) {
|
|
8609
|
+
case "port-free":
|
|
8610
|
+
break;
|
|
8611
|
+
case "stale-killed":
|
|
8612
|
+
staleProcessRecovered = true;
|
|
8613
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
8614
|
+
await checkPortAvailable(input3.port);
|
|
8615
|
+
break;
|
|
8616
|
+
case "healthy-running":
|
|
8617
|
+
throw new Error(
|
|
8618
|
+
`Port ${input3.port} is in use by a running server (${result.name}, PID: ${result.pid}). Use --port <number> or --replace to restart.`
|
|
8619
|
+
);
|
|
8620
|
+
case "foreign-process":
|
|
8621
|
+
throw new Error(
|
|
8622
|
+
`Port ${input3.port} is in use by ${result.name} (PID: ${result.pid}). Stop it manually before running runa dev.`
|
|
8623
|
+
);
|
|
8624
|
+
case "detection-failed":
|
|
8625
|
+
throw portError;
|
|
8626
|
+
}
|
|
8627
|
+
} else {
|
|
8628
|
+
throw portError;
|
|
8629
|
+
}
|
|
8630
|
+
}
|
|
8631
|
+
}
|
|
8632
|
+
return { staleProcessRecovered };
|
|
8516
8633
|
}
|
|
8517
|
-
|
|
8518
|
-
await checkPortAvailable(input3.port);
|
|
8519
|
-
}
|
|
8520
|
-
});
|
|
8634
|
+
);
|
|
8521
8635
|
var appStartActor = fromPromise(
|
|
8522
8636
|
async ({ input: input3 }) => {
|
|
8523
8637
|
const { repoRoot, appDir, port, tmpDir, stream, bundler } = input3;
|
|
@@ -8593,6 +8707,7 @@ var devMachine = setup({
|
|
|
8593
8707
|
hasDatabase: detectDatabase(repoRoot),
|
|
8594
8708
|
appPid: null,
|
|
8595
8709
|
supabaseStarted: false,
|
|
8710
|
+
staleProcessRecovered: false,
|
|
8596
8711
|
startTime: Date.now(),
|
|
8597
8712
|
error: null
|
|
8598
8713
|
};
|
|
@@ -8621,7 +8736,15 @@ var devMachine = setup({
|
|
|
8621
8736
|
replace: context.input.replace,
|
|
8622
8737
|
skipApp: context.input.skipApp
|
|
8623
8738
|
}),
|
|
8624
|
-
onDone: {
|
|
8739
|
+
onDone: {
|
|
8740
|
+
target: "setup",
|
|
8741
|
+
actions: assign({
|
|
8742
|
+
staleProcessRecovered: ({ event }) => {
|
|
8743
|
+
const output3 = event.output;
|
|
8744
|
+
return output3?.staleProcessRecovered ?? false;
|
|
8745
|
+
}
|
|
8746
|
+
})
|
|
8747
|
+
},
|
|
8625
8748
|
onError: {
|
|
8626
8749
|
target: "failed",
|
|
8627
8750
|
actions: assign({
|
|
@@ -8821,6 +8944,9 @@ function handleStateChange2(snapshot2, prevState, logger16, port) {
|
|
|
8821
8944
|
if (handler) {
|
|
8822
8945
|
handler(logger16, { port });
|
|
8823
8946
|
}
|
|
8947
|
+
if (prevState === "processCheck" && state2.startsWith("setup") && snapshot2.context.staleProcessRecovered) {
|
|
8948
|
+
logger16.success(" Stale process recovered. Port is now available.");
|
|
8949
|
+
}
|
|
8824
8950
|
}
|
|
8825
8951
|
function printSummary2(logger16, output3) {
|
|
8826
8952
|
if (output3.success) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runa-ai/runa-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.65",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "AI-powered DevOps CLI",
|
|
6
6
|
"type": "module",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"typescript": "5.9.3",
|
|
54
54
|
"xstate": "5.28.0",
|
|
55
55
|
"zod": "4.3.6",
|
|
56
|
-
"@runa-ai/runa": "0.5.
|
|
56
|
+
"@runa-ai/runa": "0.5.65",
|
|
57
57
|
"@runa-ai/runa-xstate-test-plugin": "0.5.58"
|
|
58
58
|
},
|
|
59
59
|
"engines": {
|