History

fix deno detection

Rhys Sullivan committed 8 months ago

Commit b4898091 parent 04294d26 files · +110 −58Browse files

Unified · Split

6 files
  1. .github/workflows/publish-executor-package.yml +5 −0
  2. apps/executor/src/cli/main.ts +53 −50
  3. package.json +1 −0
  4. packages/runtime-deno-subprocess/src/index.test.ts +21 −0
  5. packages/runtime-deno-subprocess/src/index.ts +26 −8
  6. turbo.json +4 −0
.github/workflows/publish-executor-package.yml+5−0
@@ -39,6 +39,11 @@ jobs: · 38 unmodified lines
39 node-version: 2239 node-version: 22
40 registry-url: https://registry.npmjs.org40 registry-url: https://registry.npmjs.org
41 41
42 - name: Setup Deno
43 uses: denoland/setup-deno@v2
44 with:
45 deno-version: v2.x
46
42 - name: Install dependencies47 - name: Install dependencies
43 run: bun install --frozen-lockfile48 run: bun install --frozen-lockfile
44 49
apps/executor/src/cli/main.ts+53−50
@@ -538,8 +538,14 @@ type LocalServerStatus = { · 537 unmodified lines
538 accountId: string;538 accountId: string;
539 workspaceId: string;539 workspaceId: string;
540 } | null;540 } | null;
541 denoVersion: string | null;
541};542};
542 543
544const renderDenoSandboxDetail = (denoVersion: string | null): string =>
545 denoVersion !== null
546 ? `deno ${denoVersion}`
547 : "deno not found (run `executor sandbox` to install)";
548
543const getServerStatus = (baseUrl: string): Effect.Effect<LocalServerStatus, Error, never> =>549const getServerStatus = (baseUrl: string): Effect.Effect<LocalServerStatus, Error, never> =>
544 Effect.gen(function* () {550 Effect.gen(function* () {
545 const pidRecord = yield* readPidRecord();551 const pidRecord = yield* readPidRecord();
@@ -554,6 +560,7 @@ const getServerStatus = (baseUrl: string): Effect.Effect<LocalServerStatus, Erro · 8 unmodified lines
554 const pid = typeof pidRecord?.pid === "number" ? pidRecord.pid : null;560 const pid = typeof pidRecord?.pid === "number" ? pidRecord.pid : null;
555 const pidRunning = pid !== null ? isPidRunning(pid) : false;561 const pidRunning = pid !== null ? isPidRunning(pid) : false;
556 const logFile = pidRecord?.logFile ?? DEFAULT_SERVER_LOG_FILE;562 const logFile = pidRecord?.logFile ?? DEFAULT_SERVER_LOG_FILE;
563 const denoVersion = yield* getDenoVersion();
557 564
558 return {565 return {
559 baseUrl,566 baseUrl,
@@ -566,6 +573,7 @@ const getServerStatus = (baseUrl: string): Effect.Effect<LocalServerStatus, Erro · 6 unmodified lines
566 webAssetsDir: resolveRuntimeWebAssetsDir(),573 webAssetsDir: resolveRuntimeWebAssetsDir(),
567 migrationsDir: resolveRuntimeMigrationsDir(),574 migrationsDir: resolveRuntimeMigrationsDir(),
568 installation,575 installation,
576 denoVersion,
569 } satisfies LocalServerStatus;577 } satisfies LocalServerStatus;
570 });578 });
571 579
@@ -581,60 +589,55 @@ const renderStatus = (status: LocalServerStatus): string => · 9 unmodified lines
581 `webAssetsDir: ${status.webAssetsDir ?? "missing"}`,589 `webAssetsDir: ${status.webAssetsDir ?? "missing"}`,
582 `migrationsDir: ${status.migrationsDir ?? "missing"}`,590 `migrationsDir: ${status.migrationsDir ?? "missing"}`,
583 `workspaceId: ${status.installation?.workspaceId ?? "unavailable"}`,591 `workspaceId: ${status.installation?.workspaceId ?? "unavailable"}`,
592 `denoSandbox: ${renderDenoSandboxDetail(status.denoVersion)}`,
584 ].join("\n");593 ].join("\n");
585 594
586const getDoctorReport = (baseUrl: string) =>595const getDoctorReport = (baseUrl: string) =>
587 getServerStatus(baseUrl).pipe(596 getServerStatus(baseUrl).pipe(
588 Effect.flatMap((status) =>597 Effect.map((status) => {
589 getDenoVersion().pipe(598 const checks = {
590 Effect.map((denoVersion) => {599 serverReachable: {
591 const checks = {600 ok: status.reachable,
592 serverReachable: {601 detail: status.reachable ? `reachable at ${status.baseUrl}` : `not reachable at ${status.baseUrl}`,
593 ok: status.reachable,602 },
594 detail: status.reachable ? `reachable at ${status.baseUrl}` : `not reachable at ${status.baseUrl}`,603 pidFile: {
595 },604 ok: status.pid !== null,
596 pidFile: {605 detail: status.pid !== null ? `pid ${status.pid}` : `missing pid file at ${status.pidFile}`,
597 ok: status.pid !== null,606 },
598 detail: status.pid !== null ? `pid ${status.pid}` : `missing pid file at ${status.pidFile}`,607 process: {
599 },608 ok: status.pidRunning,
600 process: {609 detail: status.pidRunning ? `pid ${status.pid}` : "no live daemon process recorded",
601 ok: status.pidRunning,610 },
602 detail: status.pidRunning ? `pid ${status.pid}` : "no live daemon process recorded",611 database: {
603 },612 ok: status.localDataDir.length > 0,
604 database: {613 detail: status.localDataDir,
605 ok: status.localDataDir.length > 0,614 },
606 detail: status.localDataDir,615 webAssets: {
607 },616 ok: status.webAssetsDir !== null,
608 webAssets: {617 detail: status.webAssetsDir ?? "missing bundled web assets",
609 ok: status.webAssetsDir !== null,618 },
610 detail: status.webAssetsDir ?? "missing bundled web assets",619 migrations: {
611 },620 ok: status.migrationsDir !== null,
612 migrations: {621 detail: status.migrationsDir ?? "missing migrations directory",
613 ok: status.migrationsDir !== null,622 },
614 detail: status.migrationsDir ?? "missing migrations directory",623 installation: {
615 },624 ok: status.installation !== null,
616 installation: {625 detail: status.installation
617 ok: status.installation !== null,626 ? `workspace ${status.installation.workspaceId}`
618 detail: status.installation627 : "local installation unavailable",
619 ? `workspace ${status.installation.workspaceId}`628 },
620 : "local installation unavailable",629 denoSandbox: {
621 },630 ok: status.denoVersion !== null,
622 denoSandbox: {631 detail: renderDenoSandboxDetail(status.denoVersion),
623 ok: denoVersion !== null,632 },
624 detail: denoVersion !== null633 } as const;
625 ? `deno ${denoVersion}`634
626 : "deno not found (run `executor sandbox` to install)",635 return {
627 },636 ok: Object.values(checks).every((check) => check.ok),
628 } as const;637 status,
629 638 checks,
630 return {639 };
631 ok: Object.values(checks).every((check) => check.ok),640 }),
632 status,
633 checks,
634 };
635 }),
636 ),
637 ),
638 );641 );
639 642
640const printJson = (value: unknown) =>643const printJson = (value: unknown) =>
package.json+1−0
@@ -20,6 +20,7 @@ · 19 unmodified lines
20 },20 },
21 "scripts": {21 "scripts": {
22 "dev": "turbo run dev --filter=@executor/web",22 "dev": "turbo run dev --filter=@executor/web",
23 "test": "turbo run test",
23 "executor": "./apps/executor/bin/executor",24 "executor": "./apps/executor/bin/executor",
24 "typecheck": "bun run --filter='*' typecheck",25 "typecheck": "bun run --filter='*' typecheck",
25 "knip": "knip"26 "knip": "knip"
packages/runtime-deno-subprocess/src/index.test.ts+21−0
@@ -43,6 +43,27 @@ const tools = { · 42 unmodified lines
43 }),43 }),
44};44};
45 45
46it("reports unavailable Deno executables", () => {
47 expect(isDenoAvailable("__executor_missing_deno__")).toBe(false);
48});
49
50it.effect("returns an actionable error when Deno is missing", () =>
51 Effect.gen(function* () {
52 const executor = makeDenoSubprocessExecutor({
53 denoExecutable: "__executor_missing_deno__",
54 });
55 const toolInvoker = makeToolInvokerFromTools({ tools });
56
57 const output = yield* executor.execute(
58 "return 1 + 2;",
59 toolInvoker,
60 );
61
62 expect(output.result).toBeNull();
63 expect(output.error).toContain("Install Deno or set DENO_BIN");
64 }),
65);
66
46const skipUnlessDeno = isDenoAvailable()67const skipUnlessDeno = isDenoAvailable()
47 ? describe68 ? describe
48 : describe.skip;69 : describe.skip;
packages/runtime-deno-subprocess/src/index.ts+26−8
@@ -1,3 +1,4 @@
1import { spawnSync } from "node:child_process";
1import { existsSync } from "node:fs";2import { existsSync } from "node:fs";
2import { fileURLToPath } from "node:url";3import { fileURLToPath } from "node:url";
3 4
@@ -91,6 +92,21 @@ const defaultDenoExecutable = (): string => { · 87 unmodified lines
91 return "deno";92 return "deno";
92};93};
93 94
95const formatDenoSpawnError = (
96 cause: unknown,
97 executable: string,
98): string => {
99 const code = typeof cause === "object" && cause !== null && "code" in cause
100 ? String((cause as { code?: unknown }).code)
101 : null;
102
103 if (code === "ENOENT") {
104 return `Failed to spawn Deno subprocess: Deno executable "${executable}" was not found. Install Deno or set DENO_BIN.`;
105 }
106
107 return `Failed to spawn Deno subprocess: ${cause instanceof Error ? cause.message : String(cause)}`;
108};
109
94// --------------------------------------------------------------------------110// --------------------------------------------------------------------------
95// Worker script resolution111// Worker script resolution
96// --------------------------------------------------------------------------112// --------------------------------------------------------------------------
@@ -294,9 +310,7 @@ const executeInDeno = ( · 197 unmodified lines
294 stderrBuffer += chunk;310 stderrBuffer += chunk;
295 },311 },
296 onError: (cause) => {312 onError: (cause) => {
297 fail(313 fail(formatDenoSpawnError(cause, denoExecutable));
298 `Failed to spawn Deno subprocess: ${cause.message}`,
299 );
300 },314 },
301 onExit: (exitCode, signal) => {315 onExit: (exitCode, signal) => {
302 if (settled) {316 if (settled) {
@@ -310,9 +324,7 @@ const executeInDeno = ( · 7 unmodified lines
310 },324 },
311 );325 );
312 } catch (cause) {326 } catch (cause) {
313 fail(327 fail(formatDenoSpawnError(cause, denoExecutable));
314 `Failed to spawn Deno subprocess: ${cause instanceof Error ? cause.message : String(cause)}`,
315 );
316 return;328 return;
317 }329 }
318 330
@@ -334,8 +346,14 @@ const executeInDeno = ( · 15 unmodified lines
334 */346 */
335export const isDenoAvailable = (347export const isDenoAvailable = (
336 executable: string = defaultDenoExecutable(),348 executable: string = defaultDenoExecutable(),
337): boolean =>349): boolean => {
338 executable.includes("/") ? existsSync(executable) : true;350 const result = spawnSync(executable, ["--version"], {
351 stdio: "ignore",
352 timeout: 5000,
353 });
354
355 return result.error === undefined && result.status === 0;
356};
339 357
340/**358/**
341 * Create a `CodeExecutor` that runs code in a sandboxed Deno subprocess.359 * Create a `CodeExecutor` that runs code in a sandboxed Deno subprocess.
turbo.json+4−0
@@ -5,6 +5,10 @@ · 4 unmodified lines
5 "dependsOn": ["^build"],5 "dependsOn": ["^build"],
6 "outputs": ["dist/**"]6 "outputs": ["dist/**"]
7 },7 },
8 "test": {
9 "dependsOn": ["^test"],
10 "outputs": []
11 },
8 "typecheck": {12 "typecheck": {
9 "dependsOn": ["^typecheck"],13 "dependsOn": ["^typecheck"],
10 "outputs": []14 "outputs": []

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t