Appearance
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
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
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
Create scripts/deploy/adapters/<name>.sh. Minimal discrete-step example:
bash
#!/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
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:
bash
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
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:
bash
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
bash
make test # full suite, including test-deploy-adapters
scripts/deploy/deploy.sh --list # your adapter should appear
scripts/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
- 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).