Skip to content

Deploy, plan, promote

Shipped

Once an extension passes its tests, you ship it. Turbobase supports two deploy styles: a quick single-file deploy for one extension, and config-as-code for the whole instance — where your repository is the desired state, plan diffs it against an instance, and deploy --to applies it. turbobase logs closes the loop after a deploy.

The fast path — push one authored extension straight to the instance:

Terminal window
turbobase ext deploy hooks/customer.before_save.ts
# deployed ns:customer:require-name → status=active (created).

The CLI parses the file’s metadata (id / recordType / event / source / kind), POSTs it to /v1/extensions, and prints the resulting id, status, and outcome. A non-admin personal access token (PAT) gets a deploy forbidden (403).

Config-as-code — your repository is the desired state

Section titled “Config-as-code — your repository is the desired state”

For everything else, the unit of deploy is your project tree. These four inputs are the config-as-code source of truth for an instance’s deployable surfaces, and plan, deploy --to and promote all build from them:

In your repo Becomes
hooks/**/*.ts (defineHook) hooks — including nested folders, which is what ext new hook writes
conduits/**/*.ts (defineConduit) conduits
turbobase/sync.json promoted-field ownership + mirrored tables
turbobase/schedules.json scheduled jobs

turbobase apps init scaffolds all four, so a project has them from the first commit. Nothing has to be generated first — a plan reads the branch you are on, which is what lets a pull request show what merging would change.

Adopting an instance that already has config

Section titled “Adopting an instance that already has config”

If your instance was configured before the repo existed — hooks deployed one file at a time, a schedule set up in the console — pull it down first:

Terminal window
turbobase ext export
# wrote turbobase/bundle.json (142 lines).

ext export calls GET /v1/config/export (needs extensions:deploy; a PAT without it gets a clear 403). This is the adopt path, not the authoring one: move the extensions it lists into hooks/ and conduits/, and the file stops being consulted. A project that authors nothing in-tree still falls back to turbobase/bundle.json, so nothing breaks while you migrate.

plan prints a terraform-style diff of this project against a target env’s live config. It writes nothing — run it on every pull request:

Terminal window
turbobase ext plan --to prod
# plan against prod:
# + hook ns:customer:require-name
# ~ conduit shop2ns:order
# - hook custom:legacy:foo
#
# 1 to add, 1 to change, 1 to destroy.

--from <env> is advisory (it just labels which env the config was authored against); your project is always the desired state and --to selects the env whose live state you diff against. An empty plan prints (no changes — up to date.).

A - line means the instance has something your repo does not declare. Reading those before merging is the point of planning on a pull request — but note that deploy will not act on them unless you ask (see below).

To apply the bundle (idempotently) to an env:

Terminal window
turbobase ext deploy --to sandbox
# applied bundle to sandbox: +1 ~1 -1 (2 backfill(s) queued)

deploy --to builds the manifest from your project tree and POSTs it to /v1/config/apply, reporting adds / changes and any backfills queued. <file> is ignored when --to is given. As with single-file deploy, a non-admin PAT gets a 403.

deploy --to adds and updates. It does not remove. Deleting a hook from your repo does not delete it from the instance; the run prints what it declined to remove:

Terminal window
turbobase ext deploy --to prod
# applied bundle to prod: +1 ~1 -0
# 2 resource(s) exist on prod but are not declared by this project — NOT removed:
# hook custom:legacy:foo
# schedule nightly-legacy-export
# Add them to this repo (`turbobase ext export` pulls them down), or re-run with --prune to remove them.

Pass --prune to take them:

Terminal window
turbobase ext deploy --to prod --prune
# applied bundle to prod: +1 ~1 -2

The safe default exists because a repo does not always declare everything already running on an instance. Without it, the first deploy --to from a newly scaffolded project — which declares one example hook — would delete every other hook, conduit, promoted field, mirrored table and scheduled job on that instance. Once your repo IS the whole truth for your config, add --prune to your CI step.

promote takes --prune too, with the same default.

A prune never leaves a schedule running a process you removed

Section titled “A prune never leaves a schedule running a process you removed”

One removal is refused rather than taken: dropping a process while a recurring job on the instance still runs it. The job cannot do anything useful once the process is gone — it fires on its schedule, fails every time, and switches itself off after five failures — so the deploy stops instead:

Terminal window
turbobase ext deploy --to prod --prune
# turbobase: bundle apply failed: POST /v1/config/apply → 400: invalid_bundle:
# schedule 'rollup-tick' runs the process 'custom:process:nightly-rollup', which
# this deploy would remove — the schedule would keep firing, fail every time and
# switch itself off after five failures. Keep the process in the bundle, point
# the schedule at another process in the same deploy, or drop the schedule in
# the same deploy (delete it on the instance if it is not the bundle's)

That whole sentence reaches your terminal, ways out included — the refusal is the instance’s own words and the CLI no longer shortens one.

Nothing is written when it refuses — not the additions earlier in the same plan. Three ways out, and the right one depends on what you meant:

  • Retire both. Delete the schedule from turbobase/schedules.json in the same commit as the process. One deploy, both gone — this is the supported way.
  • Move the job. Point the schedule at a different process in the same commit. The job is screened against the body it will have once the deploy lands, so repointing it at a process you keep — or one this same bundle brings — applies normally, and so does turning it into a chat-prompt or system job.
  • Keep the process. Leave it declared and the prune carries on with everything else.

A job somebody created in the app rather than in your repo is not yours to drop from the bundle; delete it on the Scheduled tasks page and re-run the deploy.

A process no job runs prunes exactly as before, and so does one whose job was already dead before this deploy: the refusal is about an orphan your deploy would create, never one it inherited.

promote — env→env behind a fail-closed gate

Section titled “promote — env→env behind a fail-closed gate”

Promotion moves a vetted bundle from one env into another — typically sandbox → prod. It is guarded by a fail-closed confirm-slug check:

Terminal window
turbobase ext promote --from sandbox --to prod --confirm acme
# promoted sandbox → prod (acme): +3 ~2 -0

If --confirm does not exactly match the target instance’s slug, the CLI rejects before any network write — nothing is POSTed:

Terminal window
turbobase ext promote --from sandbox --to prod --confirm wrongslug
# turbobase: promote refused (fail-closed): …

This is the customer promoting their own instance config across their own environments. It is unrelated to — and never touches — the control-plane’s production cloud deploys.

After a deploy, watch the extension run:

Terminal window
turbobase logs --subsystem hooks --since 10m # every hook outcome
turbobase logs --extension ext_abc123 # this extension's log buffer
turbobase logs --conduit shop2ns:order --follow # a CONDUIT's run ledger

With no target flag, logs reads the unified platform event log — every hook rejection and error, every guardrail verdict, every conduit run, in one stream with one correlation id. --extension <id> reads that extension’s bounded log ring buffer ([level] ts message per line) and --conduit <id> streams the conduit_event run ledger, polling every --interval-ms (default 2000).

These two flags replace turbobase ext logs, which is removed.

Terminal window
turbobase ext export # 1. snapshot live config
# edit hooks/ + conduits/, then:
turbobase ext lint hooks/foo.ts # 2. lint offline
turbobase ext test hooks/foo.ts # 3. dry-run in the sandbox
turbobase ext plan --to sandbox # 4. preview the diff
turbobase ext deploy --to sandbox # 5. apply to sandbox
turbobase ext promote --from sandbox --to prod --confirm acme # 6. promote
turbobase logs --extension ext_abc123 # 7. verify it runs