( baseUrl: string, )
| 877 | }).pipe(Effect.mapError(toError)); |
| 878 | |
| 879 | const stopDaemon = ( |
| 880 | baseUrl: string, |
| 881 | ): Effect.Effect<void, Error, FileSystem.FileSystem | PlatformPath.Path> => |
| 882 | Effect.gen(function* () { |
| 883 | const target = yield* resolveDaemonTarget(baseUrl); |
| 884 | const host = canonicalDaemonHost(target.hostname); |
| 885 | const scopeId = target.scopeId; |
| 886 | const record = yield* readDaemonRecord({ hostname: host, port: target.port }); |
| 887 | const reachable = yield* isServerReachable(target.baseUrl); |
| 888 | |
| 889 | if (!record) { |
| 890 | if (reachable) { |
| 891 | return yield* Effect.fail( |
| 892 | new Error( |
| 893 | [ |
| 894 | `Executor is reachable at ${target.baseUrl} but no daemon record exists.`, |
| 895 | "It may not be managed by this CLI process.", |
| 896 | "Stop it from the terminal/session where it was started.", |
| 897 | ].join("\n"), |
| 898 | ), |
| 899 | ); |
| 900 | } |
| 901 | console.log(`No daemon running at ${target.baseUrl}.`); |
| 902 | return; |
| 903 | } |
| 904 | |
| 905 | if (!isPidAlive(record.pid)) { |
| 906 | yield* removeDaemonRecord({ hostname: host, port: target.port }); |
| 907 | yield* removeDaemonPointer({ hostname: host, scopeId }).pipe(Effect.ignore); |
| 908 | if (reachable) { |
| 909 | return yield* Effect.fail( |
| 910 | new Error( |
| 911 | [ |
| 912 | `Daemon record for ${target.baseUrl} points to dead pid ${record.pid}, but endpoint is still reachable.`, |
| 913 | "Refusing to stop an unknown process without ownership metadata.", |
| 914 | ].join("\n"), |
| 915 | ), |
| 916 | ); |
| 917 | } |
| 918 | console.log( |
| 919 | `No daemon running at ${target.baseUrl} (removed stale record for pid ${record.pid}).`, |
| 920 | ); |
| 921 | return; |
| 922 | } |
| 923 | |
| 924 | console.log(`Stopping daemon at ${target.baseUrl} (pid ${record.pid})...`); |
| 925 | |
| 926 | yield* terminatePid(record.pid); |
| 927 | |
| 928 | const stopped = yield* waitForUnreachable({ |
| 929 | check: isServerReachable(target.baseUrl), |
| 930 | timeoutMs: DAEMON_STOP_TIMEOUT_MS, |
| 931 | intervalMs: DAEMON_BOOT_POLL_MS, |
| 932 | }); |
| 933 | |
| 934 | if (!stopped) { |
| 935 | return yield* Effect.fail( |
| 936 | new Error( |
no test coverage detected