@runa-ai/runa-cli 0.5.63 → 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.
@@ -10,11 +10,12 @@
10
10
  * - supabase/schemas/idempotent/*.sql for extensions/cleanup
11
11
  *
12
12
  * This actor works across all modes:
13
- * - ci-local: Uses `runa db sync` (pg-schema-diff + introspect + zod + seeds)
13
+ * - ci-local: Uses `runa db sync` (pg-schema-diff + introspect + zod, NO seeds)
14
14
  * - ci-pr-local: Uses `runa db apply --no-seed` (idempotent → pg-schema-diff, NO seeds)
15
15
  *
16
- * IMPORTANT: In ci-pr mode, seeds are applied separately by applySeedsActor.
17
- * Using --no-seed prevents duplicate seed application.
16
+ * IMPORTANT: Seeds are applied separately by applySeedsActor in ALL modes.
17
+ * - db sync: Has no seed functionality (schema-only command)
18
+ * - db apply: Requires --no-seed to skip its built-in seed step
18
19
  *
19
20
  * IMPORTANT: NO dry-run/check mode in CI. Always apply changes directly.
20
21
  *
@@ -1 +1 @@
1
- {"version":3,"file":"sync-schema.d.ts","sourceRoot":"","sources":["../../../../../../src/commands/ci/machine/actors/db/sync-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AASH,OAAO,KAAK,EAAE,MAAM,EAAqB,mBAAmB,EAAiB,MAAM,gBAAgB,CAAC;AAEpG,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,mBAAmB,CAAC;CACnC;AAmQD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,qGA0G3B,CAAC"}
1
+ {"version":3,"file":"sync-schema.d.ts","sourceRoot":"","sources":["../../../../../../src/commands/ci/machine/actors/db/sync-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AASH,OAAO,KAAK,EAAE,MAAM,EAAqB,mBAAmB,EAAiB,MAAM,gBAAgB,CAAC;AAEpG,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,mBAAmB,CAAC;CACnC;AAmQD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,qGA4G3B,CAAC"}
@@ -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;AAqOpC,eAAO,MAAM,UAAU,SAuBnB,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<void, 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;
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<void, ProcessCheckInput, import("xstate").EventObject>;
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;AAU1D,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;AA+ED,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8MrB,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"}
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.63";
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(async ({ input: input3 }) => {
8492
- const pidFile = path12__default.join(input3.repoRoot, input3.tmpDir, "app.pid");
8493
- let pidFileContent = null;
8494
- try {
8495
- pidFileContent = readFileSync(pidFile, "utf-8").trim();
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
- unlinkSync(pidFile);
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
- if (!input3.skipApp) {
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: { target: "setup" },
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) {
@@ -13253,8 +13379,6 @@ var syncSchemaActor = fromPromise(
13253
13379
  "sync",
13254
13380
  envArg,
13255
13381
  "--auto-approve",
13256
- "--no-seed",
13257
- // Seeds applied separately by applySeedsActor
13258
13382
  "--verbose",
13259
13383
  // Always verbose for full traceability
13260
13384
  ...skipCodegen ? ["--skip-codegen"] : []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runa-ai/runa-cli",
3
- "version": "0.5.63",
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.63",
56
+ "@runa-ai/runa": "0.5.65",
57
57
  "@runa-ai/runa-xstate-test-plugin": "0.5.58"
58
58
  },
59
59
  "engines": {