@catapultjs/deploy 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +9 -1
  2. package/build/commands/list_revisions.js +12 -25
  3. package/build/commands/list_revisions.js.map +1 -1
  4. package/build/commands/status.d.ts +1 -0
  5. package/build/commands/status.js +36 -78
  6. package/build/commands/status.js.map +1 -1
  7. package/build/commands/version.js +3 -3
  8. package/build/commands/version.js.map +1 -1
  9. package/build/index.d.ts +3 -0
  10. package/build/index.js +3 -0
  11. package/build/index.js.map +1 -1
  12. package/build/recipes/astro.js +9 -13
  13. package/build/recipes/astro.js.map +1 -1
  14. package/build/recipes/astro_static.d.ts +1 -0
  15. package/build/recipes/astro_static.js +11 -0
  16. package/build/recipes/astro_static.js.map +1 -0
  17. package/build/recipes/deploy/update_code.js +5 -4
  18. package/build/recipes/deploy/update_code.js.map +1 -1
  19. package/build/recipes/nestjs.d.ts +1 -0
  20. package/build/recipes/nestjs.js +5 -0
  21. package/build/recipes/nestjs.js.map +1 -0
  22. package/build/recipes/nextjs.d.ts +1 -0
  23. package/build/recipes/nextjs.js +20 -0
  24. package/build/recipes/nextjs.js.map +1 -0
  25. package/build/recipes/nextjs_static.d.ts +1 -0
  26. package/build/recipes/nextjs_static.js +8 -0
  27. package/build/recipes/nextjs_static.js.map +1 -0
  28. package/build/recipes/nuxt_static.d.ts +1 -0
  29. package/build/recipes/nuxt_static.js +8 -0
  30. package/build/recipes/nuxt_static.js.map +1 -0
  31. package/build/recipes/pm2.js +15 -14
  32. package/build/recipes/pm2.js.map +1 -1
  33. package/build/recipes/tanstack.d.ts +1 -0
  34. package/build/recipes/tanstack.js +5 -0
  35. package/build/recipes/tanstack.js.map +1 -0
  36. package/build/src/api.d.ts +60 -0
  37. package/build/src/api.js +159 -0
  38. package/build/src/api.js.map +1 -0
  39. package/build/src/deployer.d.ts +7 -1
  40. package/build/src/deployer.js +19 -1
  41. package/build/src/deployer.js.map +1 -1
  42. package/build/src/status.d.ts +18 -0
  43. package/build/src/status.js +59 -0
  44. package/build/src/status.js.map +1 -0
  45. package/build/src/task/store.d.ts +4 -1
  46. package/build/src/task/store.js +8 -2
  47. package/build/src/task/store.js.map +1 -1
  48. package/build/src/task.d.ts +4 -1
  49. package/build/src/task.js +2 -2
  50. package/build/src/task.js.map +1 -1
  51. package/package.json +16 -7
  52. package/skills/catapultjs/SKILL.md +41 -0
  53. package/skills/catapultjs/references/api.md +70 -0
  54. package/skills/catapultjs/references/cli.md +160 -0
  55. package/skills/catapultjs/references/config.md +194 -0
  56. package/skills/catapultjs/references/github-actions.md +99 -0
  57. package/skills/catapultjs/references/recipe.md +115 -0
  58. package/skills/catapultjs/references/recipes.md +596 -0
