History

compat with legacy dir

Rhys Sullivan committed 8 months ago

Commit 04294d21 parent d1301fc6 files · +377 −25Browse files

Unified · Split

6 files
  1. .github/workflows/publish-executor-package.yml +72 −6
  2. apps/executor/package.json +2 −1
  3. apps/executor/src/distribution/build-release.ts +137 −0
  4. apps/executor/src/distribution/portable.ts +2 −0
  5. packages/server/src/config.ts +19 −0
  6. packages/server/src/index.ts +145 −18
.github/workflows/publish-executor-package.yml+72−6
@@ -5,7 +5,7 @@ on: · 4 unmodified lines
5 workflow_dispatch:5 workflow_dispatch:
6 6
7permissions:7permissions:
8 contents: read8 contents: write
9 id-token: write9 id-token: write
10 10
11concurrency:11concurrency:
@@ -19,6 +19,8 @@ jobs: · 7 unmodified lines
19 steps:19 steps:
20 - name: Checkout20 - name: Checkout
21 uses: actions/checkout@v421 uses: actions/checkout@v4
22 with:
23 fetch-depth: 0
22 24
23 - name: Require default branch25 - name: Require default branch
24 if: github.ref_name != github.event.repository.default_branch26 if: github.ref_name != github.event.repository.default_branch
@@ -53,15 +55,50 @@ jobs: · 28 unmodified lines
53 fi55 fi
54 56
55 echo "version=${version}" >> "$GITHUB_OUTPUT"57 echo "version=${version}" >> "$GITHUB_OUTPUT"
58 echo "tag=v${version}" >> "$GITHUB_OUTPUT"
56 echo "Publishing executor@${version}"59 echo "Publishing executor@${version}"
57 60
58 - name: Ensure version is unpublished61 - name: Inspect existing publish state
62 id: state
59 shell: bash63 shell: bash
64 env:
65 GH_TOKEN: ${{ github.token }}
60 run: |66 run: |
61 set -euo pipefail67 set -euo pipefail
68 version="${{ steps.version.outputs.version }}"
69 tag="${{ steps.version.outputs.tag }}"
70 package_published=false
71 release_exists=false
72 tag_exists=false
73
74 if npm view "executor@${version}" version >/dev/null 2>&1; then
75 package_published=true
76 fi
77
78 if gh release view "${tag}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
79 release_exists=true
80 fi
81
82 if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null || git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
83 tag_exists=true
84 fi
85
86 echo "package_published=${package_published}" >> "$GITHUB_OUTPUT"
87 echo "release_exists=${release_exists}" >> "$GITHUB_OUTPUT"
88 echo "tag_exists=${tag_exists}" >> "$GITHUB_OUTPUT"
89
90 if [[ "${package_published}" == true && "${release_exists}" == true ]]; then
91 echo "executor@${version} is already published and ${tag} already exists" >&2
92 exit 1
93 fi
62 94
63 if npm view "executor@${{ steps.version.outputs.version }}" version >/dev/null 2>&1; then95 if [[ "${package_published}" == false && "${release_exists}" == true ]]; then
64 echo "executor@${{ steps.version.outputs.version }} is already published" >&296 echo "GitHub release ${tag} exists, but executor@${version} is not published to npm" >&2
97 exit 1
98 fi
99
100 if [[ "${package_published}" == false && "${tag_exists}" == true && "${release_exists}" == false ]]; then
101 echo "Release tag ${tag} already exists without a GitHub release; resolve that before publishing executor@${version}" >&2
65 exit 1102 exit 1
66 fi103 fi
67 104
@@ -70,9 +107,38 @@ jobs: · 2 unmodified lines
70 bun run --cwd apps/executor typecheck107 bun run --cwd apps/executor typecheck
71 bun run --cwd apps/executor test108 bun run --cwd apps/executor test
72 109
73 - name: Build distribution package110 - name: Build release artifacts
74 run: bun run --cwd apps/executor build:dist111 run: bun run --cwd apps/executor build:release
75 112
76 - name: Publish to npm113 - name: Publish to npm
114 if: steps.state.outputs.package_published != 'true'
77 working-directory: apps/executor115 working-directory: apps/executor
78 run: npm publish ./dist/npm --access public --provenance116 run: npm publish ./dist/npm --access public --provenance
117
118 - name: Create GitHub release
119 if: steps.state.outputs.release_exists != 'true'
120 shell: bash
121 env:
122 GH_TOKEN: ${{ github.token }}
123 run: |
124 set -euo pipefail
125 shopt -s nullglob
126 assets=(
127 apps/executor/dist/release/*.tgz
128 apps/executor/dist/release/checksums.txt
129 apps/executor/dist/release/portable/*.tar.gz
130 apps/executor/dist/release/portable/*.zip
131 )
132
133 if [[ ${#assets[@]} -lt 3 ]]; then
134 echo "Missing release assets under apps/executor/dist/release" >&2
135 exit 1
136 fi
137
138 gh release create "${{ steps.version.outputs.tag }}" \
139 "${assets[@]}" \
140 --repo "${GITHUB_REPOSITORY}" \
141 --target "${GITHUB_SHA}" \
142 --title "${{ steps.version.outputs.tag }}" \
143 --generate-notes \
144 --latest
apps/executor/package.json+2−1
@@ -33,7 +33,8 @@ · 32 unmodified lines
33 "pack:dist": "bun src/distribution/build-package.ts && npm pack ./dist/npm",33 "pack:dist": "bun src/distribution/build-package.ts && npm pack ./dist/npm",
34 "publish:dist:dry-run": "bun src/distribution/build-package.ts && npm publish ./dist/npm --dry-run",34 "publish:dist:dry-run": "bun src/distribution/build-package.ts && npm publish ./dist/npm --dry-run",
35 "publish:dist": "bun src/distribution/build-package.ts && npm publish ./dist/npm",35 "publish:dist": "bun src/distribution/build-package.ts && npm publish ./dist/npm",
36 "build:portable": "bun src/distribution/build-portable.ts"36 "build:portable": "bun src/distribution/build-portable.ts",
37 "build:release": "bun src/distribution/build-release.ts"
37 },38 },
38 "dependencies": {39 "dependencies": {
39 "@effect/cli": "catalog:",40 "@effect/cli": "catalog:",
apps/executor/src/distribution/build-release.ts+137−0
@@ -0,0 +1,137 @@
1import { createHash } from "node:crypto";
2import { spawn } from "node:child_process";
3import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
4import { join, relative, resolve } from "node:path";
5
6import { buildDistributionPackage } from "./artifact";
7import { repoRoot } from "./metadata";
8import { buildPortableDistribution, portableTargetIds } from "./portable";
9
10const defaultReleaseDir = resolve(repoRoot, "apps/executor/dist/release");
11
12type CommandInput = {
13 command: string;
14 args: ReadonlyArray<string>;
15 cwd: string;
16};
17
18type CommandOutput = {
19 stdout: string;
20 stderr: string;
21};
22
23type PackResult = {
24 filename?: string;
25};
26
27const runCommand = async (input: CommandInput): Promise<CommandOutput> => {
28 const child = spawn(input.command, [...input.args], {
29 cwd: input.cwd,
30 env: process.env,
31 stdio: ["ignore", "pipe", "pipe"],
32 });
33
34 let stdout = "";
35 let stderr = "";
36
37 child.stdout?.setEncoding("utf8");
38 child.stdout?.on("data", (chunk) => {
39 stdout += chunk;
40 });
41
42 child.stderr?.setEncoding("utf8");
43 child.stderr?.on("data", (chunk) => {
44 stderr += chunk;
45 });
46
47 const exitCode = await new Promise<number>((resolveExitCode, reject) => {
48 child.once("error", reject);
49 child.once("close", (code) => {
50 resolveExitCode(code ?? -1);
51 });
52 });
53
54 if (exitCode !== 0) {
55 throw new Error(
56 [
57 `${input.command} ${input.args.join(" ")} exited with code ${exitCode}`,
58 stdout.trim().length > 0 ? `stdout:\n${stdout.trim()}` : null,
59 stderr.trim().length > 0 ? `stderr:\n${stderr.trim()}` : null,
60 ]
61 .filter((part) => part !== null)
62 .join("\n\n"),
63 );
64 }
65
66 return {
67 stdout: stdout.trim(),
68 stderr: stderr.trim(),
69 };
70};
71
72const computeSha256 = (contents: Uint8Array): string =>
73 createHash("sha256").update(contents).digest("hex");
74
75const createChecksumsFile = async (
76 releaseDir: string,
77 assetPaths: ReadonlyArray<string>,
78): Promise<string> => {
79 const entries = await Promise.all(
80 [...assetPaths]
81 .sort((left, right) => left.localeCompare(right))
82 .map(async (assetPath) => {
83 const contents = await readFile(assetPath);
84 const relativePath = relative(releaseDir, assetPath).replaceAll("\\", "/");
85 return `${computeSha256(contents)} ${relativePath}`;
86 }),
87 );
88
89 const checksumsPath = join(releaseDir, "checksums.txt");
90 await writeFile(checksumsPath, `${entries.join("\n")}\n`);
91 return checksumsPath;
92};
93
94const packDistributionPackage = async (releaseDir: string): Promise<string> => {
95 const output = await runCommand({
96 command: "npm",
97 args: ["pack", "./apps/executor/dist/npm", "--pack-destination", releaseDir, "--json"],
98 cwd: repoRoot,
99 });
100 const [result] = JSON.parse(output.stdout) as ReadonlyArray<PackResult>;
101 const filename = result?.filename;
102
103 if (!filename) {
104 throw new Error(`npm pack did not report an output filename. stdout:\n${output.stdout}`);
105 }
106
107 return join(releaseDir, filename);
108};
109
110const main = async () => {
111 const releaseDir = defaultReleaseDir;
112 const portableDir = join(releaseDir, "portable");
113
114 await rm(releaseDir, { recursive: true, force: true });
115 await mkdir(releaseDir, { recursive: true });
116
117 await buildDistributionPackage();
118 const packageArchivePath = await packDistributionPackage(releaseDir);
119 const portableArtifact = await buildPortableDistribution({
120 outputDir: portableDir,
121 buildWeb: false,
122 targets: portableTargetIds,
123 });
124 const portableArchivePaths = portableArtifact.artifacts
125 .map((artifact) => artifact.archivePath)
126 .filter((archivePath): archivePath is string => archivePath !== null);
127 const checksumsPath = await createChecksumsFile(releaseDir, [
128 packageArchivePath,
129 ...portableArchivePaths,
130 ]);
131
132 for (const assetPath of [packageArchivePath, ...portableArchivePaths, checksumsPath]) {
133 process.stdout.write(`${assetPath}\n`);
134 }
135};
136
137await main();
apps/executor/src/distribution/portable.ts+2−0
@@ -74,6 +74,8 @@ const portableTargets: Record<string, PortableTarget> = { · 73 unmodified lines
74 },74 },
75};75};
76 76
77export const portableTargetIds = Object.freeze(Object.keys(portableTargets));
78
77export type BuildPortableDistributionOptions = {79export type BuildPortableDistributionOptions = {
78 outputDir?: string;80 outputDir?: string;
79 packageName?: string;81 packageName?: string;
packages/server/src/config.ts+19−0
@@ -23,6 +23,17 @@ const defaultLinuxStateHome = trim(process.env.XDG_STATE_HOME) ?? join(home, ".l · 22 unmodified lines
23const defaultWindowsHome = join(trim(process.env.LOCALAPPDATA) ?? join(home, "AppData", "Local"), "Executor");23const defaultWindowsHome = join(trim(process.env.LOCALAPPDATA) ?? join(home, "AppData", "Local"), "Executor");
24const defaultMacHome = join(home, "Library", "Application Support", "Executor");24const defaultMacHome = join(home, "Library", "Application Support", "Executor");
25const defaultLinuxHome = join(defaultLinuxDataHome, "executor");25const defaultLinuxHome = join(defaultLinuxDataHome, "executor");
26const legacyLinuxHome = join(defaultLinuxDataHome, "executor-v3");
27const legacyWindowsHome = join(
28 trim(process.env.LOCALAPPDATA) ?? join(home, "AppData", "Local"),
29 "Executor-v3",
30);
31const legacyMacHome = join(
32 home,
33 "Library",
34 "Application Support",
35 "Executor-v3",
36);
26 37
27export const DEFAULT_SERVER_PORT = 8788;38export const DEFAULT_SERVER_PORT = 8788;
28export const DEFAULT_SERVER_HOST = "127.0.0.1";39export const DEFAULT_SERVER_HOST = "127.0.0.1";
@@ -43,6 +54,14 @@ export const DEFAULT_LOCAL_DATA_DIR = · 14 unmodified lines
43 trim(process.env[EXECUTOR_LOCAL_DATA_DIR_ENV])54 trim(process.env[EXECUTOR_LOCAL_DATA_DIR_ENV])
44 ?? join(DEFAULT_EXECUTOR_DATA_DIR, "control-plane");55 ?? join(DEFAULT_EXECUTOR_DATA_DIR, "control-plane");
45 56
57export const DEFAULT_LEGACY_LOCAL_DATA_DIRS = explicitExecutorHome
58 ? []
59 : [
60 isWindows ? join(legacyWindowsHome, "control-plane") : null,
61 isMac ? join(legacyMacHome, "control-plane") : null,
62 !isWindows && !isMac ? join(legacyLinuxHome, "control-plane") : null,
63 ].filter((value): value is string => value !== null);
64
46export const DEFAULT_SERVER_PID_FILE =65export const DEFAULT_SERVER_PID_FILE =
47 trim(process.env[EXECUTOR_SERVER_PID_FILE_ENV])66 trim(process.env[EXECUTOR_SERVER_PID_FILE_ENV])
48 ?? join(DEFAULT_EXECUTOR_RUN_DIR, "server.pid");67 ?? join(DEFAULT_EXECUTOR_RUN_DIR, "server.pid");
packages/server/src/index.ts+145−18
@@ -1,13 +1,13 @@
1import { existsSync } from "node:fs";
2import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
1import {3import {
2 createServer as createNodeServer,4 createServer as createNodeServer,
3 type IncomingMessage,5 type IncomingMessage,
4 type ServerResponse,6 type ServerResponse,
5} from "node:http";7} from "node:http";
6import type { AddressInfo } from "node:net";8import type { AddressInfo } from "node:net";
7import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
8import { Readable } from "node:stream";
9import { dirname, extname, resolve } from "node:path";9import { dirname, extname, resolve } from "node:path";
10 10import { Readable } from "node:stream";
11import { HttpApiBuilder, HttpServer } from "@effect/platform";11import { HttpApiBuilder, HttpServer } from "@effect/platform";
12import {12import {
13 createControlPlaneApiLayer,13 createControlPlaneApiLayer,
@@ -17,11 +17,14 @@ import { · 3 unmodified lines
17 type SqlControlPlaneRuntime,17 type SqlControlPlaneRuntime,
18} from "@executor/control-plane";18} from "@executor/control-plane";
19import { createExecutorMcpRequestHandler } from "@executor/executor-mcp";19import { createExecutorMcpRequestHandler } from "@executor/executor-mcp";
20import * as Cause from "effect/Cause";
20import * as Effect from "effect/Effect";21import * as Effect from "effect/Effect";
22import * as Exit from "effect/Exit";
21import * as Layer from "effect/Layer";23import * as Layer from "effect/Layer";
22import type * as Scope from "effect/Scope";24import type * as Scope from "effect/Scope";
23 25
24import {26import {
27 DEFAULT_LEGACY_LOCAL_DATA_DIRS,
25 DEFAULT_LOCAL_DATA_DIR,28 DEFAULT_LOCAL_DATA_DIR,
26 DEFAULT_SERVER_LOG_FILE,29 DEFAULT_SERVER_LOG_FILE,
27 DEFAULT_SERVER_PID_FILE,30 DEFAULT_SERVER_PID_FILE,
@@ -79,6 +82,9 @@ export type LocalExecutorRequestHandler = { · 51 unmodified lines
79 readonly setBaseUrl: (baseUrl: string) => void;82 readonly setBaseUrl: (baseUrl: string) => void;
80};83};
81 84
85type ControlPlaneWebHandler = ReturnType<typeof HttpApiBuilder.toWebHandler>;
86type ExecutorMcpHandler = ReturnType<typeof createExecutorMcpRequestHandler>;
87
82const disposeRuntime = (runtime: SqlControlPlaneRuntime) =>88const disposeRuntime = (runtime: SqlControlPlaneRuntime) =>
83 Effect.tryPromise({89 Effect.tryPromise({
84 try: () => runtime.close(),90 try: () => runtime.close(),
@@ -86,6 +92,121 @@ const disposeRuntime = (runtime: SqlControlPlaneRuntime) => · 1 unmodified line
86 cause instanceof Error ? cause : new Error(String(cause ?? "runtime close failed")),92 cause instanceof Error ? cause : new Error(String(cause ?? "runtime close failed")),
87 }).pipe(Effect.orDie);93 }).pipe(Effect.orDie);
88 94
95const createRuntime = (
96 localDataDir: string,
97 getLocalServerBaseUrl: () => string | undefined,
98 options: StartLocalExecutorServerOptions,
99) =>
100 createSqlControlPlaneRuntime({
101 localDataDir,
102 migrationsFolder: options.migrationsFolder,
103 executionResolver: options.executionResolver,
104 resolveSecretMaterial: options.resolveSecretMaterial,
105 getLocalServerBaseUrl,
106 }).pipe(
107 Effect.mapError((cause) =>
108 cause instanceof Error ? cause : new Error(String(cause)),
109 ),
110 );
111
112const moveLegacyLocalDataDir = (
113 legacyLocalDataDir: string,
114 requestedLocalDataDir: string,
115 ) =>
116 Effect.tryPromise({
117 try: async () => {
118 await mkdir(dirname(requestedLocalDataDir), { recursive: true });
119 if (existsSync(requestedLocalDataDir)) {
120 const backupPath = `${requestedLocalDataDir}.backup-${Date.now()}`;
121 await rename(requestedLocalDataDir, backupPath);
122 console.warn(
123 `[executor] Backed up unreadable local data dir to: ${backupPath}`,
124 );
125 }
126 await rename(legacyLocalDataDir, requestedLocalDataDir);
127 console.warn(
128 `[executor] Moved legacy local data dir to: ${requestedLocalDataDir}`,
129 );
130 },
131 catch: (cause) =>
132 cause instanceof Error ? cause : new Error(String(cause)),
133 });
134
135const createRuntimeWithLegacyMigration = (
136 requestedLocalDataDir: string,
137 legacyLocalDataDirs: ReadonlyArray<string>,
138 getLocalServerBaseUrl: () => string | undefined,
139 options: StartLocalExecutorServerOptions,
140 ) =>
141 Effect.gen(function* () {
142 if (
143 requestedLocalDataDir !== ":memory:"
144 && !existsSync(requestedLocalDataDir)
145 && legacyLocalDataDirs.length > 0
146 ) {
147 for (const legacyLocalDataDir of legacyLocalDataDirs) {
148 const legacyExit = yield* Effect.exit(
149 createRuntime(legacyLocalDataDir, getLocalServerBaseUrl, options),
150 );
151 if (Exit.isFailure(legacyExit)) {
152 continue;
153 }
154 yield* disposeRuntime(legacyExit.value);
155 const migrationExit = yield* Effect.exit(
156 moveLegacyLocalDataDir(legacyLocalDataDir, requestedLocalDataDir),
157 );
158 if (Exit.isFailure(migrationExit)) {
159 continue;
160 }
161 const migratedExit = yield* Effect.exit(
162 createRuntime(requestedLocalDataDir, getLocalServerBaseUrl, options),
163 );
164 if (Exit.isSuccess(migratedExit)) {
165 return migratedExit.value;
166 }
167 }
168 }
169 const primaryExit = yield* Effect.exit(
170 createRuntime(requestedLocalDataDir, getLocalServerBaseUrl, options),
171 );
172 if (Exit.isSuccess(primaryExit)) {
173 return primaryExit.value;
174 }
175 const primaryError = Cause.squash(primaryExit.cause);
176 if (legacyLocalDataDirs.length > 0) {
177 yield* Effect.sync(() => {
178 console.warn(
179 `[executor] Failed to open default local data dir: ${requestedLocalDataDir}`,
180 primaryError instanceof Error ? primaryError.message : String(primaryError),
181 );
182 });
183 }
184 for (const legacyLocalDataDir of legacyLocalDataDirs) {
185 const legacyExit = yield* Effect.exit(
186 createRuntime(legacyLocalDataDir, getLocalServerBaseUrl, options),
187 );
188 if (Exit.isFailure(legacyExit)) {
189 continue;
190 }
191 yield* disposeRuntime(legacyExit.value);
192 const migrationExit = yield* Effect.exit(
193 moveLegacyLocalDataDir(legacyLocalDataDir, requestedLocalDataDir),
194 );
195 if (Exit.isFailure(migrationExit)) {
196 continue;
197 }
198 const migratedExit = yield* Effect.exit(
199 createRuntime(requestedLocalDataDir, getLocalServerBaseUrl, options),
200 );
201 if (Exit.isSuccess(migratedExit)) {
202 return migratedExit.value;
203 }
204 }
205 return yield* Effect.fail(
206 primaryError instanceof Error ? primaryError : new Error(String(primaryError)),
207 );
208 });
209
89const createControlPlaneWebHandler = (runtime: SqlControlPlaneRuntime) =>210const createControlPlaneWebHandler = (runtime: SqlControlPlaneRuntime) =>
90 Effect.acquireRelease(211 Effect.acquireRelease(
91 Effect.sync(() =>212 Effect.sync(() =>
@@ -96,7 +217,7 @@ const createControlPlaneWebHandler = (runtime: SqlControlPlaneRuntime) => · 4 unmodified lines
96 ),217 ),
97 ),218 ),
98 ),219 ),
99 (handler) => Effect.tryPromise({ try: () => handler.dispose(), catch: (cause) => cause instanceof Error ? cause : new Error(String(cause ?? "web handler dispose failed")) }).pipe(Effect.orDie),220 (handler: ControlPlaneWebHandler) => Effect.tryPromise({ try: () => handler.dispose(), catch: (cause) => cause instanceof Error ? cause : new Error(String(cause ?? "web handler dispose failed")) }).pipe(Effect.orDie),
100 );221 );
101 222
102const safeFilePath = (assetsDir: string, pathname: string): string | null => {223const safeFilePath = (assetsDir: string, pathname: string): string | null => {
@@ -255,11 +376,15 @@ export const createLocalExecutorRequestHandler = ( · 152 unmodified lines
255 options: StartLocalExecutorServerOptions = {},376 options: StartLocalExecutorServerOptions = {},
256): Effect.Effect<LocalExecutorRequestHandler, Error, Scope.Scope> =>377): Effect.Effect<LocalExecutorRequestHandler, Error, Scope.Scope> =>
257 Effect.gen(function* () {378 Effect.gen(function* () {
258 const localDataDir = options.localDataDir ?? DEFAULT_LOCAL_DATA_DIR;379 const requestedLocalDataDir = options.localDataDir ?? DEFAULT_LOCAL_DATA_DIR;
380 const legacyLocalDataDirs =
381 options.localDataDir === undefined
382 ? DEFAULT_LEGACY_LOCAL_DATA_DIRS.filter((candidate) => existsSync(candidate))
383 : [];
259 384
260 if (localDataDir !== ":memory:") {385 if (requestedLocalDataDir !== ":memory:") {
261 yield* Effect.tryPromise({386 yield* Effect.tryPromise({
262 try: () => mkdir(dirname(localDataDir), { recursive: true }),387 try: () => mkdir(dirname(requestedLocalDataDir), { recursive: true }),
263 catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),388 catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
264 });389 });
265 }390 }
@@ -267,16 +392,11 @@ export const createLocalExecutorRequestHandler = ( · 1 unmodified line
267 let baseUrlRef: string | undefined;392 let baseUrlRef: string | undefined;
268 393
269 const runtime = yield* Effect.acquireRelease(394 const runtime = yield* Effect.acquireRelease(
270 createSqlControlPlaneRuntime({395 createRuntimeWithLegacyMigration(
271 localDataDir,396 requestedLocalDataDir,
272 migrationsFolder: options.migrationsFolder,397 legacyLocalDataDirs,
273 executionResolver: options.executionResolver,398 () => baseUrlRef,
274 resolveSecretMaterial: options.resolveSecretMaterial,399 options,
275 getLocalServerBaseUrl: () => baseUrlRef,
276 }).pipe(
277 Effect.mapError((cause) =>
278 cause instanceof Error ? cause : new Error(String(cause)),
279 ),
280 ),400 ),
281 disposeRuntime,401 disposeRuntime,
282 );402 );
@@ -284,7 +404,14 @@ export const createLocalExecutorRequestHandler = ( · 1 unmodified line
284 const apiHandler = yield* createControlPlaneWebHandler(runtime);404 const apiHandler = yield* createControlPlaneWebHandler(runtime);
285 const mcpHandler = yield* Effect.acquireRelease(405 const mcpHandler = yield* Effect.acquireRelease(
286 Effect.sync(() => createExecutorMcpRequestHandler(runtime)),406 Effect.sync(() => createExecutorMcpRequestHandler(runtime)),
287 (handler) => Effect.tryPromise({ try: () => handler.close(), catch: (cause) => cause instanceof Error ? cause : new Error(String(cause ?? "mcp handler close failed")) }).pipe(Effect.orDie),407 (handler: ExecutorMcpHandler) =>
408 Effect.tryPromise({
409 try: () => handler.close(),
410 catch: (cause) =>
411 cause instanceof Error
412 ? cause
413 : new Error(String(cause ?? "mcp handler close failed")),
414 }).pipe(Effect.orDie),
288 );415 );
289 416
290 return {417 return {