| @@ -22,6 +22,7 @@ import { · 21 unmodified lines |
| 22 | type SqlControlPlaneRuntime, | 22 | type SqlControlPlaneRuntime, |
| 23 | } from "@executor-v3/control-plane"; | 23 | } from "@executor-v3/control-plane"; |
| 24 | import type { ToolCatalog } from "@executor-v3/codemode-core"; | 24 | import type { ToolCatalog } from "@executor-v3/codemode-core"; |
| | 25 | import { isDenoAvailable } from "@executor-v3/runtime-deno-subprocess"; |
| 25 | import * as Effect from "effect/Effect"; | 26 | import * as Effect from "effect/Effect"; |
| 26 | import * as Schema from "effect/Schema"; | 27 | import * as Schema from "effect/Schema"; |
| 27 | import * as Option from "effect/Option"; | 28 | import * as Option from "effect/Option"; |
| @@ -583,46 +584,56 @@ const renderStatus = (status: LocalServerStatus): string => · 555 unmodified lines |
| 583 | | 584 | |
| 584 | const getDoctorReport = (baseUrl: string) => | 585 | const getDoctorReport = (baseUrl: string) => |
| 585 | getServerStatus(baseUrl).pipe( | 586 | getServerStatus(baseUrl).pipe( |
| 586 | Effect.map((status) => { | 587 | Effect.flatMap((status) => |
| 587 | const checks = { | 588 | getDenoVersion().pipe( |
| 588 | serverReachable: { | 589 | Effect.map((denoVersion) => { |
| 589 | ok: status.reachable, | 590 | const checks = { |
| 590 | detail: status.reachable ? `reachable at ${status.baseUrl}` : `not reachable at ${status.baseUrl}`, | 591 | serverReachable: { |
| 591 | }, | 592 | ok: status.reachable, |
| 592 | pidFile: { | 593 | detail: status.reachable ? `reachable at ${status.baseUrl}` : `not reachable at ${status.baseUrl}`, |
| 593 | ok: status.pid !== null, | 594 | }, |
| 594 | detail: status.pid !== null ? `pid ${status.pid}` : `missing pid file at ${status.pidFile}`, | 595 | pidFile: { |
| 595 | }, | 596 | ok: status.pid !== null, |
| 596 | process: { | 597 | detail: status.pid !== null ? `pid ${status.pid}` : `missing pid file at ${status.pidFile}`, |
| 597 | ok: status.pidRunning, | 598 | }, |
| 598 | detail: status.pidRunning ? `pid ${status.pid}` : "no live daemon process recorded", | 599 | process: { |
| 599 | }, | 600 | ok: status.pidRunning, |
| 600 | database: { | 601 | detail: status.pidRunning ? `pid ${status.pid}` : "no live daemon process recorded", |
| 601 | ok: status.localDataDir.length > 0, | 602 | }, |
| 602 | detail: status.localDataDir, | 603 | database: { |
| 603 | }, | 604 | ok: status.localDataDir.length > 0, |
| 604 | webAssets: { | 605 | detail: status.localDataDir, |
| 605 | ok: status.webAssetsDir !== null, | 606 | }, |
| 606 | detail: status.webAssetsDir ?? "missing bundled web assets", | 607 | webAssets: { |
| 607 | }, | 608 | ok: status.webAssetsDir !== null, |
| 608 | migrations: { | 609 | detail: status.webAssetsDir ?? "missing bundled web assets", |
| 609 | ok: status.migrationsDir !== null, | 610 | }, |
| 610 | detail: status.migrationsDir ?? "missing migrations directory", | 611 | migrations: { |
| 611 | }, | 612 | ok: status.migrationsDir !== null, |
| 612 | installation: { | 613 | detail: status.migrationsDir ?? "missing migrations directory", |
| 613 | ok: status.installation !== null, | 614 | }, |
| 614 | detail: status.installation | 615 | installation: { |
| 615 | ? `workspace ${status.installation.workspaceId}` | 616 | ok: status.installation !== null, |
| 616 | : "local installation unavailable", | 617 | detail: status.installation |
| 617 | }, | 618 | ? `workspace ${status.installation.workspaceId}` |
| 618 | } as const; | 619 | : "local installation unavailable", |
| 619 | | 620 | }, |
| 620 | return { | 621 | denoSandbox: { |
| 621 | ok: Object.values(checks).every((check) => check.ok), | 622 | ok: denoVersion !== null, |
| 622 | status, | 623 | detail: denoVersion !== null |
| 623 | checks, | 624 | ? `deno ${denoVersion}` |
| 624 | }; | 625 | : "deno not found (run `executor sandbox` to install)", |
| 625 | }), | 626 | }, |
| | 627 | } as const; |
| | 628 | |
| | 629 | return { |
| | 630 | ok: Object.values(checks).every((check) => check.ok), |
| | 631 | status, |
| | 632 | checks, |
| | 633 | }; |
| | 634 | }), |
| | 635 | ), |
| | 636 | ), |
| 626 | ); | 637 | ); |
| 627 | | 638 | |
| 628 | const printJson = (value: unknown) => | 639 | const printJson = (value: unknown) => |
| @@ -1143,6 +1154,93 @@ const doctorCommand = Command.make( · 514 unmodified lines |
| 1143 | ), | 1154 | ), |
| 1144 | ).pipe(Command.withDescription("Check local executor install and daemon health")); | 1155 | ).pipe(Command.withDescription("Check local executor install and daemon health")); |
| 1145 | | 1156 | |
| | 1157 | const getDenoVersion = (): Effect.Effect<string | null, never, never> => |
| | 1158 | Effect.tryPromise({ |
| | 1159 | try: () => |
| | 1160 | new Promise<string | null>((resolve) => { |
| | 1161 | const denoExecutable = process.env.DENO_BIN?.trim() |
| | 1162 | || (process.env.HOME && existsSync(`${process.env.HOME}/.deno/bin/deno`) |
| | 1163 | ? `${process.env.HOME}/.deno/bin/deno` |
| | 1164 | : "deno"); |
| | 1165 | |
| | 1166 | const child = spawn(denoExecutable, ["--version"], { |
| | 1167 | stdio: ["ignore", "pipe", "ignore"], |
| | 1168 | timeout: 5000, |
| | 1169 | }); |
| | 1170 | |
| | 1171 | let stdout = ""; |
| | 1172 | child.stdout?.setEncoding("utf8"); |
| | 1173 | child.stdout?.on("data", (chunk: string) => { |
| | 1174 | stdout += chunk; |
| | 1175 | }); |
| | 1176 | |
| | 1177 | child.once("error", () => resolve(null)); |
| | 1178 | child.once("close", (code) => { |
| | 1179 | if (code !== 0) { |
| | 1180 | resolve(null); |
| | 1181 | return; |
| | 1182 | } |
| | 1183 | |
| | 1184 | const match = /deno\s+(\S+)/i.exec(stdout); |
| | 1185 | resolve(match ? match[1] : null); |
| | 1186 | }); |
| | 1187 | }), |
| | 1188 | catch: () => null, |
| | 1189 | }).pipe(Effect.catchAll(() => Effect.succeed(null))); |
| | 1190 | |
| | 1191 | const installDeno = (): Effect.Effect<boolean, Error, never> => |
| | 1192 | Effect.tryPromise({ |
| | 1193 | try: () => |
| | 1194 | new Promise<boolean>((resolve, reject) => { |
| | 1195 | const child = spawn("sh", ["-c", "curl -fsSL https://deno.land/install.sh | sh"], { |
| | 1196 | stdio: ["ignore", "inherit", "inherit"], |
| | 1197 | }); |
| | 1198 | |
| | 1199 | child.once("error", (err) => reject(err)); |
| | 1200 | child.once("close", (code) => resolve(code === 0)); |
| | 1201 | }), |
| | 1202 | catch: toError, |
| | 1203 | }); |
| | 1204 | |
| | 1205 | const sandboxCommand = Command.make( |
| | 1206 | "sandbox", |
| | 1207 | {}, |
| | 1208 | () => |
| | 1209 | Effect.gen(function* () { |
| | 1210 | yield* printText("Checking Deno sandbox runtime..."); |
| | 1211 | |
| | 1212 | const version = yield* getDenoVersion(); |
| | 1213 | |
| | 1214 | if (version !== null) { |
| | 1215 | yield* printText(`Deno sandbox is ready (deno ${version}).`); |
| | 1216 | return; |
| | 1217 | } |
| | 1218 | |
| | 1219 | if (!isDenoAvailable()) { |
| | 1220 | yield* printText("Deno is not installed. Installing..."); |
| | 1221 | } else { |
| | 1222 | yield* printText("Deno binary found but did not respond. Reinstalling..."); |
| | 1223 | } |
| | 1224 | |
| | 1225 | const installed = yield* installDeno(); |
| | 1226 | if (!installed) { |
| | 1227 | return yield* Effect.fail( |
| | 1228 | new Error("Deno installation failed. Install manually: https://docs.deno.com/runtime/getting_started/installation/"), |
| | 1229 | ); |
| | 1230 | } |
| | 1231 | |
| | 1232 | const postVersion = yield* getDenoVersion(); |
| | 1233 | if (postVersion === null) { |
| | 1234 | return yield* Effect.fail( |
| | 1235 | new Error("Deno was installed but could not be verified. You may need to restart your shell."), |
| | 1236 | ); |
| | 1237 | } |
| | 1238 | |
| | 1239 | yield* printText(`Deno sandbox installed successfully (deno ${postVersion}).`); |
| | 1240 | }), |
| | 1241 | ).pipe(Command.withDescription("Check and install the Deno sandbox runtime")); |
| | 1242 | |
| | 1243 | |
| 1146 | const callCommand = Command.make( | 1244 | const callCommand = Command.make( |
| 1147 | "call", | 1245 | "call", |
| 1148 | { | 1246 | { |
| @@ -1287,6 +1385,7 @@ const root = Command.make("executor").pipe( · 138 unmodified lines |
| 1287 | downCommand, | 1385 | downCommand, |
| 1288 | statusCommand, | 1386 | statusCommand, |
| 1289 | doctorCommand, | 1387 | doctorCommand, |
| | 1388 | sandboxCommand, |
| 1290 | callCommand, | 1389 | callCommand, |
| 1291 | resumeCommand, | 1390 | resumeCommand, |
| 1292 | devCommand, | 1391 | devCommand, |