@@ -0,0 +1,70 @@
1
+ # Using the Catapult programmatic API
2
+
3
+ The `Catapult` class exposes the same operations as the `cata` CLI, without any interactive prompt. Full docs: https://catapultjs.com/guide/programmatic
4
+
5
+ ```typescript
6
+ import { Catapult } from '@catapultjs/deploy'
7
+ import '@catapultjs/deploy/recipes/git'
8
+ import '@catapultjs/deploy/recipes/pm2'
9
+
10
+ const catapult = new Catapult({
11
+ hosts: [{ name: 'production', ssh: 'deploy@example.com', deployPath: '/home/deploy/myapp' }],
12
+ })
13
+
14
+ await catapult.deploy()
15
+ ```
16
+
17
+ Rules that differ from a `deploy.ts`:
18
+
19
+ - Import recipes **before** constructing, exactly like in a config file.
20
+ - The constructor takes the same object as `defineConfig()`: `hosts`, `keepReleases`, `repository`, `packageManager`, `hooks`, `verbose`.
21
+ - One `Catapult` instance per process. The deploy context is global; a second instance with a different config is not supported.
22
+ - No prompts: methods either act or throw. Catch errors instead of expecting confirmation flows.
23
+
24
+ ## Methods
25
+
26
+ All server-targeting methods accept `{ hosts?: string[] }` (default: all configured hosts; unknown names throw before anything runs).
27
+
28
+ | Method | Returns | CLI equivalent |
29
+ | --- | --- | --- |
30
+ | `setup(options?)` | `void` | `cata deploy:setup` |
31
+ | `deploy(options?)` | `void` | `cata deploy` |
32
+ | `rollback({ hosts?, release? })` | `void` | `cata rollback` |
33
+ | `task(name, options?)` | `TaskOutput[]` (`{ host, output }`) | `cata task <name>` |
34
+ | `status(options?)` | `HostStatus[]` | `cata status --json` |
35
+ | `listReleases(options?)` | `{ name, current, releases }[]` | `cata list:releases --json` |
36
+ | `listRevisions({ hosts?, limit? })` | `{ name, revisions }[]` | `cata list:revisions --json` |
37
+ | `listTasks()` | `{ pipeline, extra }` of `{ name, description }` | `cata list:tasks --json` |
38
+ | `pipeline()` | `string[]` | `cata pipeline --json` |
39
+
40
+ Behavior notes:
41
+
42
+ - `deploy()` generates a fresh release name on every call, runs the full pipeline, applies the CLI's auto-rollback on failure, then rethrows. Hosts must be initialized first (`setup()`), otherwise it throws.
43
+ - `task()` runs against the **currently deployed release** and returns what the task wrote through its `logger`, one entry per host. Display tasks (`pm2:logs`, `pm2:list`, …) report their result this way; output may contain ANSI codes. Tasks built from queued `run()` commands return an empty `output`.
44
+ - `status()` never throws on SSH failure; the affected host entry gets an `error` field instead. Data returned by recipes' `onStatus` hooks is merged into each host entry.
45
+
46
+ ## Events
47
+
48
+ `Catapult` is an `EventEmitter`. `deploy()` and `task()` emit:
49
+
50
+ - `task:start`, `task:done` — `{ host, task }`
51
+ - `task:error` — `{ host, task, error }`
52
+ - `host:done` — `{ host, release }` (deploy only)
53
+
54
+ ```typescript
55
+ catapult.on('host:done', ({ host, release }) => notify(`${host} → ${release}`))
56
+ ```
57
+
58
+ ## Silent mode
59
+
60
+ By default Catapult logs progress to stdout like the CLI. For programs that rely on events and return values only:
61
+
62
+ ```typescript
63
+ import { Verbose } from '@catapultjs/deploy/enums'
64
+
65
+ const catapult = new Catapult({ verbose: Verbose.SILENT, hosts: […] })
66
+ ```
67
+
68
+ ## Lower-level functions
69
+
70
+ When the class is too coarse, the primitives are exported from `@catapultjs/deploy` and take an explicit `DeployContext` + `Host`: `deployHost()`, `rollbackHost()`, `initializeHost()`, `isHostSetup()`, `getCurrentRelease()`, `getReleaseNames()`, `getRevisions()`, `collectHostStatus()`, `runTask(name, ctx, host, { logger? })`.
@@ -0,0 +1,160 @@
1
+ # Catapult CLI reference
2
+
3
+ The CLI is `cata`, invoked via `npx cata <command>`. Full docs: https://catapultjs.com/guide/cli
4
+
5
+ ## Config loading
6
+
7
+ `version` and `init` work without a config file. All other commands load `deploy.ts`, `deploy.config.ts`, `deploy.js`, or `deploy.config.js` from the current directory, or use `--config <path>` / `-c <path>` to specify another file.
8
+
9
+ ## Global options
10
+
11
+ | Option | Description |
12
+ | --- | --- |
13
+ | `--config <path>`, `-c <path>` | Load a specific deploy config file |
14
+ | `--host <name>`, `-H <name>` | Target a specific host (available on all host-aware commands) |
15
+ | `--help` | Show help for the current command |
16
+
17
+ ## Commands
18
+
19
+ ### `deploy:setup` (alias: `setup`)
20
+
21
+ Creates the remote directory structure and runs recipe setup hooks. Must be run once before the first deploy.
22
+
23
+ ```bash
24
+ npx cata deploy:setup
25
+ npx cata setup --host production
26
+ npx cata setup -H staging
27
+ ```
28
+
29
+ ### `deploy` (alias: `dep`)
30
+
31
+ Runs the full deployment pipeline. Prompts to run `deploy:setup` if the host is not initialized.
32
+
33
+ | Option | Description |
34
+ | --- | --- |
35
+ | `--branch <name>`, `-b <name>` | Override the branch for this deployment |
36
+ | `-v`, `-vv`, `-vvv` | Increase verbosity |
37
+
38
+ ```bash
39
+ npx cata deploy
40
+ npx cata deploy --host production
41
+ npx cata deploy --branch feature/my-feature
42
+ npx cata dep -H staging -b develop
43
+ npx cata deploy --config deploy.staging.ts
44
+ ```
45
+
46
+ ### `rollback`
47
+
48
+ Restores the previous release. With `--interactive`, prompts to choose the target release. Automatic rollback also triggers after a failure past `deploy:publish`.
49
+
50
+ | Option | Description |
51
+ | --- | --- |
52
+ | `--interactive`, `-i` | Choose the target release interactively |
53
+
54
+ ```bash
55
+ npx cata rollback
56
+ npx cata rollback --interactive
57
+ npx cata rollback -i -H production
58
+ ```
59
+
60
+ ### `status`
61
+
62
+ Shows current release, healthcheck, runtime versions, revision metadata, and lock state. With `--json`, targets all hosts by default and writes aggregated JSON to stdout (errors to stderr).
63
+
64
+ | Option | Description |
65
+ | --- | --- |
66
+ | `--json` | Output result as JSON |
67
+
68
+ ```bash
69
+ npx cata status
70
+ npx cata status --host production
71
+ npx cata status --json
72
+ ```
73
+
74
+ ### `task <task-name>`
75
+
76
+ Runs a registered task against the current release. Requires an existing release on each target host. Use `list:tasks` first to see available task names.
77
+
78
+ | Option | Description |
79
+ | --- | --- |
80
+ | `-v`, `-vv`, `-vvv` | Increase verbosity |
81
+
82
+ ```bash
83
+ npx cata list:tasks
84
+ npx cata task deploy:build
85
+ npx cata task deploy:unlock
86
+ npx cata task deploy:restart -H production
87
+ npx cata task deploy:healthcheck -vv
88
+ ```
89
+
90
+ ### `run "<command>"`
91
+
92
+ Executes a shell command on the selected hosts over SSH. Quote the command string.
93
+
94
+ ```bash
95
+ npx cata run "pm2 list"
96
+ npx cata run "pm2 reload all" --host production
97
+ npx cata run "ls -lah current" -H staging
98
+ ```
99
+
100
+ ### `ssh`
101
+
102
+ Opens an interactive SSH session in `deployPath` on the selected host. Prompts to choose when several hosts are configured and `--host` is omitted.
103
+
104
+ ```bash
105
+ npx cata ssh
106
+ npx cata ssh --host production
107
+ ```
108
+
109
+ ### `list:releases`
110
+
111
+ Lists all releases on the target hosts, marking the current one.
112
+
113
+ ```bash
114
+ npx cata list:releases
115
+ npx cata list:releases --host production
116
+ npx cata list:releases --json
117
+ ```
118
+
119
+ ### `list:revisions`
120
+
121
+ Shows the last 10 logged revisions from `.catapult/revisions.log`.
122
+
123
+ ```bash
124
+ npx cata list:revisions
125
+ npx cata list:revisions --host production
126
+ npx cata list:revisions --json
127
+ ```
128
+
129
+ ### `list:tasks`
130
+
131
+ Lists all pipeline tasks and extra registered tasks from the loaded config.
132
+
133
+ ```bash
134
+ npx cata list:tasks
135
+ ```
136
+
137
+ ### `pipeline`
138
+
139
+ Shows the current deployment pipeline task order.
140
+
141
+ ```bash
142
+ npx cata pipeline
143
+ npx cata pipeline --json
144
+ ```
145
+
146
+ ### `init`
147
+
148
+ Generates a starter `deploy.ts` in the current directory. No config required.
149
+
150
+ ```bash
151
+ npx cata init
152
+ ```
153
+
154
+ ### `version`
155
+
156
+ Prints the installed `@catapultjs/deploy` version. No config required.
157
+
158
+ ```bash
159
+ npx cata version
160
+ ```
@@ -0,0 +1,194 @@
1
+ # Writing a Catapult deploy config
2
+
3
+ The config file is auto-detected as `deploy.ts`, `deploy.js`, `deploy.config.ts` or `deploy.config.js` at the project root (override with `cata --config <path>`). It must default-export `defineConfig()`. Full reference: https://catapultjs.com/guide/api
4
+
5
+ ## Before creating a config
6
+
7
+ If the user asks to create or configure a deployment and the repo/prompt does not provide enough information, ask for the missing deployment facts before writing files. Do not guess real production values such as SSH hostnames, deploy paths, domains, branches, or healthcheck URLs.
8
+
9
+ Use project inspection first:
10
+
11
+ - `package.json` scripts and dependencies identify the stack and package manager.
12
+ - Framework config files identify variants: `next.config.*` (`standalone` vs `export`), `nuxt.config.*`, `astro.config.*`, `vite.config.*`, `adonisrc.ts`, `nest-cli.json`.
13
+ - Existing `ecosystem.config.cjs` means `recipes/pm2` is likely wanted.
14
+ - Existing `.env.example`, `storage/`, `uploads/`, `public/`, or app-specific folders can suggest shared paths, but confirm anything destructive or production-specific.
15
+
16
+ Ask a compact set of questions for missing values:
17
+
18
+ 1. Which environment and server? Need host name, SSH target (`user@host`), and absolute `deployPath`.
19
+ 2. How should code reach the server? Use default SCP when uploading `source_path` is enough; choose `git` when the server can access the repository; choose `rsync` when rsync-based sync/delete behavior is preferred.
20
+ 3. Which branch or source path should deploy?
21
+ 4. Should PM2 be configured? If yes, confirm app name, start entry, port, and whether to create `ecosystem.config.cjs`.
22
+ 5. Is there a healthcheck URL and any extra shared files or directories?
23
+
24
+ When the user answers, generate `deploy.ts` with the selected recipes and put `set(...)` calls before the recipe imports they configure.
25
+
26
+ ```typescript
27
+ import { defineConfig } from '@catapultjs/deploy'
28
+ import '@catapultjs/deploy/recipes/git'
29
+ import '@catapultjs/deploy/recipes/pm2'
30
+
31
+ export default defineConfig({
32
+ hosts: [
33
+ {
34
+ name: 'production',
35
+ ssh: 'deploy@example.com',
36
+ deployPath: '/home/deploy/myapp',
37
+ branch: 'main',
38
+ },
39
+ ],
40
+ })
41
+ ```
42
+
43
+ ## Code delivery
44
+
45
+ Catapult registers a default `deploy:update_code` task that uploads `source_path` via SCP. Recipes can override that task when a different delivery mechanism is needed. Pick one delivery mode:
46
+
47
+ | Recipe | Delivery | Use for |
48
+ | --- | --- | --- |
49
+ | default `deploy:update_code` | Uploads `source_path` via SCP | Simple local upload, static recipes that set `source_path` |
50
+ | `recipes/git` | Clones the repo on the server (bare mirror in `.catapult/repo`) | Server can reach the repo, build on server |
51
+ | `recipes/rsync` | Pushes a local directory into the release | Local builds, no repo access from server |
52
+ | `recipes/adonisjs_local` | Builds AdonisJS locally, uploads the artifact | AdonisJS without building on the server |
53
+ | `recipes/vitepress` | Builds locally, uploads the static output | VitePress static sites |
54
+ | custom `task('deploy:update_code', …)` | Whatever you implement | Anything else |
55
+
56
+ Avoid combining providers that override `deploy:update_code` (`git`, `rsync`, `adonisjs_local`, `vitepress`, custom) unless intentionally replacing task behavior.
57
+
58
+ ## Picking recipes by stack
59
+
60
+ Inspect the project (`package.json`, lock file, config files) before choosing:
61
+
62
+ - **Next.js**: `nextjs` for standalone server builds on the server (pair with `git` or `rsync`); `nextjs_static` for static export built locally and uploaded from `./out/.` using default SCP unless `rsync` is imported.
63
+ - **Nuxt**: `nuxt` for server builds on the server (pair with `git` or `rsync`); `nuxt_static` for local static generation uploaded from `./.output/public/.` using default SCP unless `rsync` is imported.
64
+ - **Astro**: `astro` for standalone server builds on the server (pair with `git` or `rsync`); `astro_static` for static sites built locally and uploaded from `./dist/.` using default SCP unless `rsync` is imported.
65
+ - **TanStack Start**: `tanstack` for server builds on the server (pair with `git` or `rsync`); configure Vite with Nitro `node-server`.
66
+ - **NestJS**: `nestjs` wires standard install/build on the server (pair with `git` or `rsync`).
67
+ - **AdonisJS**: `adonisjs` (installs and builds on the server, exposes `ace:*` migration tasks; pair with `git` or `rsync`) or `adonisjs_local` (build locally, upload).
68
+ - **VitePress**: `vitepress` builds locally and uploads the static output.
69
+ - **Process manager**: add `pm2` if the app runs under PM2 (`ecosystem.config.cjs` expected in the project). It wires restart after publish and adds `pm2:*` tasks plus a status report.
70
+ - **Extras**: `directus` (DB migrations and schema snapshots), `redis` (`redis:db:flush*` tasks, configure with `set('redis_db', n)`).
71
+
72
+ Recipes register their tasks at import time: imports must appear before or inside the config file, never lazily.
73
+
74
+ ## Config options
75
+
76
+ | Option | Type | Notes |
77
+ | --- | --- | --- |
78
+ | `hosts` | `Host[]` | Required |
79
+ | `keepReleases?` | `number` | Default `5` |
80
+ | `repository?` | `string` | Auto-detected from git origin |
81
+ | `packageManager?` | `PackageManager` | Auto-detected from lock file |
82
+ | `hooks?` | `Hooks` | See below |
83
+ | `verbose?` | `Verbose` | From `@catapultjs/deploy/enums`: `SILENT`, `NORMAL`, `TRACE` (default), `DEBUG` |
84
+
85
+ ## Host options
86
+
87
+ | Option | Type | Notes |
88
+ | --- | --- | --- |
89
+ | `name` | `string` | Identifier used by `--host` / `hosts:` selectors |
90
+ | `ssh` | `string \| SshConfig` | `'user@host'` or `{ user, host, port? }` |
91
+ | `deployPath` | `string` | Absolute path on the server |
92
+ | `branch?` | `string \| { name, ask }` | `ask: true` prompts at deploy time (CLI only) |
93
+ | `healthcheck?` | `{ url?, retries?, delayMs? }` | Curl check after publish; the `deploy:healthcheck` task is removed automatically when no host defines a `url` |
94
+ | `bin?` | `Record<string, string>` | Per-host binary paths, e.g. `{ node: '/home/deploy/.nvm/versions/node/v24.0.0/bin/node' }`. Needed when binaries are not on the non-interactive SSH PATH (nvm setups) |
95
+
96
+ Multiple environments are just multiple hosts (`production`, `staging`); target one with `cata deploy -H staging`.
97
+
98
+ ## Lifecycle hooks
99
+
100
+ All optional, all `(ctx: { host?, hosts?, error? }) => Promise<void>`:
101
+
102
+ ```typescript
103
+ export default defineConfig({
104
+ hosts: […],
105
+ hooks: {
106
+ beforeDeploy: async ({ hosts }) => {}, // once, before all hosts
107
+ beforeHostDeploy: async ({ host }) => {}, // per host
108
+ afterHostDeploy: async ({ host }) => {}, // per host, even on failure
109
+ afterFailure: async ({ hosts, error }) => {}, // on error, before rethrow
110
+ afterDeploy: async ({ hosts }) => {}, // once, after success
111
+ },
112
+ })
113
+ ```
114
+
115
+ ## Pipeline customisation
116
+
117
+ The config file is a regular module: pipeline and store calls work alongside `defineConfig()`.
118
+
119
+ Default pipeline order: `deploy:lock` → `deploy:release` → `deploy:update_code` → `deploy:shared` → `deploy:publish` → `deploy:log_revision` → `deploy:healthcheck` → `deploy:unlock` → `deploy:cleanup`. Inspect with `cata pipeline`. Reference: https://catapultjs.com/guide/pipeline
120
+
121
+ Three built-in tasks are registered but NOT inserted by default. Insert them instead of writing custom install/build/test tasks:
122
+
123
+ ```typescript
124
+ import { after } from '@catapultjs/deploy'
125
+
126
+ after('deploy:update_code', 'deploy:install') // package manager install
127
+ after('deploy:install', 'deploy:build') // pm run build
128
+ after('deploy:build', 'deploy:test') // pm run test
129
+ ```
130
+
131
+ Pipeline functions: `after(existing, task)`, `before(existing, task)`, `remove(name)`, `inPipeline(name)` to place a task relative to optional steps, `setPipeline([…])` to replace the whole sequence. Insertion deduplicates: re-adding a task moves it, the last position wins.
132
+
133
+ ```typescript
134
+ import { set, after, inPipeline, remove, task, run, cd } from '@catapultjs/deploy'
135
+
136
+ set('writable_dirs', ['logs', 'tmp/uploads']) // created under shared/ at setup
137
+ set('shared_files', ['.env']) // touched under shared/ at setup
138
+ set('rsync_source_path', './dist') // rsync recipe option
139
+
140
+ task('app:warmup', () => {
141
+ cd('{{current_path}}')
142
+ run('curl -s localhost:3333/health')
143
+ })
144
+
145
+ // healthcheck is removed automatically when no host defines one
146
+ if (inPipeline('deploy:healthcheck')) {
147
+ after('deploy:healthcheck', 'app:warmup')
148
+ } else {
149
+ after('deploy:publish', 'app:warmup')
150
+ }
151
+ ```
152
+
153
+ Custom tasks can also be async functions receiving the `TaskContext` (e.g. `fetch` a Slack webhook with the `release` name). Run any registered task manually with `cata task <name>`.
154
+
155
+ ### Rewriting the whole pipeline
156
+
157
+ `setPipeline()` replaces the sequence entirely, for full control over the order:
158
+
159
+ ```typescript
160
+ import { defineConfig, setPipeline } from '@catapultjs/deploy'
161
+ import '@catapultjs/deploy/recipes/git'
162
+ import '@catapultjs/deploy/recipes/pm2'
163
+
164
+ setPipeline([
165
+ 'deploy:lock',
166
+ 'deploy:release',
167
+ 'deploy:update_code',
168
+ 'deploy:install',
169
+ 'deploy:build',
170
+ 'deploy:shared',
171
+ 'deploy:publish',
172
+ 'pm2:startOrReload',
173
+ 'deploy:unlock',
174
+ 'deploy:cleanup',
175
+ ])
176
+
177
+ export default defineConfig({ hosts: […] })
178
+ ```
179
+
180
+ Rules:
181
+
182
+ - Call it AFTER the recipe imports: recipes run their own `after()`/`before()` insertions at import time, and `setPipeline()` must have the last word.
183
+ - Every name must be a registered task (built-in or from an imported recipe), otherwise the deploy fails at that step with "Task not found".
184
+ - Keep `deploy:lock`/`deploy:unlock` unless concurrent deploys are acceptable, and `deploy:log_revision` if `list:revisions` and revision metadata matter.
185
+ - Omitting `deploy:cleanup` disables release pruning (`keepReleases` has no effect then).
186
+
187
+ ## Checklist
188
+
189
+ 1. `defineConfig()` is the default export.
190
+ 2. Delivery mode is intentional: default SCP, or one overriding provider such as `git`, `rsync`, `adonisjs_local`, `vitepress`, or a custom `deploy:update_code`.
191
+ 3. Recipe store options (`set(…)`) match the imported recipes.
192
+ 4. Healthcheck URLs point to an endpoint reachable from the server itself.
193
+ 5. Pin the package version in `package.json` (the API is still in beta).
194
+ 6. Verify with `cata pipeline` (task order) and `cata list:tasks` (registered tasks), then `cata deploy:setup` before the first deploy.
@@ -0,0 +1,99 @@
1
+ # GitHub Actions with Catapult
2
+
3
+ Use the official `catapultjs/deploy-action` to run Catapult inside GitHub Actions. The action handles SSH setup, detects the package manager from the lockfile, installs dependencies, and runs the requested Catapult command.
4
+
5
+ Full docs: https://catapultjs.com/guide/ci-cd
6
+
7
+ ## Minimal workflow
8
+
9
+ ```yaml
10
+ name: Deploy
11
+
12
+ on:
13
+ workflow_dispatch:
14
+
15
+ jobs:
16
+ deploy:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - uses: catapultjs/deploy-action@v0.5.0
22
+ with:
23
+ command: deploy
24
+ config: deploy.ts
25
+ private-key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
26
+ known-hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
27
+ version: latest
28
+ ```
29
+
30
+ ## Workflow with a custom runtime
31
+
32
+ Add `actions/setup-node` (or pnpm/bun setup) before the action when the project needs a specific Node version.
33
+
34
+ ```yaml
35
+ - uses: actions/setup-node@v4
36
+ with:
37
+ node-version: 24
38
+ cache: npm
39
+
40
+ - uses: catapultjs/deploy-action@v0.5.0
41
+ with:
42
+ command: deploy
43
+ config: deploy.ts
44
+ private-key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
45
+ known-hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
46
+ version: latest
47
+ working-directory: .
48
+ ```
49
+
50
+ ## Action inputs
51
+
52
+ | Name | Default | Description |
53
+ | --- | --- | --- |
54
+ | `command` | `deploy` | Catapult command to run (`deploy`, `rollback`, `task <name>`, etc.) |
55
+ | `config` | — | Path to the deploy config file |
56
+ | `args` | — | Extra CLI args, one per line |
57
+ | `package-manager` | auto-detected | `npm`, `pnpm`, `yarn`, or `bun` |
58
+ | `version` | `latest` | Version of `@catapultjs/deploy` to install |
59
+ | `working-directory` | `.` | Working directory relative to the repository root |
60
+
61
+ ## SSH inputs
62
+
63
+ | Input | Required | Description |
64
+ | --- | --- | --- |
65
+ | `private-key` | Yes | SSH private key added to `ssh-agent` |
66
+ | `known-hosts` | Recommended | Content written to `~/.ssh/known_hosts` |
67
+ | `ssh-config` | No | Content written to `~/.ssh/config` |
68
+ | `insecure-ignore-host-key` | No | Disables strict host key checking |
69
+
70
+ ## Reading sensitive config from environment variables
71
+
72
+ Keep SSH host and path out of `deploy.ts` by reading them from env vars, then passing them from the workflow:
73
+
74
+ ```ts
75
+ // deploy.ts
76
+ const ssh = process.env.DEPLOY_SSH
77
+ const deployPath = process.env.DEPLOY_PATH
78
+ if (!ssh) throw new Error('Missing DEPLOY_SSH')
79
+ if (!deployPath) throw new Error('Missing DEPLOY_PATH')
80
+
81
+ export default defineConfig({
82
+ hosts: [{ name: 'production', ssh, deployPath, branch: process.env.DEPLOY_BRANCH || 'main' }],
83
+ })
84
+ ```
85
+
86
+ ```yaml
87
+ - uses: catapultjs/deploy-action@v0.5.0
88
+ with:
89
+ command: deploy
90
+ private-key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
91
+ known-hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
92
+ env:
93
+ DEPLOY_SSH: ${{ secrets.DEPLOY_SSH }}
94
+ DEPLOY_PATH: ${{ vars.DEPLOY_PATH }}
95
+ DEPLOY_BRANCH: main
96
+ DEPLOY_USER: ${{ github.actor }}
97
+ ```
98
+
99
+ Use GitHub **Secrets** for sensitive values (SSH key, host string) and **Variables** for non-sensitive ones (deploy path). `DEPLOY_USER` is picked up by `deploy:log_revision` when `git config user.name` is unavailable on the runner.
@@ -0,0 +1,115 @@
1
+ # Writing a Catapult recipe
2
+
3
+ A recipe is a JavaScript/TypeScript module that registers tasks and inserts them into the deployment pipeline as a side effect of being imported. Users activate it with a bare import in their `deploy.ts`:
4
+
5
+ ```typescript
6
+ import './recipes/my-recipe.ts'
7
+ ```
8
+
9
+ Full docs: https://catapultjs.com/guide/creating-recipes
10
+
11
+ ## Conventions
12
+
13
+ - Namespace every task with a recipe prefix: `my-recipe:build`, not `build`.
14
+ - Call `desc('…')` immediately before each `task()` so the task shows a description in `cata list:tasks`.
15
+ - Tasks that queue remote commands use the DSL (`cd()`, `run()`); tasks that need command output or conditionals use `ssh()` directly with `set -e` and `q()` for quoting.
16
+ - Resolve binaries with `bin('node')` instead of hardcoding, so users can override paths per host via `host.bin`.
17
+ - Expose recipe options through the store (`get()`/`set()`), never through module-level mutable state.
18
+
19
+ ## Basic structure
20
+
21
+ ```typescript
22
+ import { type TaskContext, task, desc, cd, run, after, bin } from '@catapultjs/deploy'
23
+ import { ssh, q } from '@catapultjs/deploy/utils'
24
+
25
+ // TypeScript: register task names for autocompletion
26
+ declare module '@catapultjs/deploy' {
27
+ interface TaskRegistry {
28
+ 'my-recipe:build': true
29
+ }
30
+ }
31
+
32
+ desc('Builds the application in the release directory')
33
+ task('my-recipe:build', () => {
34
+ cd('{{release_path}}')
35
+ run(`${bin('node')} --run build`)
36
+ })
37
+
38
+ // Insert into the pipeline (deduplicated: re-adding moves the task)
39
+ after('deploy:update_code', 'my-recipe:build')
40
+ ```
41
+
42
+ Pipeline functions: `after(existing, newTask)`, `before(existing, newTask)`, `remove(name)`, `inPipeline(name)`, `getPipeline()`, `setPipeline(tasks)`.
43
+
44
+ Default pipeline: `deploy:lock`, `deploy:release`, `deploy:update_code`, `deploy:shared`, `deploy:publish`, `deploy:log_revision`, `deploy:healthcheck`, `deploy:unlock`, `deploy:cleanup`. Recipes may override a built-in task by registering the same name (e.g. `git`/`rsync` recipes provide `deploy:update_code`).
45
+
46
+ Use `inPipeline()` to attach relative to optional steps (`deploy:healthcheck` is removed when no host defines one). Insertion deduplicates: re-adding a task moves it, the last position wins.
47
+
48
+ Three tasks are registered but not inserted: `deploy:install` (package manager install), `deploy:build` (`pm run build`), `deploy:test` (`pm run test`). Prefer inserting or overriding them over inventing new install/build tasks, like the `adonisjs` and `nuxt` recipes do.
49
+
50
+ ## Task context and raw SSH
51
+
52
+ The task function receives a `TaskContext`: `{ host, paths, config, release, logger }`. Use it for output capture or host-specific logic:
53
+
54
+ ```typescript
55
+ desc('Shows service logs')
56
+ task('my-recipe:logs', async ({ host, paths, logger }: TaskContext) => {
57
+ const { stdout } = await ssh(host, `set -e\ncd ${q(paths.current)}\nmy-service logs --tail 50`)
58
+ logger.log(stdout.trim())
59
+ })
60
+ ```
61
+
62
+ Always write display output through `ctx.logger` (not `console.log`): the programmatic API captures it and returns it from `catapult.task()`.
63
+
64
+ `paths` fields: `base`, `releases`, `release`, `current`, `shared`, `cataConfig`, `repo`, `builder`, `lock`.
65
+
66
+ Template variables in `cd()`/`run()` strings: `{{release_path}}`, `{{current_path}}`, `{{shared_path}}`, `{{releases_path}}`, `{{base_path}}`, `{{release}}`.
67
+
68
+ Other DSL helpers: `local(command, { cwd? })` runs on the local machine, `upload(localPath, remotePath)` / `download(remotePath, localPath)` transfer over SCP, `isVerbose(level)` checks verbosity.
69
+
70
+ ## Hooks
71
+
72
+ `onSetup()` runs during `cata deploy:setup`, after base directories exist:
73
+
74
+ ```typescript
75
+ import { onSetup } from '@catapultjs/deploy'
76
+ import { ssh, q, getPaths } from '@catapultjs/deploy/utils'
77
+
78
+ onSetup(async (ctx, host, logger) => {
79
+ const paths = getPaths(host.deployPath, ctx.release)
80
+ await ssh(host, `set -e\nmkdir -p ${q(paths.shared + '/storage')}`)
81
+ logger.step(host.name, 'storage ready')
82
+ })
83
+ ```
84
+
85
+ `onStatus()` runs during `cata status`. Return an object: each key becomes an aligned line in text mode and is merged into the host entry of `status --json` and `catapult.status()`. Output written via the `logger` argument only appears in text mode.
86
+
87
+ ```typescript
88
+ import { onStatus } from '@catapultjs/deploy'
89
+
90
+ onStatus(async (_ctx, host) => {
91
+ const { stdout } = await ssh(host, `set +e\nmy-service --version || true`)
92
+ return { 'my-service': stdout.trim() || 'unavailable' }
93
+ })
94
+ ```
95
+
96
+ ## Configurable options
97
+
98
+ ```typescript
99
+ import { task, run, get } from '@catapultjs/deploy'
100
+
101
+ task('my-recipe:sync', () => {
102
+ const excludes = get<string[]>('my_recipe_excludes', [])
103
+ run(`rsync ${excludes.map((e) => `--exclude=${e}`).join(' ')} …`)
104
+ })
105
+ ```
106
+
107
+ Users set values in `deploy.ts` with `set('my_recipe_excludes', […])` before or after the import. Two store keys have built-in meaning during setup: `writable_dirs` (string[], created under `shared/`) and `shared_files` (string[], touched under `shared/`).
108
+
109
+ ## Checklist before shipping
110
+
111
+ 1. Tasks namespaced and described with `desc()`.
112
+ 2. `TaskRegistry` augmented for TypeScript users.
113
+ 3. Remote commands quoted with `q()` and prefixed with `set -e` (or `set +e` when failure is expected).
114
+ 4. Display tasks write to `ctx.logger`, status hooks return data objects.
115
+ 5. Reusable recipes can be contributed to the `contrib/` directory of https://github.com/catapultjs/deploy via pull request.