All checks were successful
CD / Build and push images (push) Successful in 3m14s
CI / Lint, typecheck, test (push) Successful in 3m18s
CI / Auth e2e pack (push) Successful in 4m3s
CI / Import/export fidelity gate (push) Successful in 54s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m9s
CD / Promote to Int (push) Has been skipped
The SDK is the contract every other M7 story builds on (ADR 0008,
plugin-architecture.md). New package `@dorfteich/plugin-sdk`, standalone
(only depends on zod) so a plugin author needs nothing else.
- Zod manifest schema (`validateManifest`/`parseManifest`) with actionable
`{ path, message }` issues and cross-field rules (extension-point/kind
match, unique ids, section_style declares no permissions). Fixtures:
3 valid + 14 invalid variants, asserted individually.
- `checkApiVersion` compatibility helper against the host's supported range.
- Capability names + method→capability map as the single source of truth
for the permission gate.
- Transport-agnostic postMessage RPC engine (`createRpcEndpoint`) with
request/response ids, per-request timeouts, unknown-method and
endpoint-disposed handling, plus a `windowTransport` adapter.
- Host side (`createHostBridge`): routes plugin capability calls through
the manifest permission gate; drives plugin lifecycle (render/edit/destroy).
- Plugin side (`createPlugin`): answers lifecycle calls, exposes a typed
`host` proxy. RPC roundtrip verified in a jsdom MessageChannel test
(roundtrip, args, timeout, unknown method, undeclared capability, dispose).
- README documents the protocol with a mermaid sequence diagram.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
42 lines
1.4 KiB
TypeScript
42 lines
1.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
|
||
import { checkApiVersion, HOST_API_VERSION, isApiVersionSupported } from './api-version';
|
||
|
||
describe('checkApiVersion', () => {
|
||
it('accepts a supported major', () => {
|
||
const result = checkApiVersion('1');
|
||
expect(result).toEqual({ compatible: true, version: 1 });
|
||
});
|
||
|
||
it('rejects a non-numeric version with a reason', () => {
|
||
const result = checkApiVersion('1.0');
|
||
expect(result.compatible).toBe(false);
|
||
expect(result.version).toBeNull();
|
||
expect(result.reason).toMatch(/whole-number major/i);
|
||
});
|
||
|
||
it('rejects a version above the host range', () => {
|
||
const result = checkApiVersion('2', { min: 1, max: 1 });
|
||
expect(result.compatible).toBe(false);
|
||
expect(result.version).toBe(2);
|
||
expect(result.reason).toMatch(/outside the supported range 1–1/);
|
||
});
|
||
|
||
it('rejects a version below the host range', () => {
|
||
const result = checkApiVersion('1', { min: 2, max: 3 });
|
||
expect(result.compatible).toBe(false);
|
||
expect(result.reason).toMatch(/outside the supported range 2–3/);
|
||
});
|
||
|
||
it('accepts any major inside a wider host range', () => {
|
||
expect(checkApiVersion('2', { min: 1, max: 3 }).compatible).toBe(true);
|
||
});
|
||
});
|
||
|
||
describe('isApiVersionSupported', () => {
|
||
it('mirrors checkApiVersion as a boolean', () => {
|
||
expect(isApiVersionSupported(String(HOST_API_VERSION))).toBe(true);
|
||
expect(isApiVersionSupported('99')).toBe(false);
|
||
});
|
||
});
|