Plugins
Glubean has two distinct plugin concepts. Pick the one that matches what you’re extending.
| Concept | API | Scope | Consumed by |
|---|---|---|---|
| Plugin manifest (global) | definePlugin(manifest) + installPlugin(manifest) | Process-wide matchers, protocol adapters, one-time setup | glubean.setup.ts |
| Client factory (per file) | defineClientFactory((runtime) => client) | Per-test lazy client instance (shared auth, HTTP, domain helpers) | configure({ plugins }) |
The legacy definePlugin((runtime) => T) overload has been removed. Use defineClientFactory for that use case.
1. Client factory — per-file shared client
Use this when multiple tests in the same file need the same client (e.g. an internal API wrapper that reads a base URL from vars).
Basic pattern
- Define a factory with
defineClientFactory(...) - Register it in
configure({ plugins }) - Use the shared instance in tests
The client is created lazily on first access during test execution.
Example
import { configure, defineClientFactory, test } from "@glubean/sdk";
const myClient = (opts: { endpointKey: string }) =>
defineClientFactory((runtime) => {
const baseUrl = runtime.requireVar(opts.endpointKey);
return {
getStatus: async () => runtime.http.get(`${baseUrl}/status`).json(),
};
});
const { internalApi } = configure({
plugins: {
internalApi: myClient({ endpointKey: "INTERNAL_API_URL" }),
},
});
export const health = test("internal-health", async (ctx) => {
const status = await internalApi.getStatus();
ctx.expect(status.ok).toBe(true);
});Runtime object passed to the factory
| Property | Description |
|---|---|
runtime.http | HTTP client (auto-traced) |
runtime.requireVar(key) | Read a var (throws if missing) |
runtime.requireSecret(key) | Read a secret (throws if missing) |
runtime.resolveTemplate(str) | Resolve {{KEY}} placeholders |
runtime.vars | Raw vars record |
runtime.secrets | Raw secrets record |
runtime.action(a) | Emit a custom action event |
runtime.event(ev) | Emit a custom event |
runtime.log(msg, data?) | Log from plugin context |
Notes
- Reserved keys (
vars,secrets,http) cannot be used as plugin names inconfigure({ plugins }). - Clients are cached per test execution and recreated for each new test.
2. Plugin manifest — global matchers / protocol adapters
Use this when you want to publish an npm package that adds process-wide behavior: custom ctx.expect(...) matchers, a new contract protocol (e.g. contract.graphql), or a one-time setup() hook that runs before any tests load.
This is what @glubean/graphql, @glubean/grpc, and @glubean/browser are.
@glubean/auth is different: it provides HTTP auth helper functions and a
builder transform, so you use it through configure({ http: ... }) or
test().use(...) rather than installPlugin(...).
This is also the openness boundary for contracts. contract.http and
contract.browser are first-party paths, but protocols are not closed: GraphQL
and gRPC install contract.graphql / contract.grpc, and a team plugin can
register another protocol the same way.
Authoring a manifest
// packages/my-plugin/src/index.ts
import { contract, definePlugin } from "@glubean/sdk";
import type { PluginManifest } from "@glubean/sdk";
const myPlugin: PluginManifest = definePlugin({
name: "@my-scope/my-plugin",
matchers: {
toBeMyThing(actual) {
return { pass: actual === "thing", message: () => "..." };
},
},
contracts: {
myproto: myprotoAdapter,
},
setup() {
// Optional. Runs once after matchers/contracts are registered.
},
});
export default myPlugin;Installing it in a project — required
A plugin manifest has no effect until installPlugin(manifest) runs. The canonical place to call it is glubean.setup.ts at the project root:
// glubean.setup.ts (project root, alongside glubean.yaml and tests/)
import { installPlugin } from "@glubean/sdk";
import graphqlPlugin from "@glubean/graphql";
import grpcPlugin from "@glubean/grpc";
await installPlugin(graphqlPlugin, grpcPlugin);glubean.setup.ts runs exactly once per process (CLI run, CLI contracts, MCP tool calls, VSCode scan) before any test file or .contract.ts module is imported. Matchers and protocol adapters are only available after this file executes.
A top-level import "@glubean/graphql" is not a side-effect registration anymore — you must explicitly installPlugin.
Install-time guarantees
- Installing the same plugin twice (same
name) is a no-op. - Duplicate matcher or protocol names across plugins throw at install time.
- If
setup()throws, the plugin is marked failed and retrying in the same process throws with a clear error — restart to recover.
First-party plugin manifests
- Browser (
@glubean/browser) — Puppeteer-powered browser automation - GraphQL (
@glubean/graphql) — Typed GraphQL queries, mutations, and contract protocol - gRPC (
@glubean/grpc) — gRPC status matchers and contract protocol
All of these require await installPlugin(...) in glubean.setup.ts when you
use their global matchers or contract protocols.
First-party helper packages
- Auth (
@glubean/auth) — bearer, API key, Basic Auth, OAuth helpers, andwithLogin(...)builder transforms