Files
punktfunk/plugin-kit/src/host-client.ts
T
enricobuehler 10a0ef3283 style(plugin-kit): adopt the biome config its own plugins already use
The kit had NO biome config and no lint script, while every plugin repo that
consumes it has both. So its source quietly drifted — unused imports, unsorted
imports, formatting — with nothing to catch any of it. Running biome here for
the first time reported 20 findings across 8 files.

Adds `plugin-kit/biome.json` mirroring the plugin repos' (tab indent, double
quotes, recommended lint preset, organizeImports), a `check` script, and
`@biomejs/biome` pinned to the same `^2.5.2` the plugins pin — without that pin
`bunx biome` resolved 2.4.6, which rejects the 2.5 `rules.preset` key.

Two deliberate differences from the plugin repos' copy:

  * no `vcs.useIgnoreFile` — those are standalone repos with a .gitignore beside
    the config; plugin-kit is a directory inside this one, and biome errors with
    "couldn't find an ignore file". The `files.includes` exclusions cover it.
  * `!examples/**/dist` instead of `!ui/dist` — the kit has examples, not a UI.

`css.parser.tailwindDirectives` is carried over and is load-bearing: without it
biome cannot parse `@theme` in src/theme.css and reports three parse errors on
CSS that is perfectly valid Tailwind v4.

Everything here is formatter/import churn except two real findings, both fixed:

  * `Layer` (library/define.ts) and `Cause` (sync-engine.ts) were imported and
    never used;
  * test/spike-httpapi.test.ts read `(reg?.body as …).ui.secret` one line after
    `expect(reg).toBeDefined()`. The optional chain undoes the assertion: had
    `reg` been undefined the `.ui` access would throw a TypeError instead of
    failing the test readably. Now asserted to the type system too.

Wired into plugin-kit-publish.yml as a `Lint & format` step ahead of Typecheck,
so this cannot rot again.

Gates after: biome clean (42 files), tsc clean, 67/67 tests, build clean.
2026-08-08 02:19:06 +02:00

54 lines
1.9 KiB
TypeScript

// The pf seam as Effect services. `HostClient.request` keeps the SDK's deliberately
// untyped `pf.request` wire (version-skew-safe against the runner-bundled SDK — design D7);
// typing happens one level up (reconcile.ts owns the wire schemas). Only the plain `pf`
// object crosses the plugin boundary, so no Effect values are shared with the runner's
// bundled effect instance.
import type { Punktfunk } from "@punktfunk/host";
import { Context, Effect, Layer } from "effect";
import { HostRequestError } from "./errors.js";
export interface HostClientService {
/** Raw management-API call (loopback + bearer, via the SDK facade). */
readonly request: (
method: string,
path: string,
body?: unknown,
) => Effect.Effect<unknown, HostRequestError>;
/**
* Escape hatch for SDK helpers that take the facade directly (`servePluginUi`).
* Never share this with code that could leak Effect values back to the runner.
*/
readonly facade: Punktfunk;
}
export class HostClient extends Context.Service<
HostClient,
HostClientService
>()("@punktfunk/plugin-kit/HostClient") {}
/** Wrap the facade the runner hands to `main` (or `connect()` in the CLI/dev paths). */
export const hostClientFromFacade = (pf: Punktfunk): Layer.Layer<HostClient> =>
Layer.succeed(HostClient)({
request: (method, path, body) =>
Effect.tryPromise({
try: () => pf.request(method, path, body),
catch: (cause) => new HostRequestError({ method, path, cause }),
}),
facade: pf,
});
export interface PluginInfoService {
/** The plugin id (`definePlugin` name, `[a-z][a-z0-9-]*`). */
readonly name: string;
readonly version?: string;
}
export class PluginInfo extends Context.Service<
PluginInfo,
PluginInfoService
>()("@punktfunk/plugin-kit/PluginInfo") {}
export const pluginInfoLayer = (
info: PluginInfoService,
): Layer.Layer<PluginInfo> => Layer.succeed(PluginInfo)(info);