Add a Deploy Adapter
Get a task done. Add support for a new deploy target (a stack or hosting platform) to the pluggable deploy-adapter framework. (Back to how-to home.)
The framework lives at scripts/deploy/ and is invoked through /deploy (or
scripts/deploy.sh). It resolves an adapter for the project and runs the
deploy contract — check → build → migrate → deploy → verify — parameterized
by environment (--env test|prod). Adding an adapter is a one-file change:
drop adapters/<name>.sh; resolution and dispatch pick it up automatically.
The contract
Section titled “The contract”An adapter is a bash file sourced by the dispatcher. It sets ADAPTER_NAME and
implements either the five ordered step functions or a single monolithic
adapter_run:
| Function | Step | Notes |
|---|---|---|
adapter_check <env> |
check | pre-flight gates (clean tree, types, tests) |
adapter_build <env> |
build | produce the deployable artifact |
adapter_migrate <env> |
migrate | apply schema/data migrations to <env> |
adapter_deploy <env> |
deploy | ship to <env> |
adapter_verify <env> |
verify | health-check the deployed <env> |
adapter_run <env> |
(all) | escape hatch: run the whole pipeline yourself |
Each function receives the target environment (test or prod) as $1. Any
undefined step is a no-op (a static site has no migrate). If adapter_run is
defined it takes precedence over the step functions — use it to wrap a
proven, indivisible pipeline (this is what the cloudflare adapter does).
The dispatcher exports to every adapter: DEPLOY_ENV, DEPLOY_DRY_RUN,
DEPLOY_SKIP_TESTS, DEPLOY_SKIP_BUILD, DEPLOY_PROJECT_DIR.
Shared helpers
Section titled “Shared helpers”lib/contract.sh provides helpers so adapters stay lean — use them:
deploy_cfg '<jq filter>'— read a value from the project’sdev.json(empty if absent).run_cmd '<label>' '<command>'— run a command, honoring--dry-run(echo intent, don’t execute).http_health '<url>' [tries]— curl a URL with backoff; 0 on first HTTP 200.pass/fail/warn/info/header— consistent logging.
Step 1 — Write the adapter
Section titled “Step 1 — Write the adapter”Create scripts/deploy/adapters/<name>.sh. Minimal discrete-step example:
#!/usr/bin/env bash# myhost.sh — deploy adapter for MyHost.ADAPTER_NAME="myhost"
adapter_check() { local env="$1"; run_cmd "test" "npm test"; }adapter_build() { local env="$1"; run_cmd "build" "npm run build"; }adapter_migrate() { local env="$1"; run_cmd "migrate ($env)" "$(deploy_cfg '.deploy.migrate')"; }adapter_deploy() { local env="$1"; run_cmd "deploy ($env)" "myhost push --env $env"; }adapter_verify() { local env="$1"; http_health "$(deploy_cfg ".access.\"$env\"")"; }Do not call set -e or exit inside step functions — return non-zero to
signal failure; the dispatcher aborts the pipeline on the first failing step and
reports status:error. Honor --dry-run by routing real side effects through
run_cmd (or guarding on DEPLOY_DRY_RUN).
Step 2 — Wire resolution
Section titled “Step 2 — Wire resolution”Auto-detection lives in lib/resolve.sh::_autodetect_adapter, which maps a
detected stack → adapter name using scripts/fleet/readiness.js (the single
source of truth for stack detection). Add a branch there if your adapter should
be picked automatically:
if [[ "$stack" == *" myhost "* ]]; then echo "myhost"; return 0; fiA project can always select an adapter explicitly without touching resolution —
via dev.json .deploy.adapter or the --adapter <name> flag. An adapter name
is valid iff adapters/<name>.sh exists.
Step 3 — Add a test
Section titled “Step 3 — Add a test”Extend tests/test-deploy-adapters.sh with a fixture + a --dry-run dispatch
assertion (no real deploys), then confirm it is wired into the make test target:
out="$(bash "$DEPLOY" --env prod --dry-run --adapter myhost --project "$TMP/myhost" 2>&1)"contains "myhost runs deploy step" "myhost push --env prod" "$out"Step 4 — Verify and deploy
Section titled “Step 4 — Verify and deploy”make test # full suite, including test-deploy-adaptersscripts/deploy/deploy.sh --list # your adapter should appearscripts/deploy.sh --adapter myhost --env test --dry-run # smoke-test the contractThen sync to BOB_HOME with scripts/deploy.sh (the BoB deploy) so the runtime
picks up the new adapter.
Reference
Section titled “Reference”- Framework:
scripts/deploy/(deploy.sh,lib/contract.sh,lib/resolve.sh,adapters/) - Command: /deploy
- Environment targets come from
scripts/promotion/promotion.js(decide()→deploy: test|prod).