Scaffold
1
Generate the package
--label "Acme API" to pnpm new:provider to set the display label (it defaults to the capitalized name).2
Install and build
pnpm-lock.yaml.3
Copy the checklist
The script prints the provider definition of done. Paste it into your pull request body.
pnpm new:provider acme --remove. It only removes the last provider in the registry, and refuses providers that shipped in 0.14.0, because removing any other slot would shift later ports.
What the scaffold creates
- Package
- Wiring
- Docs
packages/@deploybaseline/acme/ with:package.json,tsconfig.json,tsup.config.ts(entriessrc/index.ts,src/manifest.ts, andsrc/provider.ts),vitest.config.tsusingproviderTestConfigREADME.md,LICENSE,THIRD_PARTY_NOTICES.mdsrc/manifest.ts,src/entities.ts,src/store.ts,src/index.tssrc/provider.ts, the process entry the CLI forks: one call to core’srunProvider(manifest)src/routes/items.ts(sample routes) andsrc/routes/inspector.tssrc/__tests__/coverage.ts(matrix),coverage.test.ts(gate 1),coverage.setup.ts(gate 2, infailmode)src/__tests__/acme.sdk.test.ts, a test skeleton including arestartServicecheck and an inspector check
Provider definition of done
This is the checklistscripts/new-provider.mjs prints:
- Routes match the vendor’s API: paths, shapes, status codes, and error bodies
- Tests drive the emulator with the official SDK, pinned to an exact version
- Coverage matrix lists every route; the gate passes in fail mode
- All state lives in the store; a
restartServicetest shows reads match after a restart - Seed config documented;
initConfiginsrc/manifest.tsis a useful starter - Inspector page uses the shared UI in core
ui.ts - Docs: README row,
docs/acme.mdxand itsdocs.jsonentry,skills/acme/SKILL.md, CLI--helpif it adds flags - Vendored specs or schemas have a license check and a
THIRD_PARTY_NOTICES.mdentry - npm first publish bootstrap for
@deploybaseline/acme(a new package name needs a placeholder publish and a trusted publisher, see Bootstrapping package names)
The last item is a maintainer task. The release workflow refuses to run while any workspace package is missing from npm. See Releasing.
Anatomy of a finished provider
@deploybaseline/resend is a compact, complete example:
State rules
- Keep every piece of state in the store: collections, or
store.setDatafor maps and flags. Never in module variables or closures insideregister. - Namespace collection names with the provider (
resend.emails). - Every provider has a
state-restarttest: run a main flow, callrestartServicefrom@deploybaseline/testing(save, then start a fresh instance from the saved JSON on the same port), and check that reads match and tokens still work. - Bump
stateVersioninsrc/manifest.tswhen an entity shape changes in a way older saved state cannot load. - Changes to how a runtime saves or loads state must keep the shared contract in
tests/contracts/persistence.jspassing forcreateEmulatorand thestartrunner.
Webhooks and UI
- Sign deliveries with the core helpers (
svixSignature,hmacSha256Hex, and the rest), or a provider’s ownsigning.tsfor a scheme no other provider uses. See Architecture. - Build inspector, OAuth, and checkout pages with the render functions in
core/src/ui.ts. See Shared UI.
GraphQL providers
- Serve the endpoint with
graphqlEndpointand build the schema withbuildSchemafrom@deploybaseline/graphql. - Start from the vendor’s published SDL.
node scripts/vendor-graphql-schema.mjs <name>fetches it and writessrc/graphql/schema.generated.ts(descriptions trimmed for size). Add the source to that script with its license, and add aTHIRD_PARTY_NOTICES.mdentry. - The coverage matrix lists every root field in the schema (
Query.issues,Mutation.issueCreate) next to the routes. A root field without a resolver staysnot_startedand answers “not emulated”.
Composed providers
For several APIs behind one port and seed key, build the plugin withcomposeService (see Composed services):
- Each module registers its routes and declares the namespaced collections it owns.
- Put each module’s matrix under its name in
src/__tests__/coverage.ts:export const modules = { gmail: [...], drive: [...] }. - Check it with
checkModuleCoverageand passmodulestocoverageGate. A route listed under the wrong module fails, the CI job summary gets one row per module, andpnpm conformancepublishes per-API counts. node scripts/generate-coverage-matrix.mjs <name>writes the module layout.