defineTask

Defines a reusable unit of work with environment requirements and parameters.

Import

import { defineTask } from '@pokit/core';

Signature

// Exec task - runs a shell command
function defineTask<TEnv, TParams, TEnvWriter>(config: {
  label: string;
  shortLabel?: string;
  env?: TEnv;
  params?: TParams;
  envWriter?: TEnvWriter;
  retry?: RetryConfig;
  exec: ExecInput | ((ctx: TaskContext) => ExecInput);
}): ExecTaskConfig;

// Run task - custom logic
function defineTask<TEnv, TParams, TEnvWriter, TReturn>(config: {
  label: string;
  shortLabel?: string;
  env?: TEnv;
  params?: TParams;
  envWriter?: TEnvWriter;
  retry?: RetryConfig;
  run: (runner: Runner, ctx: TaskContext) => Promise<TReturn> | TReturn;
}): RunTaskConfig;

Configuration

PropertyTypeDescription
labelstringHuman-readable label
shortLabelstringShort label for tabs (defaults to first word of exec)
envEnv | Env[]Environment(s) to resolve before execution
paramsz.ZodTypeZod schema for task parameters
envWriterEnvEnvironment for writing (enables ctx.writeEnvs)
retryRetryConfigRetry configuration for failed executions
execstring | FunctionShell command to execute
runFunctionCustom execution logic

RetryConfig

type RetryConfig = {
  maxAttempts: number; // Retry attempts (not including initial)
  delay?: number; // Base delay in ms (default: 1000)
  backoff?: BackoffStrategy; // Default: 'fixed'
  maxDelay?: number; // Cap for backoff growth
};

type BackoffStrategy = 'fixed' | 'linear' | 'exponential';

Backoff strategies:

  • fixed: Same delay between each retry
  • linear: Delay increases linearly (delay * attempt)
  • exponential: Delay doubles each attempt (delay * 2^attempt)

TaskContext

type TaskContext<TEnvs, TParams, TWriteEnvs, TContext> = {
  context: TContext; // Inherited from parent command
  cwd: string; // Project root
  envs: TEnvs; // Resolved environment variables
  params: TParams; // Validated parameters
  extraArgs: string[]; // Remaining CLI args
  reporter: TaskReporter; // Event emission
  writeEnvs: TWriteEnvs; // Write function (if envWriter defined)
};

Examples

Simple Exec Task

import { defineTask } from '@pokit/core';

export const build = defineTask({
  label: 'Build project',
  exec: 'npm run build',
});

Exec Task with Parameters

import { z } from 'zod';
import { defineTask } from '@pokit/core';

export const migrate = defineTask({
  label: 'Run migrations',
  params: z.object({
    dryRun: z.boolean().default(false),
  }),
  exec: (ctx) => `prisma migrate deploy ${ctx.params.dryRun ? '--dry-run' : ''}`,
});

// Usage in command
await r.run(migrate, { dryRun: true });

Task with Environment

import { defineTask, defineEnv, defineEnvResolver } from '@pokit/core';
import { z } from 'zod';

// Define resolver
const secretsResolver = defineEnvResolver({
  requiredContext: z.object({ env: z.enum(['dev', 'staging', 'prod']) }),
  availableVars: ['DATABASE_URL', 'API_KEY'] as const,
  resolve: async (keys, ctx) => {
    // Fetch secrets based on environment
    return { DATABASE_URL: '...', API_KEY: '...' };
  },
});

// Define env selecting specific vars
const dbEnv = defineEnv({
  resolver: secretsResolver,
  vars: ['DATABASE_URL'],
});

// Task using the env
export const migrate = defineTask({
  label: 'Run migrations',
  env: dbEnv,
  exec: (ctx) => {
    // ctx.envs.DATABASE_URL is available
    return 'prisma migrate deploy';
  },
});

Run Task with Custom Logic

import { defineTask } from '@pokit/core';

export const setup = defineTask({
  label: 'Setup development',
  run: async (r, ctx) => {
    ctx.reporter.info('Setting up...');

    await r.exec('npm install');
    await r.exec('npm run db:setup');

    ctx.reporter.success('Ready!');
  },
});

Task with Retry

import { defineTask } from '@pokit/core';

// Retry with exponential backoff
export const flakyApiCall = defineTask({
  label: 'Call flaky API',
  retry: {
    maxAttempts: 3,
    delay: 1000,
    backoff: 'exponential',
    maxDelay: 10000,
  },
  exec: 'curl https://flaky-api.example.com/endpoint',
});

// Run task with retry
export const deployWithRetry = defineTask({
  label: 'Deploy to CDN',
  retry: { maxAttempts: 2, delay: 5000 },
  run: async (r, ctx) => {
    ctx.reporter.info('Deploying...');
    await r.exec('aws s3 sync ./dist s3://my-bucket');
    ctx.reporter.success('Deployed!');
  },
});

When used in parallel, retries exhaust before parallel mode rules apply:

// In fail-fast mode, flakyApiCall will retry up to 3 times
// before being considered a failure
await r.parallel([r.run(flakyApiCall), r.run(stableTask)], { mode: 'fail-fast' });

Task with Return Value

import { defineTask } from '@pokit/core';
import { z } from 'zod';

export const getVersion = defineTask({
  label: 'Get version',
  run: async (r, ctx) => {
    // Return value is typed
    return '1.0.0';
  },
});

// Usage
const version = await r.run(getVersion); // string

Multiple Environments

const dbEnv = defineEnv({ resolver, vars: ['DATABASE_URL'] });
const apiEnv = defineEnv({ resolver, vars: ['API_KEY'] });

export const deploy = defineTask({
  label: 'Deploy',
  env: [dbEnv, apiEnv], // Both resolved
  exec: (ctx) => {
    // ctx.envs.DATABASE_URL
    // ctx.envs.API_KEY
    return 'deploy';
  },
});

Writing Environment Variables

const secretsEnv = defineEnv({
  resolver: secretsResolver, // Resolver must implement write()
  vars: ['NEW_SECRET'],
});

export const bootstrap = defineTask({
  label: 'Bootstrap secrets',
  envWriter: secretsEnv,
  run: async (r, ctx) => {
    const secret = generateSecret();
    await ctx.writeEnvs({ NEW_SECRET: secret });
  },
});

Using Tasks

Tasks are executed via r.run():

// In a command
run: async (r) => {
  // Execute directly
  await r.run(buildTask);

  // With parameters
  await r.run(migrateTask, { dryRun: true });

  // In tabs
  await r.tabs([r.run(devServerTask), r.run(watcherTask)]);
};

Type Safety

The type system enforces context requirements:

// Task requires { env: string } context
const deployTask = defineTask({
  env: envThatRequires({ env: z.enum(['dev', 'prod']) }),
  exec: 'deploy',
});

// Command provides matching context - OK
defineCommand({
  context: {
    env: { from: 'flag', schema: z.enum(['dev', 'prod']) },
  },
  run: async (r) => {
    await r.run(deployTask); // TypeScript OK
  },
});

// Command missing context - TypeScript error
defineCommand({
  run: async (r) => {
    await r.run(deployTask); // Error: context doesn't satisfy requirements
  },
});