Skip to content

Creating a Recipe

A recipe is a regular TypeScript file that registers tasks and inserts them into the pipeline upon import.

Basic structure

typescript
import { task, cd, run, after } from '@catapultjs/deploy'

task('my-recipe:build', () => {
  cd('{{release_path}}')
  run('npm ci')
  run('npm run build')
})

after('deploy:update_code', 'my-recipe:build')

Importing the file is enough to activate it:

typescript
import './recipes/my-recipe'

Setup hook

Use onSetup() to run server-side initialization during cata deploy:setup (e.g. create shared directories):

typescript
import { onSetup } from '@catapultjs/deploy'
import { ssh, q, getPaths } from '@catapultjs/deploy/utils'

onSetup(async (ctx, host) => {
  const paths = getPaths(host.deployPath, ctx.release)
  await ssh(
    host,
    `
    set -e
    mkdir -p ${q(paths.shared + '/storage')}
    if [ ! -f ${q(paths.shared + '/.env')} ]; then
      touch ${q(paths.shared + '/.env')}
    fi
  `
  )
})

Status hook

Use onStatus() to display additional information during cata status (e.g. process manager version, queue state):

typescript
import { onStatus } from '@catapultjs/deploy'
import { ssh } from '@catapultjs/deploy/utils'

onStatus(async (_ctx, host) => {
  const { stdout } = await ssh(host, `set +e\nmy-service --version || true`)
  console.log(`my-service ${stdout.trim() || 'unavailable'}`)
})

Configuration: set() / get()

Expose configurable options via set() / get() so users can customize the recipe without modifying it:

typescript
import { task, run, get } from '@catapultjs/deploy'

task('my-recipe:deploy', () => {
  const excludes = get<string[]>('my_recipe_excludes', [])
  run(`rsync --exclude=${excludes.join(' --exclude=')} ...`)
})

Users configure it in their deploy.ts:

typescript
import { set } from '@catapultjs/deploy'
import './recipes/my-recipe'

set('my_recipe_excludes', ['.git', 'node_modules'])

Binaries: bin()

Use bin() to let users override binary paths (useful for nvm/fnm environments):

typescript
import { task, cd, run, bin } from '@catapultjs/deploy'

task('my-recipe:build', () => {
  cd('{{release_path}}')
  run(`${bin('node')} my-script.js`)
})

Users override the path via:

typescript
import { set } from '@catapultjs/deploy'

set('bin/node', '/home/deploy/.nvm/versions/node/v22.14.0/bin/node')

TypeScript: extending TaskRegistry

To get type-safe task names, extend the TaskRegistry interface:

typescript
import type {} from '@catapultjs/deploy'

declare module '@catapultjs/deploy' {
  interface TaskRegistry {
    'my-recipe:build': true
    'my-recipe:deploy': true
  }
}