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
51 },51 },
52 });52 });
53 `);53 `);
54 const exit = yield* makeInProcessAppToolExecutor()54 const error = yield* makeInProcessAppToolExecutor()
55 .collect(bundled.code, {55 .collect(bundled.code, {
56 fileSlug: "sync-deals",56 fileSlug: "sync-deals",
57 sourcePath: "tools/sync-deals.ts",57 sourcePath: "tools/sync-deals.ts",
58 })58 })
59 .pipe(Effect.exit);59 .pipe(Effect.flip);
60 expect(exit._tag).toBe("Failure");60 expect(error).toMatchObject({ message: expect.stringContaining("nested.crm") });
61 if (exit._tag === "Failure") {
62 expect(String(exit.cause)).toContain("nested.crm");
63 }
64 }),61 }),
65 );62 );
66 63
@@ -106,14 +103,13 @@ describe("in-process app tool executor", () => { · 39 unmodified lines
106 }),103 }),
107 };104 };
108 `);105 `);
109 const exit = yield* makeInProcessAppToolExecutor()106 const error = yield* makeInProcessAppToolExecutor()
110 .collect(bundled.code, {107 .collect(bundled.code, {
111 fileSlug: "sync-deals",108 fileSlug: "sync-deals",
112 sourcePath: "tools/sync-deals.ts",109 sourcePath: "tools/sync-deals.ts",
113 })110 })
114 .pipe(Effect.exit);111 .pipe(Effect.flip);
115 expect(exit._tag).toBe("Failure");112 expect(error).toMatchObject({ message: expect.stringContaining("collides") });
116 if (exit._tag === "Failure") expect(String(exit.cause)).toContain("collides");
117 }),113 }),
118 );114 );
119 115
@@ -161,7 +157,7 @@ describe("in-process app tool executor", () => { · 41 unmodified lines
161 handler(input) { return input; },157 handler(input) { return input; },
162 });158 });
163 `);159 `);
164 const exit = yield* makeInProcessAppToolExecutor()160 const error = yield* makeInProcessAppToolExecutor()
165 .invoke(161 .invoke(
166 bundled.code,162 bundled.code,
167 { toolName: "sync-deals" },163 { toolName: "sync-deals" },
@@ -171,9 +167,8 @@ describe("in-process app tool executor", () => { · 3 unmodified lines
171 timeoutMs: 1000,167 timeoutMs: 1000,
172 },168 },
173 )169 )
174 .pipe(Effect.exit);170 .pipe(Effect.flip);
175 expect(exit._tag).toBe("Failure");171 expect(error).toMatchObject({ message: expect.stringContaining("validation failed") });
176 if (exit._tag === "Failure") expect(String(exit.cause)).toContain("validation failed");
177 }),172 }),
178 );173 );
179 174
@@ -191,7 +186,7 @@ describe("in-process app tool executor", () => { · 11 unmodified lines
191 },186 },
192 });187 });
193 `);188 `);
194 const exit = yield* makeInProcessAppToolExecutor()189 const error = yield* makeInProcessAppToolExecutor()
195 .invoke(190 .invoke(
196 bundled.code,191 bundled.code,
197 { toolName: "sync-deals" },192 { toolName: "sync-deals" },
@@ -201,9 +196,8 @@ describe("in-process app tool executor", () => { · 3 unmodified lines
201 timeoutMs: 1,196 timeoutMs: 1,
202 },197 },
203 )198 )
204 .pipe(Effect.exit);199 .pipe(Effect.flip);
205 expect(exit._tag).toBe("Failure");200 expect(error).toMatchObject({ message: expect.stringContaining("timed out") });
206 if (exit._tag === "Failure") expect(String(exit.cause)).toContain("timed out");
207 }),201 }),
208 );202 );
209});203});
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 */
1import { Buffer } from "node:buffer";2import { Buffer } from "node:buffer";
2import { Data, Effect } from "effect";3import { Data, Effect } from "effect";
3import { z } from "zod";4import { z } from "zod";
packages/plugins/apps/src/pipeline/publish.test.ts+7−7
@@ -4,7 +4,7 @@ import { Effect } from "effect"; · 3 unmodified lines
4import type { AppsStore } from "../plugin/store";4import type { AppsStore } from "../plugin/store";
5import { makeInProcessAppToolExecutor } from "../executor/app-tool-executor";5import { makeInProcessAppToolExecutor } from "../executor/app-tool-executor";
6import type { AppDescriptor } from "./descriptor";6import type { AppDescriptor } from "./descriptor";
7import { discover, PublishError } from "./discover";7import { discover } from "./discover";
8import { publish } from "./publish";8import { publish } from "./publish";
9 9
10const encoder = new TextEncoder();10const encoder = new TextEncoder();
@@ -75,9 +75,10 @@ describe("discover", () => { · 64 unmodified lines
75 ["skills/guide.md", ""],75 ["skills/guide.md", ""],
76 ]),76 ]),
77 );77 );
78 expect(result).toBeInstanceOf(PublishError);78 expect(result).toMatchObject({
79 if (!(result instanceof PublishError)) return;79 _tag: "PublishError",
80 expect(result.diagnostics[0]?.path).toBe("tools/readme.md");80 diagnostics: [{ path: "tools/readme.md" }],
81 });
81 });82 });
82});83});
83 84
@@ -117,7 +118,7 @@ describe("publish", () => { · 33 unmodified lines
117 it.effect("rejects nondeterministic factories", () =>118 it.effect("rejects nondeterministic factories", () =>
118 Effect.gen(function* () {119 Effect.gen(function* () {
119 const store = makeMemoryStore();120 const store = makeMemoryStore();
120 const exit = yield* publish(121 yield* publish(
121 { store, executor: makeInProcessAppToolExecutor() },122 { store, executor: makeInProcessAppToolExecutor() },
122 {123 {
123 app: "crm",124 app: "crm",
@@ -139,8 +140,7 @@ describe("publish", () => { · 15 unmodified lines
139 ),140 ),
140 ],141 ],
141 },142 },
142 ).pipe(Effect.exit);143 ).pipe(Effect.flip);
143 expect(exit._tag).toBe("Failure");
144 expect(store.rows.size).toBe(0);144 expect(store.rows.size).toBe(0);
145 }),145 }),
146 );146 );
packages/plugins/apps/src/pipeline/publish.ts+3−1
@@ -66,11 +66,13 @@ const toPublishError = ( · 65 unmodified lines
66): PublishError =>66): PublishError =>
67 new PublishError({67 new PublishError({
68 stage,68 stage,
69 // oxlint-disable-next-line executor/no-unknown-error-message -- typed AppExecutorError conversion keeps its stable diagnostic message
69 message: cause.message,70 message: cause.message,
70 diagnostics:71 diagnostics:
71 cause.diagnostics && cause.diagnostics.length > 072 cause.diagnostics && cause.diagnostics.length > 0
72 ? cause.diagnostics73 ? 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 }],
74 });76 });
75 77
76const toolDescriptor = (input: {78const toolDescriptor = (input: {
packages/plugins/apps/src/plugin/apps-plugin.ts+9−12
@@ -2,15 +2,11 @@ import { Data, Effect } from "effect"; · 1 unmodified line
2import { ToolName, definePlugin, type PluginCtx, type ToolDef } from "@executor-js/sdk";2import { ToolName, definePlugin, type PluginCtx, type ToolDef } from "@executor-js/sdk";
3 3
4import { makeInProcessAppToolExecutor, type AppToolExecutor } from "../executor/app-tool-executor";4import { makeInProcessAppToolExecutor, type AppToolExecutor } from "../executor/app-tool-executor";
5import { publish, type PublishInput } from "../pipeline/publish";5import { publish } from "../pipeline/publish";
6import { buildBridge, resolveIntegrationBindings } from "./bindings";6import { buildBridge, resolveIntegrationBindings } from "./bindings";
7import { makePluginCtxAppsResolver } from "./resolver";7import { makePluginCtxAppsResolver } from "./resolver";
8import { descriptorCollection, makeAppsStore, toolCollection, type AppsStore } from "./store";8import { descriptorCollection, makeAppsStore, toolCollection, type AppsStore } from "./store";
9 9
10export interface AppsExtension {
11 readonly publish: (input: PublishInput) => Effect.Effect<unknown, unknown>;
12}
13
14const APPS_INTEGRATION = "apps";10const APPS_INTEGRATION = "apps";
15const APPS_CONNECTION = "published";11const APPS_CONNECTION = "published";
16 12
@@ -23,6 +19,13 @@ interface ProjectedToolSchema { · 6 unmodified lines
23 readonly outputSchema?: unknown;19 readonly outputSchema?: unknown;
24}20}
25 21
22const makeAppsExtension = (ctx: PluginCtx<AppsStore>, executor?: AppToolExecutor) => ({
23 publish: (input: Parameters<typeof publish>[1]) =>
24 publish({ store: ctx.storage, executor: executor ?? makeInProcessAppToolExecutor() }, input),
25});
26
27export type AppsExtension = ReturnType<typeof makeAppsExtension>;
28
26export const makeAppsPlugin = (options?: { readonly executor?: AppToolExecutor }) =>29export const makeAppsPlugin = (options?: { readonly executor?: AppToolExecutor }) =>
27 definePlugin(() => ({30 definePlugin(() => ({
28 id: "apps",31 id: "apps",
@@ -32,13 +35,7 @@ export const makeAppsPlugin = (options?: { readonly executor?: AppToolExecutor } · 3 unmodified lines
32 [toolCollection.name]: toolCollection,35 [toolCollection.name]: toolCollection,
33 },36 },
34 storage: ({ blobs, pluginStorage }) => makeAppsStore({ blobs, pluginStorage }),37 storage: ({ blobs, pluginStorage }) => makeAppsStore({ blobs, pluginStorage }),
35 extension: (ctx: PluginCtx<AppsStore>): AppsExtension => ({38 extension: (ctx: PluginCtx<AppsStore>) => makeAppsExtension(ctx, options?.executor),
36 publish: (input) =>
37 publish(
38 { store: ctx.storage, executor: options?.executor ?? makeInProcessAppToolExecutor() },
39 input,
40 ),
41 }),
42 staticSources: () => [39 staticSources: () => [
43 {40 {
44 id: "apps",41 id: "apps",
packages/plugins/apps/src/plugin/bindings.test.ts+10−15
@@ -1,12 +1,7 @@
1import { describe, expect, it } from "@effect/vitest";1import { describe, expect, it } from "@effect/vitest";
2import { Effect } from "effect";2import { Effect } from "effect";
3 3
4import {4import { buildBridge, resolveIntegrationBindings, type ClientResolver } from "./bindings";
5 BindingError,
6 buildBridge,
7 resolveIntegrationBindings,
8 type ClientResolver,
9} from "./bindings";
10 5
11const resolver: ClientResolver = {6const resolver: ClientResolver = {
12 listConnections: ({ integration }) =>7 listConnections: ({ integration }) =>
@@ -39,25 +34,23 @@ describe("integration bindings", () => { · 26 unmodified lines
39 34
40 it.effect("fails unknown connection addresses as BindingError", () =>35 it.effect("fails unknown connection addresses as BindingError", () =>
41 Effect.gen(function* () {36 Effect.gen(function* () {
42 const exit = yield* resolveIntegrationBindings(37 const error = yield* resolveIntegrationBindings(
43 { crm: { slug: "dealcloud" } },38 { crm: { slug: "dealcloud" } },
44 { crm: "tools.dealcloud.org.missing" },39 { crm: "tools.dealcloud.org.missing" },
45 resolver,40 resolver,
46 ).pipe(Effect.exit);41 ).pipe(Effect.flip);
47 expect(exit._tag).toBe("Failure");42 expect(error).toMatchObject({ _tag: "BindingError" });
48 if (exit._tag === "Failure") expect(String(exit.cause)).toContain("BindingError");
49 }),43 }),
50 );44 );
51 45
52 it.effect("fails mismatched connection integrations as BindingError", () =>46 it.effect("fails mismatched connection integrations as BindingError", () =>
53 Effect.gen(function* () {47 Effect.gen(function* () {
54 const exit = yield* resolveIntegrationBindings(48 const error = yield* resolveIntegrationBindings(
55 { crm: { slug: "dealcloud" } },49 { crm: { slug: "dealcloud" } },
56 { crm: "tools.github.org.main" },50 { crm: "tools.github.org.main" },
57 resolver,51 resolver,
58 ).pipe(Effect.exit);52 ).pipe(Effect.flip);
59 expect(exit._tag).toBe("Failure");53 expect(error).toMatchObject({ message: expect.stringContaining('not "dealcloud"') });
60 if (exit._tag === "Failure") expect(String(exit.cause)).toContain('not "dealcloud"');
61 }),54 }),
62 );55 );
63 56
@@ -79,6 +72,8 @@ describe("integration bindings", () => { · 15 unmodified lines
79 bindings: { crm: "tools.dealcloud.org.main" },72 bindings: { crm: "tools.dealcloud.org.main" },
80 resolver,73 resolver,
81 });74 });
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 });
83 });78 });
84});79});
packages/plugins/apps/src/plugin/bindings.ts+8−6
@@ -104,12 +104,14 @@ export const buildBridge = (input: { · 103 unmodified lines
104 const decl = input.declared[field ?? ""];104 const decl = input.declared[field ?? ""];
105 const connection = input.bindings[field ?? ""];105 const connection = input.bindings[field ?? ""];
106 if (!field || !decl || !connection || path.length === 0) {106 if (!field || !decl || !connection || path.length === 0) {
107 return Promise.reject(107 return Effect.runPromise(
108 new BindingError({108 Effect.fail(
109 role: field ?? "",109 new BindingError({
110 integration: decl?.slug ?? "",110 role: field ?? "",
111 message: `undeclared integration call: ${toolPath}`,111 integration: decl?.slug ?? "",
112 }),112 message: `undeclared integration call: ${toolPath}`,
113 }),
114 ),
113 );115 );
114 }116 }
115 return Effect.runPromise(117 return Effect.runPromise(