History

Use Effect-native local workspace IO

Rhys Sullivan committed 8 months ago

Commit 402a78c1 parent 1dd8a8d18 files · +518 −632Browse files

Unified · Split

18 files
  1. apps/executor/src/server/server.test.ts +5 −4
  2. packages/control-plane/src/runtime/index.ts +5 −10
  3. packages/control-plane/src/runtime/local-config.test.ts +9 −23
  4. packages/control-plane/src/runtime/local-config.ts +164 −116
  5. packages/control-plane/src/runtime/local-installation.test.ts +3 −3
  6. packages/control-plane/src/runtime/local-operations.ts +8 −1
  7. packages/control-plane/src/runtime/local-source-artifacts.ts +63 −42
  8. packages/control-plane/src/runtime/local-workspace-state.ts +51 −27
  9. packages/control-plane/src/runtime/local-workspace-sync.ts +4 −13
  10. packages/control-plane/src/runtime/policies-operations.ts +14 −26
  11. packages/control-plane/src/runtime/source-auth-service.test.ts +4 −12
  12. packages/control-plane/src/runtime/source-inspection.test.ts +8 −16
  13. packages/control-plane/src/runtime/source-materialization.ts +22 −50
  14. packages/control-plane/src/runtime/source-recipes-runtime.test.ts +14 −21
  15. packages/control-plane/src/runtime/source-recipes-runtime.ts +6 −16
  16. packages/control-plane/src/runtime/source-store.test.ts +4 −20
  17. packages/control-plane/src/runtime/source-store.ts +26 −63
  18. packages/control-plane/src/runtime/workspace-execution-environment.test.ts +108 −169
