History

Clean up apps plugin lint

Rhys Sullivan committed 4 months ago

Commit b71e24a1 parent ee980da8 files · +51 −59Browse files

Unified · Split

8 files
  1. packages/plugins/apps/CHANGELOG.md +1 −0
  2. packages/plugins/apps/src/executor/app-tool-executor.test.ts +12 −18
  3. packages/plugins/apps/src/executor/app-tool-executor.ts +1 −0
  4. packages/plugins/apps/src/pipeline/publish.test.ts +7 −7
  5. packages/plugins/apps/src/pipeline/publish.ts +3 −1
  6. packages/plugins/apps/src/plugin/apps-plugin.ts +9 −12
  7. packages/plugins/apps/src/plugin/bindings.test.ts +10 −15
  8. packages/plugins/apps/src/plugin/bindings.ts +8 −6
packages/plugins/apps/CHANGELOG.md+1−0
@@ -0,0 +1 @@
1+# @executor-js/plugin-apps
packages/plugins/apps/src/executor/app-tool-executor.test.ts+12−18
@@ -51,16 +51,13 @@ describe("in-process app tool executor", () => { · 50 unmodified lines
5151 },
5252 });
5353 `);
54− const exit = yield* makeInProcessAppToolExecutor()
54+ const error = yield* makeInProcessAppToolExecutor()
5555 .collect(bundled.code, {
5656 fileSlug: "sync-deals",
5757 sourcePath: "tools/sync-deals.ts",
5858 })
59− .pipe(Effect.exit);
60− expect(exit._tag).toBe("Failure");
61− if (exit._tag === "Failure") {
62− expect(String(exit.cause)).toContain("nested.crm");
63− }
59+ .pipe(Effect.flip);
60+ expect(error).toMatchObject({ message: expect.stringContaining("nested.crm") });
6461 }),
6562 );
6663
@@ -106,14 +103,13 @@ describe("in-process app tool executor", () => { · 39 unmodified lines
106103 }),
107104 };
108105 `);
109− const exit = yield* makeInProcessAppToolExecutor()
106+ const error = yield* makeInProcessAppToolExecutor()
110107 .collect(bundled.code, {
111108 fileSlug: "sync-deals",
112109 sourcePath: "tools/sync-deals.ts",
113110 })
114− .pipe(Effect.exit);
115− expect(exit._tag).toBe("Failure");
116− if (exit._tag === "Failure") expect(String(exit.cause)).toContain("collides");
111+ .pipe(Effect.flip);
112+ expect(error).toMatchObject({ message: expect.stringContaining("collides") });
117113 }),
118114 );
119115
@@ -161,7 +157,7 @@ describe("in-process app tool executor", () => { · 41 unmodified lines
161157 handler(input) { return input; },
162158 });
163159 `);
164− const exit = yield* makeInProcessAppToolExecutor()
160+ const error = yield* makeInProcessAppToolExecutor()
165161 .invoke(
166162 bundled.code,
167163 { toolName: "sync-deals" },
@@ -171,9 +167,8 @@ describe("in-process app tool executor", () => { · 3 unmodified lines
171167 timeoutMs: 1000,
172168 },
173169 )
174− .pipe(Effect.exit);
175− expect(exit._tag).toBe("Failure");
176− if (exit._tag === "Failure") expect(String(exit.cause)).toContain("validation failed");
170+ .pipe(Effect.flip);
171+ expect(error).toMatchObject({ message: expect.stringContaining("validation failed") });
177172 }),
178173 );
179174
@@ -191,7 +186,7 @@ describe("in-process app tool executor", () => { · 11 unmodified lines
191186 },
192187 });
193188 `);
194− const exit = yield* makeInProcessAppToolExecutor()
189+ const error = yield* makeInProcessAppToolExecutor()
195190 .invoke(
196191 bundled.code,
197192 { toolName: "sync-deals" },
@@ -201,9 +196,8 @@ describe("in-process app tool executor", () => { · 3 unmodified lines
201196 timeoutMs: 1,
202197 },
203198 )
204− .pipe(Effect.exit);
205− expect(exit._tag).toBe("Failure");
206− if (exit._tag === "Failure") expect(String(exit.cause)).toContain("timed out");
199+ .pipe(Effect.flip);
200+ expect(error).toMatchObject({ message: expect.stringContaining("timed out") });
207201 }),
208202 );
209203 });
packages/plugins/apps/src/executor/app-tool-executor.ts+1−0
@@ -1,3 +1,4 @@
1+/* oxlint-disable executor/no-try-catch-or-throw, executor/no-double-cast, executor/no-promise-reject, executor/no-instanceof-tagged-error -- boundary: in-process dynamic-import backing converts JavaScript exceptions into typed AppExecutorError */
12 import { Buffer } from "node:buffer";
23 import { Data, Effect } from "effect";
34 import { z } from "zod";
packages/plugins/apps/src/pipeline/publish.test.ts+7−7
@@ -4,7 +4,7 @@ import { Effect } from "effect"; · 3 unmodified lines
44 import type { AppsStore } from "../plugin/store";
55 import { makeInProcessAppToolExecutor } from "../executor/app-tool-executor";
66 import type { AppDescriptor } from "./descriptor";
7−import { discover, PublishError } from "./discover";
7+import { discover } from "./discover";
88 import { publish } from "./publish";
99
1010 const encoder = new TextEncoder();
@@ -75,9 +75,10 @@ describe("discover", () => { · 64 unmodified lines
7575 ["skills/guide.md", ""],
7676 ]),
7777 );
78− expect(result).toBeInstanceOf(PublishError);
79− if (!(result instanceof PublishError)) return;
80− expect(result.diagnostics[0]?.path).toBe("tools/readme.md");
78+ expect(result).toMatchObject({
79+ _tag: "PublishError",
80+ diagnostics: [{ path: "tools/readme.md" }],
81+ });
8182 });
8283 });
8384
@@ -117,7 +118,7 @@ describe("publish", () => { · 33 unmodified lines
117118 it.effect("rejects nondeterministic factories", () =>
118119 Effect.gen(function* () {
119120 const store = makeMemoryStore();
120− const exit = yield* publish(
121+ yield* publish(
121122 { store, executor: makeInProcessAppToolExecutor() },
122123 {
123124 app: "crm",
@@ -139,8 +140,7 @@ describe("publish", () => { · 15 unmodified lines
139140 ),
140141 ],
141142 },
142− ).pipe(Effect.exit);
143− expect(exit._tag).toBe("Failure");
143+ ).pipe(Effect.flip);
144144 expect(store.rows.size).toBe(0);
145145 }),
146146 );
packages/plugins/apps/src/pipeline/publish.ts+3−1
@@ -66,11 +66,13 @@ const toPublishError = ( · 65 unmodified lines
6666 ): PublishError =>
6767 new PublishError({
6868 stage,
69+ // oxlint-disable-next-line executor/no-unknown-error-message -- typed AppExecutorError conversion keeps its stable diagnostic message
6970 message: cause.message,
7071 diagnostics:
7172 cause.diagnostics && cause.diagnostics.length > 0
7273 ? cause.diagnostics
73− : [{ path, message: cause.message }],
74+ : // oxlint-disable-next-line executor/no-unknown-error-message -- typed AppExecutorError conversion keeps its stable diagnostic message
75+ [{ path, message: cause.message }],
7476 });
7577
7678 const toolDescriptor = (input: {
packages/plugins/apps/src/plugin/apps-plugin.ts+9−12
@@ -2,15 +2,11 @@ import { Data, Effect } from "effect"; · 1 unmodified line
22 import { ToolName, definePlugin, type PluginCtx, type ToolDef } from "@executor-js/sdk";
33
44 import { makeInProcessAppToolExecutor, type AppToolExecutor } from "../executor/app-tool-executor";
5−import { publish, type PublishInput } from "../pipeline/publish";
5+import { publish } from "../pipeline/publish";
66 import { buildBridge, resolveIntegrationBindings } from "./bindings";
77 import { makePluginCtxAppsResolver } from "./resolver";
88 import { descriptorCollection, makeAppsStore, toolCollection, type AppsStore } from "./store";
99
10−export interface AppsExtension {
11− readonly publish: (input: PublishInput) => Effect.Effect<unknown, unknown>;
12−}
13−
1410 const APPS_INTEGRATION = "apps";
1511 const APPS_CONNECTION = "published";
1612
@@ -23,6 +19,13 @@ interface ProjectedToolSchema { · 6 unmodified lines
2319 readonly outputSchema?: unknown;
2420 }
2521
22+const makeAppsExtension = (ctx: PluginCtx<AppsStore>, executor?: AppToolExecutor) => ({
23+ publish: (input: Parameters<typeof publish>[1]) =>
24+ publish({ store: ctx.storage, executor: executor ?? makeInProcessAppToolExecutor() }, input),
25+});
26+
27+export type AppsExtension = ReturnType<typeof makeAppsExtension>;
28+
2629 export const makeAppsPlugin = (options?: { readonly executor?: AppToolExecutor }) =>
2730 definePlugin(() => ({
2831 id: "apps",
@@ -32,13 +35,7 @@ export const makeAppsPlugin = (options?: { readonly executor?: AppToolExecutor } · 3 unmodified lines
3235 [toolCollection.name]: toolCollection,
3336 },
3437 storage: ({ blobs, pluginStorage }) => makeAppsStore({ blobs, pluginStorage }),
35− extension: (ctx: PluginCtx<AppsStore>): AppsExtension => ({
36− publish: (input) =>
37− publish(
38− { store: ctx.storage, executor: options?.executor ?? makeInProcessAppToolExecutor() },
39− input,
40− ),
41− }),
38+ extension: (ctx: PluginCtx<AppsStore>) => makeAppsExtension(ctx, options?.executor),
4239 staticSources: () => [
4340 {
4441 id: "apps",
packages/plugins/apps/src/plugin/bindings.test.ts+10−15
@@ -1,12 +1,7 @@
11 import { describe, expect, it } from "@effect/vitest";
22 import { Effect } from "effect";
33
4−import {
5− BindingError,
6− buildBridge,
7− resolveIntegrationBindings,
8− type ClientResolver,
9−} from "./bindings";
4+import { buildBridge, resolveIntegrationBindings, type ClientResolver } from "./bindings";
105
116 const resolver: ClientResolver = {
127 listConnections: ({ integration }) =>
@@ -39,25 +34,23 @@ describe("integration bindings", () => { · 26 unmodified lines
3934
4035 it.effect("fails unknown connection addresses as BindingError", () =>
4136 Effect.gen(function* () {
42− const exit = yield* resolveIntegrationBindings(
37+ const error = yield* resolveIntegrationBindings(
4338 { crm: { slug: "dealcloud" } },
4439 { crm: "tools.dealcloud.org.missing" },
4540 resolver,
46− ).pipe(Effect.exit);
47− expect(exit._tag).toBe("Failure");
48− if (exit._tag === "Failure") expect(String(exit.cause)).toContain("BindingError");
41+ ).pipe(Effect.flip);
42+ expect(error).toMatchObject({ _tag: "BindingError" });
4943 }),
5044 );
5145
5246 it.effect("fails mismatched connection integrations as BindingError", () =>
5347 Effect.gen(function* () {
54− const exit = yield* resolveIntegrationBindings(
48+ const error = yield* resolveIntegrationBindings(
5549 { crm: { slug: "dealcloud" } },
5650 { crm: "tools.github.org.main" },
5751 resolver,
58− ).pipe(Effect.exit);
59− expect(exit._tag).toBe("Failure");
60− if (exit._tag === "Failure") expect(String(exit.cause)).toContain('not "dealcloud"');
52+ ).pipe(Effect.flip);
53+ expect(error).toMatchObject({ message: expect.stringContaining('not "dealcloud"') });
6154 }),
6255 );
6356
@@ -79,6 +72,8 @@ describe("integration bindings", () => { · 15 unmodified lines
7972 bindings: { crm: "tools.dealcloud.org.main" },
8073 resolver,
8174 });
82− await expect(bridge.call("github.repos.get", {})).rejects.toBeInstanceOf(BindingError);
75+ await expect(bridge.call("github.repos.get", {})).rejects.toMatchObject({
76+ _tag: "BindingError",
77+ });
8378 });
8479 });
packages/plugins/apps/src/plugin/bindings.ts+8−6
@@ -104,12 +104,14 @@ export const buildBridge = (input: { · 103 unmodified lines
104104 const decl = input.declared[field ?? ""];
105105 const connection = input.bindings[field ?? ""];
106106 if (!field || !decl || !connection || path.length === 0) {
107− return Promise.reject(
108− new BindingError({
109− role: field ?? "",
110− integration: decl?.slug ?? "",
111− message: `undeclared integration call: ${toolPath}`,
112− }),
107+ return Effect.runPromise(
108+ Effect.fail(
109+ new BindingError({
110+ role: field ?? "",
111+ integration: decl?.slug ?? "",
112+ message: `undeclared integration call: ${toolPath}`,
113+ }),
114+ ),
113115 );
114116 }
115117 return Effect.runPromise(