apps/executor/src/server/server.test.ts+5−4
@@ -92,14 +92,14 @@ const writeConfiguredLocalMcpSource = (input: { · 91 unmodified lines
92 name?: string;92 name?: string;
93 namespace?: string;93 namespace?: string;
94}) =>94}) =>
95 Effect.promise(async () => {95 Effect.gen(function* () {
96 const sourceId = SourceIdSchema.make(input.configKey);96 const sourceId = SourceIdSchema.make(input.configKey);
97 const context = await resolveLocalWorkspaceContext({97 const context = yield* resolveLocalWorkspaceContext({
98 workspaceRoot: input.workspaceRoot,98 workspaceRoot: input.workspaceRoot,
99 });99 });
100 const installation = deriveLocalInstallation(context);100 const installation = deriveLocalInstallation(context);
101 101
102 await writeProjectLocalExecutorConfig({102 yield* writeProjectLocalExecutorConfig({
103 context,103 context,
104 config: {104 config: {
105 sources: {105 sources: {
@@ -162,7 +162,7 @@ const writeConfiguredLocalMcpSource = (input: { · 56 unmodified lines
162 }],162 }],
163 });163 });
164 164
165 await writeLocalSourceArtifact({165 yield* writeLocalSourceArtifact({
166 context,166 context,
167 sourceId,167 sourceId,
168 artifact: buildLocalSourceArtifact({168 artifact: buildLocalSourceArtifact({
@@ -718,6 +718,7 @@ describe("local-executor-server", () => { · 549 unmodified lines
718 expect(execution.execution.status).toBe("completed");718 expect(execution.execution.status).toBe("completed");
719 expect(execution.execution.resultJson).toBe(JSON.stringify({ sum: 42 }));719 expect(execution.execution.resultJson).toBe(JSON.stringify({ sum: 42 }));
720 }),720 }),
721 15_000,
721 );722 );
722 723
723 it.scoped("includes completed MCP return values in text content", () =>724 it.scoped("includes completed MCP return values in text content", () =>
packages/control-plane/src/runtime/index.ts+5−10
@@ -193,13 +193,10 @@ export const createControlPlaneRuntime = ( · 192 unmodified lines
193 const persistence = Context.get(baseContext, SqlControlPlanePersistenceService);193 const persistence = Context.get(baseContext, SqlControlPlanePersistenceService);
194 const rows = Context.get(baseContext, SqlControlPlaneRowsService);194 const rows = Context.get(baseContext, SqlControlPlaneRowsService);
195 195
196 const localWorkspaceContext = yield* Effect.tryPromise({196 const localWorkspaceContext = yield* resolveLocalWorkspaceContext({
197 try: () =>197 workspaceRoot: options.workspaceRoot,
198 resolveLocalWorkspaceContext({
199 workspaceRoot: options.workspaceRoot,
200 }),
201 catch: toLocalRuntimeBootstrapError,
202 }).pipe(198 }).pipe(
199 Effect.mapError(toLocalRuntimeBootstrapError),
203 Effect.catchAll((error) =>200 Effect.catchAll((error) =>
204 closeScope(scope).pipe(201 closeScope(scope).pipe(
205 Effect.zipRight(Effect.fail(error)),202 Effect.zipRight(Effect.fail(error)),
@@ -217,10 +214,8 @@ export const createControlPlaneRuntime = ( · 11 unmodified lines
217 )),214 )),
218 );215 );
219 216
220 const loadedLocalConfig = yield* Effect.tryPromise({217 const loadedLocalConfig = yield* loadLocalExecutorConfig(localWorkspaceContext).pipe(
221 try: () => loadLocalExecutorConfig(localWorkspaceContext),218 Effect.mapError(toLocalRuntimeBootstrapError),
222 catch: toLocalRuntimeBootstrapError,
223 }).pipe(
224 Effect.catchAll((error) =>219 Effect.catchAll((error) =>
225 closeScope(scope).pipe(220 closeScope(scope).pipe(
226 Effect.zipRight(Effect.fail(error)),221 Effect.zipRight(Effect.fail(error)),
packages/control-plane/src/runtime/local-config.test.ts+9−23
@@ -43,12 +43,8 @@ describe("local-config", () => { · 42 unmodified lines
43 ),43 ),
44 );44 );
45 45
46 const context = yield* Effect.promise(() =>46 const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
47 resolveLocalWorkspaceContext({ workspaceRoot }),47 const loaded = yield* loadLocalExecutorConfig(context);
48 );
49 const loaded = yield* Effect.promise(() =>
50 loadLocalExecutorConfig(context),
51 );
52 48
53 expect(loaded.config?.sources?.github?.kind).toBe("openapi");49 expect(loaded.config?.sources?.github?.kind).toBe("openapi");
54 expect(loaded.config?.sources?.github?.connection.endpoint).toBe(50 expect(loaded.config?.sources?.github?.connection.endpoint).toBe(
@@ -80,16 +76,8 @@ describe("local-config", () => { · 25 unmodified lines
80 ),76 ),
81 );77 );
82 78
83 const context = yield* Effect.promise(() =>79 const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
84 resolveLocalWorkspaceContext({ workspaceRoot }),80 const failure = yield* Effect.flip(loadLocalExecutorConfig(context));
85 );
86 const failure = yield* Effect.flip(
87 Effect.tryPromise({
88 try: () => loadLocalExecutorConfig(context),
89 catch: (cause) =>
90 cause instanceof Error ? cause : new Error(String(cause)),
91 }),
92 );
93 81
94 expect(failure.message).toContain("Invalid executor config");82 expect(failure.message).toContain("Invalid executor config");
95 expect(failure.message).toContain("line 5, column 7");83 expect(failure.message).toContain("line 5, column 7");
@@ -128,13 +116,11 @@ describe("local-config", () => { · 32 unmodified lines
128 ),116 ),
129 );117 );
130 118
131 const resolvedPath = yield* Effect.promise(() =>119 const resolvedPath = yield* resolveHomeConfigPath({
132 resolveHomeConfigPath({120 platform: "darwin",
133 platform: "darwin",121 homeDirectory,
134 homeDirectory,122 env: {},
135 env: {},123 });
136 }),
137 );
138 124
139 expect(resolvedPath).toBe(join(legacyConfigDirectory, "executor.jsonc"));125 expect(resolvedPath).toBe(join(legacyConfigDirectory, "executor.jsonc"));
140 }),126 }),
packages/control-plane/src/runtime/local-config.ts+164−116
@@ -1,7 +1,8 @@
1import { createHash } from "node:crypto";1import { createHash } from "node:crypto";
2import { homedir } from "node:os";2import { homedir } from "node:os";
3import { basename, dirname, isAbsolute, join, resolve } from "node:path";3import { basename, dirname, isAbsolute, join, resolve } from "node:path";
4import { promises as fs } from "node:fs";4import { FileSystem } from "@effect/platform";
5import { NodeFileSystem } from "@effect/platform-node";
5import {6import {
6 type ParseError as JsoncParseError,7 type ParseError as JsoncParseError,
7 parse as parseJsoncDocument,8 parse as parseJsoncDocument,
@@ -15,6 +16,7 @@ import { · 7 unmodified lines
15 type LocalConfigSecretProvider,16 type LocalConfigSecretProvider,
16 type LocalConfigSource,17 type LocalConfigSource,
17} from "#schema";18} from "#schema";
19import * as Effect from "effect/Effect";
18import * as Schema from "effect/Schema";20import * as Schema from "effect/Schema";
19 21
20const decodeLocalExecutorConfig = Schema.decodeUnknownSync(LocalExecutorConfigSchema);22const decodeLocalExecutorConfig = Schema.decodeUnknownSync(LocalExecutorConfigSchema);
@@ -30,22 +32,18 @@ const normalizeSlashPath = (value: string): string => · 9 unmodified lines
30const stableHash = (value: string): string =>32const stableHash = (value: string): string =>
31 createHash("sha256").update(value).digest("hex").slice(0, 16);33 createHash("sha256").update(value).digest("hex").slice(0, 16);
32 34
33const fileExists = async (path: string): Promise<boolean> => {35const provideNodeFileSystem = <A, E, R>(
34 try {36 effect: Effect.Effect<A, E, R | FileSystem.FileSystem>,
35 await fs.access(path);37): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> =>
36 return true;38 effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect<
37 } catch {39 A,
38 return false;40 E,
39 }41 Exclude<R, FileSystem.FileSystem>
40};42 >;
41 43
42const pathExists = async (path: string): Promise<boolean> => {44const mapFileSystemError = (path: string, action: string) => (cause: unknown): Error => {
43 try {45 const message = cause instanceof Error ? cause.message : String(cause);
44 await fs.access(path);46 return new Error(`Failed to ${action} ${path}: ${message}`);
45 return true;
46 } catch {
47 return false;
48 }
49};47};
50 48
51const trimOrUndefined = (value: string | undefined | null): string | undefined => {49const trimOrUndefined = (value: string | undefined | null): string | undefined => {
@@ -111,21 +109,26 @@ export const resolveDefaultHomeConfigCandidates = (input: { · 59 unmodified lines
111 ]);109 ]);
112};110};
113 111
114export const resolveHomeConfigPath = async (input: {112export const resolveHomeConfigPath = (input: {
115 env?: NodeJS.ProcessEnv;113 env?: NodeJS.ProcessEnv;
116 platform?: NodeJS.Platform;114 platform?: NodeJS.Platform;
117 homeDirectory?: string;115 homeDirectory?: string;
118} = {}): Promise<string> => {116} = {}): Effect.Effect<string, Error> =>
119 const candidates = resolveDefaultHomeConfigCandidates(input);117 provideNodeFileSystem(Effect.gen(function* () {
120 118 const fs = yield* FileSystem.FileSystem;
121 for (const candidate of candidates) {119 const candidates = resolveDefaultHomeConfigCandidates(input);
122 if (await fileExists(candidate)) {120
123 return candidate;121 for (const candidate of candidates) {
122 const exists = yield* fs.exists(candidate).pipe(
123 Effect.mapError(mapFileSystemError(candidate, "check config path")),
124 );
125 if (exists) {
126 return candidate;
127 }
124 }128 }
125 }
126 129
127 return candidates[0]!;130 return candidates[0]!;
128};131 }));
129 132
130const formatJsoncParseErrors = (content: string, errors: readonly JsoncParseError[]): string => {133const formatJsoncParseErrors = (content: string, errors: readonly JsoncParseError[]): string => {
131 const lines = content.split("\n");134 const lines = content.split("\n");
@@ -233,47 +236,74 @@ export const mergeLocalExecutorConfigs = ( · 101 unmodified lines
233 });236 });
234};237};
235 238
236const resolveProjectConfigPath = async (workspaceRoot: string): Promise<string> => {239const resolveProjectConfigPathEffect = (workspaceRoot: string) =>
237 const jsoncPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_BASENAME);240 Effect.gen(function* () {
238 if (await fileExists(jsoncPath)) {241 const fs = yield* FileSystem.FileSystem;
239 return jsoncPath;242 const jsoncPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_BASENAME);
240 }243 const jsoncExists = yield* fs.exists(jsoncPath).pipe(
241 244 Effect.mapError(mapFileSystemError(jsoncPath, "check project config path")),
242 const jsonPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_FALLBACK_BASENAME);245 );
243 if (await fileExists(jsonPath)) {246 if (jsoncExists) {
244 return jsonPath;247 return jsoncPath;
245 }248 }
246
247 return jsoncPath;
248};
249 249
250const hasProjectConfig = async (workspaceRoot: string): Promise<boolean> =>250 const jsonPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_FALLBACK_BASENAME);
251 fileExists(join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_BASENAME))251 const jsonExists = yield* fs.exists(jsonPath).pipe(
252 || fileExists(join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_FALLBACK_BASENAME));252 Effect.mapError(mapFileSystemError(jsonPath, "check project config path")),
253 );
254 return jsonExists ? jsonPath : jsoncPath;
255 });
253 256
254const resolveWorkspaceRootFromCwd = async (cwd: string): Promise<string> => {257const hasProjectConfigEffect = (workspaceRoot: string) =>
255 let current = resolve(cwd);258 Effect.gen(function* () {
256 let nearestProjectConfigRoot: string | null = null;259 const fs = yield* FileSystem.FileSystem;
257 let nearestGitRoot: string | null = null;260 const jsoncPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_BASENAME);
261 const jsonPath = join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, PROJECT_CONFIG_FALLBACK_BASENAME);
262 const [jsoncExists, jsonExists] = yield* Effect.all([
263 fs.exists(jsoncPath).pipe(
264 Effect.mapError(mapFileSystemError(jsoncPath, "check project config path")),
265 ),
266 fs.exists(jsonPath).pipe(
267 Effect.mapError(mapFileSystemError(jsonPath, "check project config path")),
268 ),
269 ]);
270 return jsoncExists || jsonExists;
271 });
258 272
259 while (true) {273const resolveWorkspaceRootFromCwdEffect = (cwd: string) =>
260 if (nearestProjectConfigRoot === null && (await hasProjectConfig(current))) {274 Effect.gen(function* () {
261 nearestProjectConfigRoot = current;275 const fs = yield* FileSystem.FileSystem;
262 }276 let current = resolve(cwd);
277 let nearestProjectConfigRoot: string | null = null;
278 let nearestGitRoot: string | null = null;
279
280 while (true) {
281 if (nearestProjectConfigRoot === null) {
282 const hasProjectConfig = yield* hasProjectConfigEffect(current);
283 if (hasProjectConfig) {
284 nearestProjectConfigRoot = current;
285 }
286 }
263 287
264 if (nearestGitRoot === null && (await pathExists(join(current, ".git")))) {288 if (nearestGitRoot === null) {
265 nearestGitRoot = current;289 const gitPath = join(current, ".git");
266 }290 const gitExists = yield* fs.exists(gitPath).pipe(
291 Effect.mapError(mapFileSystemError(gitPath, "check git root")),
292 );
293 if (gitExists) {
294 nearestGitRoot = current;
295 }
296 }
267 297
268 const parent = dirname(current);298 const parent = dirname(current);
269 if (parent === current) {299 if (parent === current) {
270 break;300 break;
301 }
302 current = parent;
271 }303 }
272 current = parent;
273 }
274 304
275 return nearestProjectConfigRoot ?? nearestGitRoot ?? resolve(cwd);305 return nearestProjectConfigRoot ?? nearestGitRoot ?? resolve(cwd);
276};306 });
277 307
278export type ResolvedLocalWorkspaceContext = {308export type ResolvedLocalWorkspaceContext = {
279 cwd: string;309 cwd: string;
@@ -295,73 +325,91 @@ export type LoadedLocalExecutorConfig = { · 15 unmodified lines
295 projectConfigPath: string;325 projectConfigPath: string;
296};326};
297 327
298export const resolveLocalWorkspaceContext = async (input: {328export const resolveLocalWorkspaceContext = (input: {
299 cwd?: string;329 cwd?: string;
300 workspaceRoot?: string;330 workspaceRoot?: string;
301} = {}): Promise<ResolvedLocalWorkspaceContext> => {331} = {}): Effect.Effect<ResolvedLocalWorkspaceContext, Error> =>
302 const cwd = resolve(input.cwd ?? process.cwd());332 provideNodeFileSystem(Effect.gen(function* () {
303 const workspaceRoot = resolve(333 const cwd = resolve(input.cwd ?? process.cwd());
304 input.workspaceRoot ?? (await resolveWorkspaceRootFromCwd(cwd)),334 const workspaceRoot = resolve(
305 );335 input.workspaceRoot ?? (yield* resolveWorkspaceRootFromCwdEffect(cwd)),
306 const workspaceName = basename(workspaceRoot) || "workspace";336 );
307 const projectConfigPath = await resolveProjectConfigPath(workspaceRoot);337 const workspaceName = basename(workspaceRoot) || "workspace";
308 const homeConfigPath = await resolveHomeConfigPath();338 const projectConfigPath = yield* resolveProjectConfigPathEffect(workspaceRoot);
309 339 const homeConfigPath = yield* resolveHomeConfigPath();
310 return {340
311 cwd,341 return {
312 workspaceRoot,342 cwd,
313 workspaceName,343 workspaceRoot,
314 configDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY),344 workspaceName,
315 projectConfigPath,345 configDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY),
316 homeConfigPath,346 projectConfigPath,
317 artifactsDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "artifacts"),347 homeConfigPath,
318 stateDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "state"),348 artifactsDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "artifacts"),
319 installationId: `local_${stableHash(normalizeSlashPath(workspaceRoot))}`,349 stateDirectory: join(workspaceRoot, PROJECT_CONFIG_DIRECTORY, "state"),
320 };350 installationId: `local_${stableHash(normalizeSlashPath(workspaceRoot))}`,
321};351 };
322 352 }));
323export const readOptionalLocalExecutorConfig = async (353
354export const readOptionalLocalExecutorConfig = (
324 path: string,355 path: string,
325): Promise<LocalExecutorConfig | null> => {356): Effect.Effect<LocalExecutorConfig | null, Error> =>
326 if (!(await fileExists(path))) {357 provideNodeFileSystem(Effect.gen(function* () {
327 return null;358 const fs = yield* FileSystem.FileSystem;
328 }359 const exists = yield* fs.exists(path).pipe(
360 Effect.mapError(mapFileSystemError(path, "check config path")),
361 );
362 if (!exists) {
363 return null;
364 }
329 365
330 const content = await fs.readFile(path, "utf8");366 const content = yield* fs.readFileString(path, "utf8").pipe(
331 return parseJsonc({ path, content });367 Effect.mapError(mapFileSystemError(path, "read config")),
332};368 );
369 return yield* Effect.try({
370 try: () => parseJsonc({ path, content }),
371 catch: (cause) =>
372 cause instanceof Error ? cause : new Error(String(cause)),
373 });
374 }));
333 375
334export const loadLocalExecutorConfig = async (376export const loadLocalExecutorConfig = (
335 context: ResolvedLocalWorkspaceContext,377 context: ResolvedLocalWorkspaceContext,
336): Promise<LoadedLocalExecutorConfig> => {378): Effect.Effect<LoadedLocalExecutorConfig, Error> =>
337 const [homeConfig, projectConfig] = await Promise.all([379 Effect.gen(function* () {
338 readOptionalLocalExecutorConfig(context.homeConfigPath),380 const [homeConfig, projectConfig] = yield* Effect.all([
339 readOptionalLocalExecutorConfig(context.projectConfigPath),381 readOptionalLocalExecutorConfig(context.homeConfigPath),
340 ]);382 readOptionalLocalExecutorConfig(context.projectConfigPath),
341 383 ]);
342 return {384
343 config: mergeLocalExecutorConfigs(homeConfig, projectConfig),385 return {
344 homeConfig,386 config: mergeLocalExecutorConfigs(homeConfig, projectConfig),
345 projectConfig,387 homeConfig,
346 homeConfigPath: context.homeConfigPath,388 projectConfig,
347 projectConfigPath: context.projectConfigPath,389 homeConfigPath: context.homeConfigPath,
348 };390 projectConfigPath: context.projectConfigPath,
349};391 };
392 });
350 393
351export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string =>394export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string =>
352 `${JSON.stringify(config, null, 2)}\n`;395 `${JSON.stringify(config, null, 2)}\n`;
353 396
354export const writeProjectLocalExecutorConfig = async (input: {397export const writeProjectLocalExecutorConfig = (input: {
355 context: ResolvedLocalWorkspaceContext;398 context: ResolvedLocalWorkspaceContext;
356 config: LocalExecutorConfig;399 config: LocalExecutorConfig;
357}): Promise<void> => {400}): Effect.Effect<void, Error> =>
358 await fs.mkdir(input.context.configDirectory, { recursive: true });401 provideNodeFileSystem(Effect.gen(function* () {
359 await fs.writeFile(402 const fs = yield* FileSystem.FileSystem;
360 input.context.projectConfigPath,403 yield* fs.makeDirectory(input.context.configDirectory, { recursive: true }).pipe(
361 encodeLocalExecutorConfig(input.config),404 Effect.mapError(mapFileSystemError(input.context.configDirectory, "create config directory")),
362 "utf8",405 );
363 );406 yield* fs.writeFileString(
364};407 input.context.projectConfigPath,
408 encodeLocalExecutorConfig(input.config),
409 ).pipe(
410 Effect.mapError(mapFileSystemError(input.context.projectConfigPath, "write config")),
411 );
412 }));
365 413
366export const resolveConfigRelativePath = (input: {414export const resolveConfigRelativePath = (input: {
367 path: string;415 path: string;
packages/control-plane/src/runtime/local-installation.test.ts+3−3
@@ -36,9 +36,9 @@ describe("local-installation", () => { · 35 unmodified lines
36 it.scoped("is idempotent when loading the default local installation", () =>36 it.scoped("is idempotent when loading the default local installation", () =>
37 Effect.gen(function* () {37 Effect.gen(function* () {
38 const runtime = yield* makeRuntime;38 const runtime = yield* makeRuntime;
39 const context = yield* Effect.promise(() =>39 const context = yield* resolveLocalWorkspaceContext({
40 resolveLocalWorkspaceContext({ workspaceRoot: TEST_WORKSPACE_ROOT }),40 workspaceRoot: TEST_WORKSPACE_ROOT,
41 );41 });
42 42
43 const first = runtime.localInstallation;43 const first = runtime.localInstallation;
44 const second = yield* getOrProvisionLocalInstallation({44 const second = yield* getOrProvisionLocalInstallation({
packages/control-plane/src/runtime/local-operations.ts+8−1
@@ -185,7 +185,14 @@ export const getLocalInstallation = () => · 184 unmodified lines
185 const store = yield* ControlPlaneStore;185 const store = yield* ControlPlaneStore;
186 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();186 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();
187 const context = runtimeLocalWorkspace?.context187 const context = runtimeLocalWorkspace?.context
188 ?? (yield* Effect.promise(() => resolveLocalWorkspaceContext()));188 ?? (yield* resolveLocalWorkspaceContext().pipe(
189 Effect.mapError((error) =>
190 localOps.installation.unknownStorage(
191 error,
192 "Failed resolving local workspace context",
193 ),
194 ),
195 ));
189 196
190 return yield* loadLocalInstallation(store, context);197 return yield* loadLocalInstallation(store, context);
191 });198 });
packages/control-plane/src/runtime/local-source-artifacts.ts+63−42
@@ -1,5 +1,6 @@
1import { promises as fs } from "node:fs";
2import { join } from "node:path";1import { join } from "node:path";
2import { FileSystem } from "@effect/platform";
3import { NodeFileSystem } from "@effect/platform-node";
3 4
4import {5import {
5 SourceIdSchema,6 SourceIdSchema,
@@ -12,6 +13,7 @@ import { · 6 unmodified lines
12 type Source,13 type Source,
13 type SourceRecipeId,14 type SourceRecipeId,
14} from "#schema";15} from "#schema";
16import * as Effect from "effect/Effect";
15import * as Schema from "effect/Schema";17import * as Schema from "effect/Schema";
16 18
17import type { ResolvedLocalWorkspaceContext } from "./local-config";19import type { ResolvedLocalWorkspaceContext } from "./local-config";
@@ -41,6 +43,20 @@ export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type; · 23 unmodified lines
41 43
42const decodeLocalSourceArtifact = Schema.decodeUnknownSync(LocalSourceArtifactSchema);44const decodeLocalSourceArtifact = Schema.decodeUnknownSync(LocalSourceArtifactSchema);
43 45
46const provideNodeFileSystem = <A, E, R>(
47 effect: Effect.Effect<A, E, R | FileSystem.FileSystem>,
48): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> =>
49 effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect<
50 A,
51 E,
52 Exclude<R, FileSystem.FileSystem>
53 >;
54
55const mapFileSystemError = (path: string, action: string) => (cause: unknown): Error => {
56 const message = cause instanceof Error ? cause.message : String(cause);
57 return new Error(`Failed to ${action} ${path}: ${message}`);
58};
59
44const localSourceArtifactPath = (input: {60const localSourceArtifactPath = (input: {
45 context: ResolvedLocalWorkspaceContext;61 context: ResolvedLocalWorkspaceContext;
46 sourceId: string;62 sourceId: string;
@@ -138,58 +154,63 @@ export const buildLocalSourceArtifact = (input: { · 91 unmodified lines
138 };154 };
139};155};
140 156
141export const readLocalSourceArtifact = async (input: {157export const readLocalSourceArtifact = (input: {
142 context: ResolvedLocalWorkspaceContext;158 context: ResolvedLocalWorkspaceContext;
143 sourceId: string;159 sourceId: string;
144}): Promise<LocalSourceArtifact | null> => {160}): Effect.Effect<LocalSourceArtifact | null, Error> =>
145 const path = localSourceArtifactPath(input);161 provideNodeFileSystem(Effect.gen(function* () {
146 162 const fs = yield* FileSystem.FileSystem;
147 try {163 const path = localSourceArtifactPath(input);
148 const content = await fs.readFile(path, "utf8");164 const exists = yield* fs.exists(path).pipe(
149 return decodeLocalSourceArtifact(JSON.parse(content) as unknown);165 Effect.mapError(mapFileSystemError(path, "check source artifact path")),
150 } catch (cause) {166 );
151 if (167 if (!exists) {
152 cause instanceof Error
153 && ("code" in cause)
154 && (cause as NodeJS.ErrnoException).code === "ENOENT"
155 ) {
156 return null;168 return null;
157 }169 }
158 170
159 const message = cause instanceof Error ? cause.message : String(cause);171 const content = yield* fs.readFileString(path, "utf8").pipe(
160 throw new Error(`Invalid local source artifact at ${path}: ${message}`);172 Effect.mapError(mapFileSystemError(path, "read source artifact")),
161 }173 );
162};174 return yield* Effect.try({
175 try: () => decodeLocalSourceArtifact(JSON.parse(content) as unknown),
176 catch: (cause) => {
177 const message = cause instanceof Error ? cause.message : String(cause);
178 return new Error(`Invalid local source artifact at ${path}: ${message}`);
179 },
180 });
181 }));
163 182
164export const writeLocalSourceArtifact = async (input: {183export const writeLocalSourceArtifact = (input: {
165 context: ResolvedLocalWorkspaceContext;184 context: ResolvedLocalWorkspaceContext;
166 sourceId: string;185 sourceId: string;
167 artifact: LocalSourceArtifact;186 artifact: LocalSourceArtifact;
168}): Promise<void> => {187}): Effect.Effect<void, Error> =>
169 const path = localSourceArtifactPath(input);188 provideNodeFileSystem(Effect.gen(function* () {
170 await fs.mkdir(join(input.context.artifactsDirectory, "sources"), {189 const fs = yield* FileSystem.FileSystem;
171 recursive: true,190 const directory = join(input.context.artifactsDirectory, "sources");
172 });191 const path = localSourceArtifactPath(input);
173 await fs.writeFile(path, `${JSON.stringify(input.artifact, null, 2)}\n`, "utf8");192 yield* fs.makeDirectory(directory, { recursive: true }).pipe(
174};193 Effect.mapError(mapFileSystemError(directory, "create source artifact directory")),
194 );
195 yield* fs.writeFileString(path, `${JSON.stringify(input.artifact, null, 2)}\n`).pipe(
196 Effect.mapError(mapFileSystemError(path, "write source artifact")),
197 );
198 }));
175 199
176export const removeLocalSourceArtifact = async (input: {200export const removeLocalSourceArtifact = (input: {
177 context: ResolvedLocalWorkspaceContext;201 context: ResolvedLocalWorkspaceContext;
178 sourceId: string;202 sourceId: string;
179}): Promise<void> => {203}): Effect.Effect<void, Error> =>
180 const path = localSourceArtifactPath(input);204 provideNodeFileSystem(Effect.gen(function* () {
181 205 const fs = yield* FileSystem.FileSystem;
182 try {206 const path = localSourceArtifactPath(input);
183 await fs.unlink(path);207 const exists = yield* fs.exists(path).pipe(
184 } catch (cause) {208 Effect.mapError(mapFileSystemError(path, "check source artifact path")),
185 if (209 );
186 cause instanceof Error210 if (!exists) {
187 && ("code" in cause)
188 && (cause as NodeJS.ErrnoException).code === "ENOENT"
189 ) {
190 return;211 return;
191 }212 }
192 213 yield* fs.remove(path).pipe(
193 throw cause;214 Effect.mapError(mapFileSystemError(path, "remove source artifact")),
194 }215 );
195};216 }));
packages/control-plane/src/runtime/local-workspace-state.ts+51−27
@@ -1,11 +1,13 @@
1import { promises as fs } from "node:fs";
2import { join } from "node:path";1import { join } from "node:path";
2import { FileSystem } from "@effect/platform";
3import { NodeFileSystem } from "@effect/platform-node";
3 4
4import {5import {
5 PolicyIdSchema,6 PolicyIdSchema,
6 SourceStatusSchema,7 SourceStatusSchema,
7 TimestampMsSchema,8 TimestampMsSchema,
8} from "#schema";9} from "#schema";
10import * as Effect from "effect/Effect";
9import * as Schema from "effect/Schema";11import * as Schema from "effect/Schema";
10 12
11import type { ResolvedLocalWorkspaceContext } from "./local-config";13import type { ResolvedLocalWorkspaceContext } from "./local-config";
@@ -44,6 +46,20 @@ export type LocalWorkspaceState = typeof LocalWorkspaceStateSchema.Type; · 32 unmodified lines
44 46
45const decodeLocalWorkspaceState = Schema.decodeUnknownSync(LocalWorkspaceStateSchema);47const decodeLocalWorkspaceState = Schema.decodeUnknownSync(LocalWorkspaceStateSchema);
46 48
49const provideNodeFileSystem = <A, E, R>(
50 effect: Effect.Effect<A, E, R | FileSystem.FileSystem>,
51): Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem>> =>
52 effect.pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect<
53 A,
54 E,
55 Exclude<R, FileSystem.FileSystem>
56 >;
57
58const mapFileSystemError = (path: string, action: string) => (cause: unknown): Error => {
59 const message = cause instanceof Error ? cause.message : String(cause);
60 return new Error(`Failed to ${action} ${path}: ${message}`);
61};
62
47const defaultLocalWorkspaceState = (): LocalWorkspaceState => ({63const defaultLocalWorkspaceState = (): LocalWorkspaceState => ({
48 version: 1,64 version: 1,
49 sources: {},65 sources: {},
@@ -54,36 +70,44 @@ export const localWorkspaceStatePath = ( · 4 unmodified lines
54 context: ResolvedLocalWorkspaceContext,70 context: ResolvedLocalWorkspaceContext,
55): string => join(context.stateDirectory, WORKSPACE_STATE_BASENAME);71): string => join(context.stateDirectory, WORKSPACE_STATE_BASENAME);
56 72
57export const loadLocalWorkspaceState = async (73export const loadLocalWorkspaceState = (
58 context: ResolvedLocalWorkspaceContext,74 context: ResolvedLocalWorkspaceContext,
59): Promise<LocalWorkspaceState> => {75): Effect.Effect<LocalWorkspaceState, Error> =>
60 const path = localWorkspaceStatePath(context);76 provideNodeFileSystem(Effect.gen(function* () {
61 77 const fs = yield* FileSystem.FileSystem;
62 try {78 const path = localWorkspaceStatePath(context);
63 const content = await fs.readFile(path, "utf8");79 const exists = yield* fs.exists(path).pipe(
64 return decodeLocalWorkspaceState(JSON.parse(content) as unknown);80 Effect.mapError(mapFileSystemError(path, "check workspace state path")),
65 } catch (cause) {81 );
66 if (82 if (!exists) {
67 cause instanceof Error
68 && ("code" in cause)
69 && (cause as NodeJS.ErrnoException).code === "ENOENT"
70 ) {
71 return defaultLocalWorkspaceState();83 return defaultLocalWorkspaceState();
72 }84 }
73 85
74 const message = cause instanceof Error ? cause.message : String(cause);86 const content = yield* fs.readFileString(path, "utf8").pipe(
75 throw new Error(`Invalid local workspace state at ${path}: ${message}`);87 Effect.mapError(mapFileSystemError(path, "read workspace state")),
76 }88 );
77};89 return yield* Effect.try({
90 try: () => decodeLocalWorkspaceState(JSON.parse(content) as unknown),
91 catch: (cause) => {
92 const message = cause instanceof Error ? cause.message : String(cause);
93 return new Error(`Invalid local workspace state at ${path}: ${message}`);
94 },
95 });
96 }));
78 97
79export const writeLocalWorkspaceState = async (input: {98export const writeLocalWorkspaceState = (input: {
80 context: ResolvedLocalWorkspaceContext;99 context: ResolvedLocalWorkspaceContext;
81 state: LocalWorkspaceState;100 state: LocalWorkspaceState;
82}): Promise<void> => {101}): Effect.Effect<void, Error> =>
83 await fs.mkdir(input.context.stateDirectory, { recursive: true });102 provideNodeFileSystem(Effect.gen(function* () {
84 await fs.writeFile(103 const fs = yield* FileSystem.FileSystem;
85 localWorkspaceStatePath(input.context),104 yield* fs.makeDirectory(input.context.stateDirectory, { recursive: true }).pipe(
86 `${JSON.stringify(input.state, null, 2)}\n`,105 Effect.mapError(mapFileSystemError(input.context.stateDirectory, "create state directory")),
87 "utf8",106 );
88 );107 yield* fs.writeFileString(
89};108 localWorkspaceStatePath(input.context),
109 `${JSON.stringify(input.state, null, 2)}\n`,
110 ).pipe(
111 Effect.mapError(mapFileSystemError(localWorkspaceStatePath(input.context), "write workspace state")),
112 );
113 }));
packages/control-plane/src/runtime/local-workspace-sync.ts+4−13
@@ -46,11 +46,7 @@ const pruneLocalWorkspaceState = (input: { · 45 unmodified lines
46 loadedConfig: LoadedLocalExecutorConfig;46 loadedConfig: LoadedLocalExecutorConfig;
47}): Effect.Effect<LocalWorkspaceState, Error, never> =>47}): Effect.Effect<LocalWorkspaceState, Error, never> =>
48 Effect.gen(function* () {48 Effect.gen(function* () {
49 const currentState = yield* Effect.tryPromise({49 const currentState = yield* loadLocalWorkspaceState(input.context);
50 try: () => loadLocalWorkspaceState(input.context),
51 catch: (cause) =>
52 cause instanceof Error ? cause : new Error(String(cause)),
53 });
54 50
55 const configuredSourceIds = new Set(51 const configuredSourceIds = new Set(
56 Object.keys(input.loadedConfig.config?.sources ?? {}),52 Object.keys(input.loadedConfig.config?.sources ?? {}),
@@ -77,14 +73,9 @@ const pruneLocalWorkspaceState = (input: { · 20 unmodified lines
77 return currentState;73 return currentState;
78 }74 }
79 75
80 yield* Effect.tryPromise({76 yield* writeLocalWorkspaceState({
81 try: () =>77 context: input.context,
82 writeLocalWorkspaceState({78 state: nextState,
83 context: input.context,
84 state: nextState,
85 }),
86 catch: (cause) =>
87 cause instanceof Error ? cause : new Error(String(cause)),
88 });79 });
89 80
90 return nextState;81 return nextState;
packages/control-plane/src/runtime/policies-operations.ts+14−26
@@ -22,6 +22,7 @@ import { · 21 unmodified lines
22} from "./local-runtime-context";22} from "./local-runtime-context";
23import {23import {
24 loadLocalWorkspaceState,24 loadLocalWorkspaceState,
25 type LocalWorkspaceState,
25 writeLocalWorkspaceState,26 writeLocalWorkspaceState,
26} from "./local-workspace-state";27} from "./local-workspace-state";
27import {28import {
@@ -59,16 +60,8 @@ const localPolicyIdForConfigKey = (input: { · 31 unmodified lines
59export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) =>60export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) =>
60 Effect.gen(function* () {61 Effect.gen(function* () {
61 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);62 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);
62 const loadedConfig = yield* Effect.tryPromise({63 const loadedConfig = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context);
63 try: () => loadLocalExecutorConfig(runtimeLocalWorkspace.context),64 const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
64 catch: (cause) =>
65 cause instanceof Error ? cause : new Error(String(cause)),
66 });
67 const workspaceState = yield* Effect.tryPromise({
68 try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
69 catch: (cause) =>
70 cause instanceof Error ? cause : new Error(String(cause)),
71 });
72 65
73 const configEntries = Object.entries(loadedConfig.config?.policies ?? {});66 const configEntries = Object.entries(loadedConfig.config?.policies ?? {});
74 const policies = configEntries.map(([configKey, configPolicy]) => {67 const policies = configEntries.map(([configKey, configPolicy]) => {
@@ -135,23 +128,18 @@ const writeLocalPolicyFiles = (input: { · 60 unmodified lines
135 operation: OperationErrors;128 operation: OperationErrors;
136 context: Parameters<typeof writeProjectLocalExecutorConfig>[0]["context"];129 context: Parameters<typeof writeProjectLocalExecutorConfig>[0]["context"];
137 projectConfig: LocalExecutorConfig;130 projectConfig: LocalExecutorConfig;
138 workspaceState: Awaited<ReturnType<typeof loadLocalWorkspaceState>>;131 workspaceState: LocalWorkspaceState;
139}) =>132}) =>
140 Effect.tryPromise({133 Effect.all([
141 try: () =>134 writeProjectLocalExecutorConfig({
142 Promise.all([135 context: input.context,
143 writeProjectLocalExecutorConfig({136 config: input.projectConfig,
144 context: input.context,137 }),
145 config: input.projectConfig,138 writeLocalWorkspaceState({
146 }),139 context: input.context,
147 writeLocalWorkspaceState({140 state: input.workspaceState,
148 context: input.context,141 }),
149 state: input.workspaceState,142 ], { discard: true }).pipe(
150 }),
151 ]).then(() => undefined),
152 catch: (cause) =>
153 cause instanceof Error ? cause : new Error(String(cause)),
154 }).pipe(
155 Effect.mapError((cause) =>143 Effect.mapError((cause) =>
156 input.operation.unknownStorage(144 input.operation.unknownStorage(
157 cause,145 cause,
packages/control-plane/src/runtime/source-auth-service.test.ts+4−12
@@ -186,16 +186,8 @@ const makeRuntimeLocalWorkspaceState = async ( · 185 unmodified lines
186 const workspaceRoot = mkdtempSync(186 const workspaceRoot = mkdtempSync(
187 join(tmpdir(), "executor-source-auth-service-"),187 join(tmpdir(), "executor-source-auth-service-"),
188 );188 );
189 const context = yield* Effect.tryPromise({189 const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
190 try: () => resolveLocalWorkspaceContext({ workspaceRoot }),190 const loadedConfig = yield* loadLocalExecutorConfig(context);
191 catch: (cause) =>
192 cause instanceof Error ? cause : new Error(String(cause)),
193 });
194 const loadedConfig = yield* Effect.tryPromise({
195 try: () => loadLocalExecutorConfig(context),
196 catch: (cause) =>
197 cause instanceof Error ? cause : new Error(String(cause)),
198 });
199 191
200 return {192 return {
201 context,193 context,
@@ -496,10 +488,10 @@ describe("source-auth-service", () => { · 294 unmodified lines
496 expect(Option.isNone(oldSecret)).toBe(true);488 expect(Option.isNone(oldSecret)).toBe(true);
497 }489 }
498 490
499 const localArtifact = await readLocalSourceArtifact({491 const localArtifact = await Effect.runPromise(readLocalSourceArtifact({
500 context: runtimeLocalWorkspace.context,492 context: runtimeLocalWorkspace.context,
501 sourceId: connectedSource.id,493 sourceId: connectedSource.id,
502 });494 }));
503 expect(localArtifact).not.toBeNull();495 expect(localArtifact).not.toBeNull();
504 expect(localArtifact?.operations.length).toBeGreaterThan(0);496 expect(localArtifact?.operations.length).toBeGreaterThan(0);
505 expect(497 expect(
packages/control-plane/src/runtime/source-inspection.test.ts+8−16
@@ -59,16 +59,8 @@ const makeRuntimeLocalWorkspaceState = ( · 58 unmodified lines
59 const workspaceRoot = mkdtempSync(59 const workspaceRoot = mkdtempSync(
60 join(tmpdir(), "executor-source-inspection-"),60 join(tmpdir(), "executor-source-inspection-"),
61 );61 );
62 const context = yield* Effect.tryPromise({62 const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
63 try: () => resolveLocalWorkspaceContext({ workspaceRoot }),63 const loadedConfig = yield* loadLocalExecutorConfig(context);
64 catch: (cause) =>
65 cause instanceof Error ? cause : new Error(String(cause)),
66 });
67 const loadedConfig = yield* Effect.tryPromise({
68 try: () => loadLocalExecutorConfig(context),
69 catch: (cause) =>
70 cause instanceof Error ? cause : new Error(String(cause)),
71 });
72 64
73 return {65 return {
74 context,66 context,
@@ -215,15 +207,15 @@ describe("source-inspection", () => { · 140 unmodified lines
215 workspaceId,207 workspaceId,
216 sourceId,208 sourceId,
217 });209 });
218 await writeProjectLocalExecutorConfig({210 await Effect.runPromise(writeProjectLocalExecutorConfig({
219 context: runtimeLocalWorkspace.context,211 context: runtimeLocalWorkspace.context,
220 config: {212 config: {
221 sources: {213 sources: {
222 [sourceId]: configSourceFromSource(source),214 [sourceId]: configSourceFromSource(source),
223 },215 },
224 },216 },
225 });217 }));
226 await writeLocalSourceArtifact({218 await Effect.runPromise(writeLocalSourceArtifact({
227 context: runtimeLocalWorkspace.context,219 context: runtimeLocalWorkspace.context,
228 sourceId,220 sourceId,
229 artifact: buildLocalSourceArtifact({221 artifact: buildLocalSourceArtifact({
@@ -271,8 +263,8 @@ describe("source-inspection", () => { · 41 unmodified lines
271 })],263 })],
272 },264 },
273 }),265 }),
274 });266 }));
275 await writeLocalWorkspaceState({267 await Effect.runPromise(writeLocalWorkspaceState({
276 context: runtimeLocalWorkspace.context,268 context: runtimeLocalWorkspace.context,
277 state: {269 state: {
278 version: 1,270 version: 1,
@@ -287,7 +279,7 @@ describe("source-inspection", () => { · 8 unmodified lines
287 },279 },
288 policies: {},280 policies: {},
289 },281 },
290 });282 }));
291 283
292 const inspection = await Effect.runPromise(284 const inspection = await Effect.runPromise(
293 getSourceInspection({285 getSourceInspection({
packages/control-plane/src/runtime/source-materialization.ts+22−50
@@ -45,11 +45,7 @@ export const syncSourceMaterialization = (input: { · 44 unmodified lines
45 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.source.workspaceId);45 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(input.source.workspaceId);
46 46
47 if (!shouldIndexSource(input.source)) {47 if (!shouldIndexSource(input.source)) {
48 const state = yield* Effect.tryPromise({48 const state = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
49 try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
50 catch: (cause) =>
51 cause instanceof Error ? cause : new Error(String(cause)),
52 });
53 const existingSourceState = state.sources[input.source.id];49 const existingSourceState = state.sources[input.source.id];
54 const nextState: LocalWorkspaceState = {50 const nextState: LocalWorkspaceState = {
55 ...state,51 ...state,
@@ -64,14 +60,9 @@ export const syncSourceMaterialization = (input: { · 8 unmodified lines
64 },60 },
65 },61 },
66 };62 };
67 yield* Effect.tryPromise({63 yield* writeLocalWorkspaceState({
68 try: () =>64 context: runtimeLocalWorkspace.context,
69 writeLocalWorkspaceState({65 state: nextState,
70 context: runtimeLocalWorkspace.context,
71 state: nextState,
72 }),
73 catch: (cause) =>
74 cause instanceof Error ? cause : new Error(String(cause)),
75 });66 });
76 return;67 return;
77 }68 }
@@ -89,25 +80,16 @@ export const syncSourceMaterialization = (input: { · 11 unmodified lines
89 resolveSecretMaterial: input.resolveSecretMaterial,80 resolveSecretMaterial: input.resolveSecretMaterial,
90 }),81 }),
91 });82 });
92 yield* Effect.tryPromise({83 yield* writeLocalSourceArtifact({
93 try: () =>84 context: runtimeLocalWorkspace.context,
94 writeLocalSourceArtifact({85 sourceId: input.source.id,
95 context: runtimeLocalWorkspace.context,86 artifact: buildLocalSourceArtifact({
96 sourceId: input.source.id,87 source: input.source,
97 artifact: buildLocalSourceArtifact({88 materialization,
98 source: input.source,89 }),
99 materialization,
100 }),
101 }),
102 catch: (cause) =>
103 cause instanceof Error ? cause : new Error(String(cause)),
104 });90 });
105 91
106 const state = yield* Effect.tryPromise({92 const state = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
107 try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
108 catch: (cause) =>
109 cause instanceof Error ? cause : new Error(String(cause)),
110 });
111 const existingSourceState = state.sources[input.source.id];93 const existingSourceState = state.sources[input.source.id];
112 const nextState: LocalWorkspaceState = {94 const nextState: LocalWorkspaceState = {
113 ...state,95 ...state,
@@ -122,14 +104,9 @@ export const syncSourceMaterialization = (input: { · 8 unmodified lines
122 },104 },
123 },105 },
124 };106 };
125 yield* Effect.tryPromise({107 yield* writeLocalWorkspaceState({
126 try: () =>108 context: runtimeLocalWorkspace.context,
127 writeLocalWorkspaceState({109 state: nextState,
128 context: runtimeLocalWorkspace.context,
129 state: nextState,
130 }),
131 catch: (cause) =>
132 cause instanceof Error ? cause : new Error(String(cause)),
133 });110 });
134 });111 });
135 112
@@ -148,17 +125,12 @@ export const persistMcpRecipeMaterializationFromManifest = (input: { · 12 unmodified lines
148 manifestEntries: input.manifestEntries,125 manifestEntries: input.manifestEntries,
149 });126 });
150 127
151 yield* Effect.tryPromise({128 yield* writeLocalSourceArtifact({
152 try: () =>129 context: runtimeLocalWorkspace.context,
153 writeLocalSourceArtifact({130 sourceId: input.source.id,
154 context: runtimeLocalWorkspace.context,131 artifact: buildLocalSourceArtifact({
155 sourceId: input.source.id,132 source: input.source,
156 artifact: buildLocalSourceArtifact({133 materialization,
157 source: input.source,134 }),
158 materialization,
159 }),
160 }),
161 catch: (cause) =>
162 cause instanceof Error ? cause : new Error(String(cause)),
163 });135 });
164 });136 });
packages/control-plane/src/runtime/source-recipes-runtime.test.ts+14−21
@@ -66,16 +66,8 @@ const makeRuntimeLocalWorkspaceState = ( · 65 unmodified lines
66 const workspaceRoot = mkdtempSync(66 const workspaceRoot = mkdtempSync(
67 join(tmpdir(), "executor-source-recipes-runtime-"),67 join(tmpdir(), "executor-source-recipes-runtime-"),
68 );68 );
69 const context = yield* Effect.tryPromise({69 const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
70 try: () => resolveLocalWorkspaceContext({ workspaceRoot }),70 const loadedConfig = yield* loadLocalExecutorConfig(context);
71 catch: (cause) =>
72 cause instanceof Error ? cause : new Error(String(cause)),
73 });
74 const loadedConfig = yield* Effect.tryPromise({
75 try: () => loadLocalExecutorConfig(context),
76 catch: (cause) =>
77 cause instanceof Error ? cause : new Error(String(cause)),
78 });
79 71
80 return {72 return {
81 context,73 context,
@@ -362,18 +354,18 @@ const seedLocalWorkspaceSources = async (input: { · 280 unmodified lines
362 };354 };
363 }>;355 }>;
364}) => {356}) => {
365 await writeProjectLocalExecutorConfig({357 await Effect.runPromise(writeProjectLocalExecutorConfig({
366 context: input.runtimeLocalWorkspace.context,358 context: input.runtimeLocalWorkspace.context,
367 config: {359 config: {
368 sources: Object.fromEntries(360 sources: Object.fromEntries(
369 input.fixtures.map(({ source }) => [source.id, configSourceFromSource(source)]),361 input.fixtures.map(({ source }) => [source.id, configSourceFromSource(source)]),
370 ),362 ),
371 },363 },
372 });364 }));
373 365
374 await Promise.all(366 await Promise.all(
375 input.fixtures.map(({ source, materialization }) =>367 input.fixtures.map(({ source, materialization }) =>
376 writeLocalSourceArtifact({368 Effect.runPromise(writeLocalSourceArtifact({
377 context: input.runtimeLocalWorkspace.context,369 context: input.runtimeLocalWorkspace.context,
378 sourceId: source.id,370 sourceId: source.id,
379 artifact: buildLocalSourceArtifact({371 artifact: buildLocalSourceArtifact({
@@ -381,9 +373,10 @@ const seedLocalWorkspaceSources = async (input: { · 1 unmodified line
381 materialization,373 materialization,
382 }),374 }),
383 })),375 })),
376 ),
384 );377 );
385 378
386 await writeLocalWorkspaceState({379 await Effect.runPromise(writeLocalWorkspaceState({
387 context: input.runtimeLocalWorkspace.context,380 context: input.runtimeLocalWorkspace.context,
388 state: {381 state: {
389 version: 1,382 version: 1,
@@ -401,7 +394,7 @@ const seedLocalWorkspaceSources = async (input: { · 11 unmodified lines
401 ),394 ),
402 policies: {},395 policies: {},
403 },396 },
404 });397 }));
405};398};
406 399
407describe("source-recipes-runtime", () => {400describe("source-recipes-runtime", () => {
@@ -774,7 +767,7 @@ describe("source-recipes-runtime", () => { · 366 unmodified lines
774 const runtimeLocalWorkspace =767 const runtimeLocalWorkspace =
775 await makeRuntimeLocalWorkspaceState(workspaceId);768 await makeRuntimeLocalWorkspaceState(workspaceId);
776 769
777 await writeProjectLocalExecutorConfig({770 await Effect.runPromise(writeProjectLocalExecutorConfig({
778 context: runtimeLocalWorkspace.context,771 context: runtimeLocalWorkspace.context,
779 config: {772 config: {
780 sources: {773 sources: {
@@ -787,8 +780,8 @@ describe("source-recipes-runtime", () => { · 6 unmodified lines
787 ),780 ),
788 },781 },
789 },782 },
790 });783 }));
791 await writeLocalWorkspaceState({784 await Effect.runPromise(writeLocalWorkspaceState({
792 context: runtimeLocalWorkspace.context,785 context: runtimeLocalWorkspace.context,
793 state: {786 state: {
794 version: 1,787 version: 1,
@@ -803,7 +796,7 @@ describe("source-recipes-runtime", () => { · 8 unmodified lines
803 },796 },
804 policies: {},797 policies: {},
805 },798 },
806 });799 }));
807 800
808 await expect(801 await expect(
809 Effect.runPromise(802 Effect.runPromise(
@@ -834,7 +827,7 @@ describe("source-recipes-runtime", () => { · 24 unmodified lines
834 operations: [],827 operations: [],
835 },828 },
836 });829 });
837 await writeLocalSourceArtifact({830 await Effect.runPromise(writeLocalSourceArtifact({
838 context: runtimeLocalWorkspace.context,831 context: runtimeLocalWorkspace.context,
839 sourceId,832 sourceId,
840 artifact: {833 artifact: {
@@ -844,7 +837,7 @@ describe("source-recipes-runtime", () => { · 3 unmodified lines
844 manifestJson: "{bad-json",837 manifestJson: "{bad-json",
845 },838 },
846 },839 },
847 });840 }));
848 841
849 await expect(842 await expect(
850 Effect.runPromise(843 Effect.runPromise(
packages/control-plane/src/runtime/source-recipes-runtime.ts+6−16
@@ -167,14 +167,9 @@ export const loadWorkspaceSourceRecipes = (input: { · 166 unmodified lines
167 });167 });
168 const localRecipes = yield* Effect.forEach(sources, (source) =>168 const localRecipes = yield* Effect.forEach(sources, (source) =>
169 Effect.gen(function* () {169 Effect.gen(function* () {
170 const artifact = yield* Effect.tryPromise({170 const artifact = yield* readLocalSourceArtifact({
171 try: () =>171 context: runtimeLocalWorkspace.context,
172 readLocalSourceArtifact({172 sourceId: source.id,
173 context: runtimeLocalWorkspace.context,
174 sourceId: source.id,
175 }),
176 catch: (cause) =>
177 cause instanceof Error ? cause : new Error(String(cause)),
178 });173 });
179 if (artifact === null) {174 if (artifact === null) {
180 return null;175 return null;
@@ -231,14 +226,9 @@ export const loadSourceWithRecipe = (input: { · 50 unmodified lines
231 sourceId: input.sourceId,226 sourceId: input.sourceId,
232 actorAccountId: input.actorAccountId,227 actorAccountId: input.actorAccountId,
233 });228 });
234 const artifact = yield* Effect.tryPromise({229 const artifact = yield* readLocalSourceArtifact({
235 try: () =>230 context: runtimeLocalWorkspace.context,
236 readLocalSourceArtifact({231 sourceId: source.id,
237 context: runtimeLocalWorkspace.context,
238 sourceId: source.id,
239 }),
240 catch: (cause) =>
241 cause instanceof Error ? cause : new Error(String(cause)),
242 });232 });
243 if (artifact === null) {233 if (artifact === null) {
244 return yield* Effect.fail(234 return yield* Effect.fail(
packages/control-plane/src/runtime/source-store.test.ts+4−20
@@ -56,16 +56,8 @@ const makeRuntimeLocalWorkspace = (input: { · 55 unmodified lines
56}) =>56}) =>
57 Effect.gen(function* () {57 Effect.gen(function* () {
58 const workspaceRoot = mkdtempSync(join(tmpdir(), "executor-source-store-"));58 const workspaceRoot = mkdtempSync(join(tmpdir(), "executor-source-store-"));
59 const context = yield* Effect.tryPromise({59 const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
60 try: () => resolveLocalWorkspaceContext({ workspaceRoot }),60 const loadedConfig = yield* loadLocalExecutorConfig(context);
61 catch: (cause) =>
62 cause instanceof Error ? cause : new Error(String(cause)),
63 });
64 const loadedConfig = yield* Effect.tryPromise({
65 try: () => loadLocalExecutorConfig(context),
66 catch: (cause) =>
67 cause instanceof Error ? cause : new Error(String(cause)),
68 });
69 61
70 return {62 return {
71 context,63 context,
@@ -245,17 +237,9 @@ describe("source-store", () => { · 173 unmodified lines
245 expect(Option.isNone(yield* persistence.rows.secretMaterials.getById(secondTokenId))).toBe(true);237 expect(Option.isNone(yield* persistence.rows.secretMaterials.getById(secondTokenId))).toBe(true);
246 expect(yield* persistence.rows.authArtifacts.listByWorkspaceId(workspaceId)).toHaveLength(0);238 expect(yield* persistence.rows.authArtifacts.listByWorkspaceId(workspaceId)).toHaveLength(0);
247 expect(yield* persistence.rows.sourceAuthSessions.listByWorkspaceId(workspaceId)).toHaveLength(0);239 expect(yield* persistence.rows.sourceAuthSessions.listByWorkspaceId(workspaceId)).toHaveLength(0);
248 const config = yield* Effect.tryPromise({240 const config = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context);
249 try: () => loadLocalExecutorConfig(runtimeLocalWorkspace.context),
250 catch: (cause) =>
251 cause instanceof Error ? cause : new Error(String(cause)),
252 });
253 expect(config.config?.sources?.[sourceId]).toBeUndefined();241 expect(config.config?.sources?.[sourceId]).toBeUndefined();
254 const workspaceState = yield* Effect.tryPromise({242 const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
255 try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
256 catch: (cause) =>
257 cause instanceof Error ? cause : new Error(String(cause)),
258 });
259 expect(workspaceState.sources[sourceId]).toBeUndefined();243 expect(workspaceState.sources[sourceId]).toBeUndefined();
260 }),244 }),
261 );245 );
packages/control-plane/src/runtime/source-store.ts+26−63
@@ -234,16 +234,8 @@ const configAuthFromSource = (input: { · 233 unmodified lines
234const resolveRuntimeLocalWorkspace = (workspaceId: WorkspaceId) =>234const resolveRuntimeLocalWorkspace = (workspaceId: WorkspaceId) =>
235 Effect.gen(function* () {235 Effect.gen(function* () {
236 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);236 const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);
237 const loadedConfig = yield* Effect.tryPromise({237 const loadedConfig = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context);
238 try: () => loadLocalExecutorConfig(runtimeLocalWorkspace.context),238 const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
239 catch: (cause) =>
240 cause instanceof Error ? cause : new Error(String(cause)),
241 });
242 const workspaceState = yield* Effect.tryPromise({
243 try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
244 catch: (cause) =>
245 cause instanceof Error ? cause : new Error(String(cause)),
246 });
247 239
248 return {240 return {
249 context: runtimeLocalWorkspace.context,241 context: runtimeLocalWorkspace.context,
@@ -297,13 +289,9 @@ const buildLocalSourceRecord = (input: { · 47 unmodified lines
297 updatedAt: existingState?.updatedAt ?? Date.now(),289 updatedAt: existingState?.updatedAt ?? Date.now(),
298 });290 });
299 291
300 const artifact = yield* Effect.tryPromise({292 const artifact = yield* readLocalSourceArtifact({
301 try: () => readLocalSourceArtifact({293 context: input.context,
302 context: input.context,294 sourceId: input.sourceId,
303 sourceId: input.sourceId,
304 }),
305 catch: (cause) =>
306 cause instanceof Error ? cause : new Error(String(cause)),
307 }).pipe(295 }).pipe(
308 Effect.catchAll(() => Effect.succeed(null)),296 Effect.catchAll(() => Effect.succeed(null)),
309 );297 );
@@ -524,17 +512,12 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 214 unmodified lines
524 ...(projectConfig.sources ?? {}),512 ...(projectConfig.sources ?? {}),
525 };513 };
526 delete sources[input.sourceId];514 delete sources[input.sourceId];
527 yield* Effect.tryPromise({515 yield* writeProjectLocalExecutorConfig({
528 try: () =>516 context: localWorkspace.context,
529 writeProjectLocalExecutorConfig({517 config: {
530 context: localWorkspace.context,518 ...projectConfig,
531 config: {519 sources,
532 ...projectConfig,520 },
533 sources,
534 },
535 }),
536 catch: (cause) =>
537 cause instanceof Error ? cause : new Error(String(cause)),
538 });521 });
539 522
540 const {523 const {
@@ -545,23 +528,13 @@ export const removeSourceById = (rows: SqlControlPlaneRows, input: { · 4 unmodified lines
545 ...localWorkspace.workspaceState,528 ...localWorkspace.workspaceState,
546 sources: remainingSources,529 sources: remainingSources,
547 };530 };
548 yield* Effect.tryPromise({531 yield* writeLocalWorkspaceState({
549 try: () =>532 context: localWorkspace.context,
550 writeLocalWorkspaceState({533 state: workspaceState,
551 context: localWorkspace.context,
552 state: workspaceState,
553 }),
554 catch: (cause) =>
555 cause instanceof Error ? cause : new Error(String(cause)),
556 });534 });
557 yield* Effect.tryPromise({535 yield* removeLocalSourceArtifact({
558 try: () =>536 context: localWorkspace.context,
559 removeLocalSourceArtifact({537 sourceId: input.sourceId,
560 context: localWorkspace.context,
561 sourceId: input.sourceId,
562 }),
563 catch: (cause) =>
564 cause instanceof Error ? cause : new Error(String(cause)),
565 });538 });
566 539
567 yield* rows.sourceAuthSessions.removeByWorkspaceAndSourceId(540 yield* rows.sourceAuthSessions.removeByWorkspaceAndSourceId(
@@ -621,17 +594,12 @@ export const persistSource = ( · 53 unmodified lines
621 existingConfigAuth: existingConfigSource?.connection.auth,594 existingConfigAuth: existingConfigSource?.connection.auth,
622 config: localWorkspace.loadedConfig.config,595 config: localWorkspace.loadedConfig.config,
623 });596 });
624 yield* Effect.tryPromise({597 yield* writeProjectLocalExecutorConfig({
625 try: () =>598 context: localWorkspace.context,
626 writeProjectLocalExecutorConfig({599 config: {
627 context: localWorkspace.context,600 ...projectConfig,
628 config: {601 sources,
629 ...projectConfig,602 },
630 sources,
631 },
632 }),
633 catch: (cause) =>
634 cause instanceof Error ? cause : new Error(String(cause)),
635 });603 });
636 604
637 const { runtimeAuthArtifact, importAuthArtifact } = splitSourceForStorage({605 const { runtimeAuthArtifact, importAuthArtifact } = splitSourceForStorage({
@@ -715,14 +683,9 @@ export const persistSource = ( · 77 unmodified lines
715 },683 },
716 },684 },
717 };685 };
718 yield* Effect.tryPromise({686 yield* writeLocalWorkspaceState({
719 try: () =>687 context: localWorkspace.context,
720 writeLocalWorkspaceState({688 state: workspaceState,
721 context: localWorkspace.context,
722 state: workspaceState,
723 }),
724 catch: (cause) =>
725 cause instanceof Error ? cause : new Error(String(cause)),
726 });689 });
727 690
728 return yield* loadSourceById(rows, {691 return yield* loadSourceById(rows, {
packages/control-plane/src/runtime/workspace-execution-environment.test.ts+108−169
@@ -631,80 +631,65 @@ const persistConnectedEchoTool = (input: { · 630 unmodified lines
631 updatedAt: now,631 updatedAt: now,
632 };632 };
633 633
634 yield* Effect.tryPromise({634 yield* writeProjectLocalExecutorConfig({
635 try: () =>635 context: input.runtimeLocalWorkspace.context,
636 writeProjectLocalExecutorConfig({636 config: {
637 context: input.runtimeLocalWorkspace.context,637 sources: {
638 config: {638 [input.sourceId]: {
639 sources: {639 kind: "mcp",
640 [input.sourceId]: {640 name: source.name,
641 kind: "mcp",641 ...(source.namespace ? { namespace: source.namespace } : {}),
642 name: source.name,642 connection: {
643 ...(source.namespace ? { namespace: source.namespace } : {}),643 endpoint: source.endpoint,
644 connection: {644 },
645 endpoint: source.endpoint,645 binding: {
646 },646 transport: "streamable-http",
647 binding: {
648 transport: "streamable-http",
649 },
650 },
651 },647 },
652 },648 },
653 }),649 },
654 catch: (cause) =>650 },
655 cause instanceof Error ? cause : new Error(String(cause)),
656 });651 });
657 yield* Effect.tryPromise({652 yield* writeLocalSourceArtifact({
658 try: () =>653 context: input.runtimeLocalWorkspace.context,
659 writeLocalSourceArtifact({654 sourceId: source.id,
660 context: input.runtimeLocalWorkspace.context,655 artifact: buildLocalSourceArtifact({
661 sourceId: source.id,656 source,
662 artifact: buildLocalSourceArtifact({657 materialization: materializationFromMcpManifestEntries({
663 source,658 recipeRevisionId: "src_recipe_rev_materialization" as never,
664 materialization: materializationFromMcpManifestEntries({659 endpoint: source.endpoint,
665 recipeRevisionId: "src_recipe_rev_materialization" as never,660 manifestEntries: [
666 endpoint: source.endpoint,661 {
667 manifestEntries: [662 toolId: "echo",
668 {663 toolName: "echo",
669 toolId: "echo",664 description: "Echo the provided string",
670 toolName: "echo",665 inputSchemaJson: JSON.stringify({
671 description: "Echo the provided string",666 type: "object",
672 inputSchemaJson: JSON.stringify({667 properties: {
673 type: "object",668 value: { type: "string" },
674 properties: {
675 value: { type: "string" },
676 },
677 required: ["value"],
678 additionalProperties: false,
679 }),
680 },669 },
681 ],670 required: ["value"],
682 }),671 additionalProperties: false,
683 }),672 }),
673 },
674 ],
684 }),675 }),
685 catch: (cause) =>676 }),
686 cause instanceof Error ? cause : new Error(String(cause)),
687 });677 });
688 yield* Effect.tryPromise({678 yield* writeLocalWorkspaceState({
689 try: () =>679 context: input.runtimeLocalWorkspace.context,
690 writeLocalWorkspaceState({680 state: {
691 context: input.runtimeLocalWorkspace.context,681 version: 1,
692 state: {682 sources: {
693 version: 1,683 [input.sourceId]: {
694 sources: {684 status: "connected",
695 [input.sourceId]: {685 lastError: null,
696 status: "connected",686 sourceHash: null,
697 lastError: null,687 createdAt: now,
698 sourceHash: null,688 updatedAt: now,
699 createdAt: now,
700 updatedAt: now,
701 },
702 },
703 policies: {},
704 },689 },
705 }),690 },
706 catch: (cause) =>691 policies: {},
707 cause instanceof Error ? cause : new Error(String(cause)),692 },
708 });693 });
709 }) as Effect.Effect<void, Error, never>;694 }) as Effect.Effect<void, Error, never>;
710 695
@@ -931,38 +916,31 @@ const persistConnectedGithubOpenApiSource = (input: { · 220 unmodified lines
931 ),916 ),
932 );917 );
933 const definitions = compileOpenApiToolDefinitions(manifest);918 const definitions = compileOpenApiToolDefinitions(manifest);
934 yield* Effect.tryPromise({919 yield* writeProjectLocalExecutorConfig({
935 try: () =>920 context: input.runtimeLocalWorkspace.context,
936 writeProjectLocalExecutorConfig({921 config: {
937 context: input.runtimeLocalWorkspace.context,922 sources: {
938 config: {923 [input.sourceId]: {
939 sources: {924 kind: "openapi",
940 [input.sourceId]: {925 name: source.name,
941 kind: "openapi",926 ...(source.namespace ? { namespace: source.namespace } : {}),
942 name: source.name,927 connection: {
943 ...(source.namespace ? { namespace: source.namespace } : {}),928 endpoint: source.endpoint,
944 connection: {929 ...(configAuth !== undefined ? { auth: configAuth } : {}),
945 endpoint: source.endpoint,930 },
946 ...(configAuth !== undefined ? { auth: configAuth } : {}),931 binding: {
947 },932 specUrl: String(source.binding.specUrl),
948 binding: {933 defaultHeaders: null,
949 specUrl: String(source.binding.specUrl),
950 defaultHeaders: null,
951 },
952 },
953 },934 },
954 },935 },
955 }),936 },
956 catch: (cause) =>937 },
957 cause instanceof Error ? cause : new Error(String(cause)),
958 });938 });
959 const refEntries = Object.entries(manifest.refHintTable ?? {});939 const refEntries = Object.entries(manifest.refHintTable ?? {});
960 yield* Effect.tryPromise({940 yield* writeLocalSourceArtifact({
961 try: () =>941 context: input.runtimeLocalWorkspace.context,
962 writeLocalSourceArtifact({942 sourceId: source.id,
963 context: input.runtimeLocalWorkspace.context,943 artifact: buildLocalSourceArtifact({
964 sourceId: source.id,
965 artifact: buildLocalSourceArtifact({
966 source: {944 source: {
967 ...source,945 ...source,
968 sourceHash: manifest.sourceHash,946 sourceHash: manifest.sourceHash,
@@ -1055,30 +1033,22 @@ const persistConnectedGithubOpenApiSource = (input: { · 86 unmodified lines
1055 }),1033 }),
1056 },1034 },
1057 }),1035 }),
1058 }),
1059 catch: (cause) =>
1060 cause instanceof Error ? cause : new Error(String(cause)),
1061 });1036 });
1062 yield* Effect.tryPromise({1037 yield* writeLocalWorkspaceState({
1063 try: () =>1038 context: input.runtimeLocalWorkspace.context,
1064 writeLocalWorkspaceState({1039 state: {
1065 context: input.runtimeLocalWorkspace.context,1040 version: 1,
1066 state: {1041 sources: {
1067 version: 1,1042 [input.sourceId]: {
1068 sources: {1043 status: "connected",
1069 [input.sourceId]: {1044 lastError: null,
1070 status: "connected",1045 sourceHash: manifest.sourceHash,
1071 lastError: null,1046 createdAt: now,
1072 sourceHash: manifest.sourceHash,1047 updatedAt: now,
1073 createdAt: now,
1074 updatedAt: now,
1075 },
1076 },
1077 policies: {},
1078 },1048 },
1079 }),1049 },
1080 catch: (cause) =>1050 policies: {},
1081 cause instanceof Error ? cause : new Error(String(cause)),1051 },
1082 });1052 });
1083 1053
1084 return;1054 return;
@@ -1087,16 +1057,8 @@ const persistConnectedGithubOpenApiSource = (input: { · 2 unmodified lines
1087const makeRuntimeLocalWorkspaceState = (workspaceId: WorkspaceId): Effect.Effect<RuntimeLocalWorkspaceState, Error, never> =>1057const makeRuntimeLocalWorkspaceState = (workspaceId: WorkspaceId): Effect.Effect<RuntimeLocalWorkspaceState, Error, never> =>
1088 Effect.gen(function* () {1058 Effect.gen(function* () {
1089 const workspaceRoot = mkdtempSync(join(tmpdir(), "executor-workspace-env-"));1059 const workspaceRoot = mkdtempSync(join(tmpdir(), "executor-workspace-env-"));
1090 const context = yield* Effect.tryPromise({1060 const context = yield* resolveLocalWorkspaceContext({ workspaceRoot });
1091 try: () => resolveLocalWorkspaceContext({ workspaceRoot }),1061 const loadedConfig = yield* loadLocalExecutorConfig(context);
1092 catch: (cause) =>
1093 cause instanceof Error ? cause : new Error(String(cause)),
1094 });
1095 const loadedConfig = yield* Effect.tryPromise({
1096 try: () => loadLocalExecutorConfig(context),
1097 catch: (cause) =>
1098 cause instanceof Error ? cause : new Error(String(cause)),
1099 });
1100 return {1062 return {
1101 context,1063 context,
1102 installation: {1064 installation: {
@@ -1377,34 +1339,21 @@ describe("workspace-execution-environment", () => { · 274 unmodified lines
1377 encodeURIComponent("exec_add_openapi:executor.sources.add:"),1339 encodeURIComponent("exec_add_openapi:executor.sources.add:"),
1378 );1340 );
1379 1341
1380 const loadedConfig = yield* Effect.tryPromise({1342 const loadedConfig = yield* loadLocalExecutorConfig(runtimeLocalWorkspace.context);
1381 try: () => loadLocalExecutorConfig(runtimeLocalWorkspace.context),
1382 catch: (cause) =>
1383 cause instanceof Error ? cause : new Error(String(cause)),
1384 });
1385 expect(loadedConfig.config?.sources?.[added.id]).toMatchObject({1343 expect(loadedConfig.config?.sources?.[added.id]).toMatchObject({
1386 kind: "openapi",1344 kind: "openapi",
1387 connection: {1345 connection: {
1388 endpoint: `${specServer.baseUrl}/`,1346 endpoint: `${specServer.baseUrl}/`,
1389 },1347 },
1390 });1348 });
1391 const artifact = yield* Effect.tryPromise({1349 const artifact = yield* readLocalSourceArtifact({
1392 try: () =>1350 context: runtimeLocalWorkspace.context,
1393 readLocalSourceArtifact({1351 sourceId: added.id,
1394 context: runtimeLocalWorkspace.context,
1395 sourceId: added.id,
1396 }),
1397 catch: (cause) =>
1398 cause instanceof Error ? cause : new Error(String(cause)),
1399 });1352 });
1400 expect(artifact?.documents[0]?.contentText).toContain(1353 expect(artifact?.documents[0]?.contentText).toContain(
1401 '"operationId":"repos.getRepo"',1354 '"operationId":"repos.getRepo"',
1402 );1355 );
1403 const workspaceState = yield* Effect.tryPromise({1356 const workspaceState = yield* loadLocalWorkspaceState(runtimeLocalWorkspace.context);
1404 try: () => loadLocalWorkspaceState(runtimeLocalWorkspace.context),
1405 catch: (cause) =>
1406 cause instanceof Error ? cause : new Error(String(cause)),
1407 });
1408 expect(workspaceState.sources[added.id]?.status).toBe("connected");1357 expect(workspaceState.sources[added.id]?.status).toBe("connected");
1409 1358
1410 const freshEnvironment = yield* resolveEnvironment({1359 const freshEnvironment = yield* resolveEnvironment({
@@ -1511,14 +1460,9 @@ describe("workspace-execution-environment", () => { · 100 unmodified lines
1511 expect(added.auth.kind).toBe("bearer");1460 expect(added.auth.kind).toBe("bearer");
1512 expect(added.auth.token?.providerId).toBe("postgres");1461 expect(added.auth.token?.providerId).toBe("postgres");
1513 1462
1514 const artifact = yield* Effect.tryPromise({1463 const artifact = yield* readLocalSourceArtifact({
1515 try: () =>1464 context: runtimeLocalWorkspace.context,
1516 readLocalSourceArtifact({1465 sourceId: added.id,
1517 context: runtimeLocalWorkspace.context,
1518 sourceId: added.id,
1519 }),
1520 catch: (cause) =>
1521 cause instanceof Error ? cause : new Error(String(cause)),
1522 });1466 });
1523 expect(artifact?.documents[0]?.contentText).toContain('"__schema"');1467 expect(artifact?.documents[0]?.contentText).toContain('"__schema"');
1524 expect(artifact?.schemaBundles.length).toBe(1);1468 expect(artifact?.schemaBundles.length).toBe(1);
@@ -1526,22 +1470,17 @@ describe("workspace-execution-environment", () => { · 1 unmodified line
1526 throw new Error(`Missing local source artifact for ${added.id}`);1470 throw new Error(`Missing local source artifact for ${added.id}`);
1527 }1471 }
1528 1472
1529 yield* Effect.tryPromise({1473 yield* writeLocalSourceArtifact({
1530 try: () =>1474 context: runtimeLocalWorkspace.context,
1531 writeLocalSourceArtifact({1475 sourceId: added.id,
1532 context: runtimeLocalWorkspace.context,1476 artifact: {
1533 sourceId: added.id,1477 ...artifact,
1534 artifact: {1478 revision: {
1535 ...artifact,1479 ...artifact.revision,
1536 revision: {1480 manifestJson: null,
1537 ...artifact.revision,1481 updatedAt: Date.now(),
1538 manifestJson: null,1482 },
1539 updatedAt: Date.now(),1483 },
1540 },
1541 },
1542 }),
1543 catch: (cause) =>
1544 cause instanceof Error ? cause : new Error(String(cause)),
1545 });1484 });
1546 1485
1547 const freshEnvironment = yield* resolveEnvironment({1486 const freshEnvironment = yield* resolveEnvironment({