/skills/fuz-stack/references/task-patterns
  • docs
  • skills
  • fuz-stack
    • Async Patterns
    • Code Generation
    • Common Utilities
    • CSS Patterns
    • Dependency Injection
    • Documentation System
    • File Organization
    • mdz — Strict Markdown Dialect
    • Approved npm Dependencies
    • Path References in Documentation
    • Approved Rust Dependencies
    • Rust Patterns for the Fuz Ecosystem
    • Rust Performance Patterns
    • Rust Spine & Consumer Servers
    • Svelte 5 Patterns
    • Task Patterns
    • Testing Patterns
    • TSDoc Comment Style Guide
    • Twin Implementations (TS ↔ Rust)
    • Type Utilities
    • WASM Patterns for the Fuz Ecosystem
    • Zod Schemas
  • grimoire
  • tools
  • hash

Task Patterns

Gro's task system for project automation in @fuzdev/gro. Tasks are TypeScript modules with a .task.ts suffix exporting a task object with a run function.

Task Interface

interface Task< TArgs = Args, TArgsSchema extends z.ZodType<Args, Args> = z.ZodType<Args, Args>, TReturn = unknown, > { run: (ctx: TaskContext<TArgs>) => TReturn | Promise<TReturn>; summary?: string; Args?: TArgsSchema; }
  • run — entry point, receives TaskContext
  • summary — shown in gro task listing and --help
  • Args — optional Zod schema for CLI arg parsing and validation (see ./zod-schemas)

TArgsSchema and TReturn are rarely customized — tasks are either Task (default args) or Task<Args> (custom Zod-inferred Args type).

Basic task example

// src/lib/greet.task.ts import type {Task} from '@fuzdev/gro'; export const task: Task = { summary: 'greet the user', run: async ({log}) => { log.info('hello!'); }, };

Run with gro greet or gro src/lib/greet.

Task with args

Both the Zod schema (value) and inferred type share the name Args:

// src/lib/greet.task.ts import type {Task} from '@fuzdev/gro'; import {z} from 'zod'; export const Args = z.strictObject({ name: z.string().meta({description: 'who to greet'}).default('world'), }); export type Args = z.infer<typeof Args>; export const task: Task<Args> = { summary: 'greet someone by name', Args, run: async ({args, log}) => { log.info(`hello, ${args.name}!`); }, };

Run with gro greet --name Claude. gro greet --help shows help auto-generated from the Zod schema.

TaskContext

interface TaskContext<TArgs = object> { args: TArgs; config: GroConfig; svelte_config: ParsedSvelteConfig; filer: Filer; log: Logger; timings: Timings; invoke_task: InvokeTask; }
FieldTypePurpose
argsTArgsParsed CLI arguments (validated by Zod if set)
configGroConfigGro configuration (plugins, task_root_dirs, etc)
svelte_configParsedSvelteConfigParsed SvelteKit config (aliases, paths)
filerFilerFilesystem tracker (watches files in dev mode)
logLoggerLogger instance scoped to the task
timingsTimingsPerformance measurement (start/stop timers)
invoke_taskInvokeTaskCall other tasks programmatically

invoke_task

type InvokeTask = (task_name: string, args?: Args, config?: GroConfig) => Promise<void>;

Omitting config passes the current config. Respects the override system: invoke_task('test') runs the user's override if one exists.

export const task: Task = { run: async ({invoke_task}) => { await invoke_task('typecheck'); await invoke_task('test'); await invoke_task('gen', {check: true}); await invoke_task('format', {check: true}); await invoke_task('lint'); }, };

This is the core pattern used by check.task.ts (which adds conditional execution via --no-* flags).

Args Pattern

Conventions

  • Export both Zod schema and inferred type as Args at module level
  • Use z.strictObject() (not z.object())
  • .meta({description: '...'}) for CLI help text
  • .default(...) for defaults — required fields without defaults must be passed via CLI
  • /** @nodocs */ to exclude from docs generation

Positional arguments

_ key for positional arguments (array of strings):

export const Args = z.strictObject({ _: z.array(z.string()).meta({description: 'file patterns to filter'}).default(['.test.']), dir: z.string().meta({description: 'working directory'}).default('src/'), }); export type Args = z.infer<typeof Args>;

Run with: gro test foo bar --dir src/lib/ (positional foo, bar go to _).

Boolean dual flags

--no-* dual flags for opt-out behavior:

export const Args = z.strictObject({ typecheck: z.boolean().meta({description: 'dual of no-typecheck'}).default(true), 'no-typecheck': z.boolean().meta({description: 'opt out of typechecking'}).default(false), test: z.boolean().meta({description: 'dual of no-test'}).default(true), 'no-test': z.boolean().meta({description: 'opt out of running tests'}).default(false), });

gro check --no-test disables testing. --help hides the positive flags when a no-* dual exists, showing only the no-* entry.

Error Handling

TaskError

Known failure with clean message (no stack trace). Use when the message is sufficient for the user to fix the problem:

import {TaskError} from '@fuzdev/gro'; throw new TaskError('Missing required config file: gro.config.ts');

SilentError

Exit with non-zero code when the error is already logged. Primarily internal to invoke_task.ts:

import {SilentError} from '@fuzdev/gro/task.ts'; log.error('Detailed error information...'); throw new SilentError();

When to use which

Error typeStack traceGro logs messageUse when
Regular ErrorYesYesUnexpected failures
TaskErrorNoYesKnown failures with clear message
SilentErrorNoNoAlready logged the error yourself

Task Discovery

Source task files use the .task.ts suffix; the .task.js form is only gro's compiled builtins under gro/dist/, which the task loader also discovers. Gro searches task_root_dirs in order (default: src/lib/, ./, gro/dist/):

src/lib/greet.task.ts -> gro greet src/lib/deploy.task.ts -> gro deploy src/lib/db/migrate.task.ts -> gro db/migrate

gro with no task name or gro some/dir lists all tasks without executing.

Task Override Pattern

Local tasks override Gro builtins with the same name:

  • src/lib/test.task.ts overrides Gro's builtin test task
  • Run the builtin explicitly: gro gro/test

The common pattern wraps the builtin:

import type {Task} from '@fuzdev/gro'; export const task: Task = { summary: 'run tests with custom setup', run: async ({invoke_task, args}) => { // custom setup await invoke_task('gro/test', args); // call the builtin // custom teardown }, };

Task Composition

invoke_task (recommended): Respects overrides, provides logging context, auto-forwards CLI args from -- sections:

await invoke_task('build', {sync: false, gen: false});

Direct import: Bypasses override resolution, tighter coupling:

import {task as test_task} from './test.task.ts'; await test_task.run(ctx);

Args forwarding

CLI args forward to composed tasks via -- separators:

gro check -- gro test --coverage

Forwards --coverage to test when check invokes it. Multiple -- sections can target different sub-tasks.

Quick Reference

ExportTypeImport fromPurpose
TaskInterface@fuzdev/groTask definition (run, summary, Args)
TaskContextInterface@fuzdev/groContext passed to task.run
TaskErrorClass@fuzdev/groKnown failure (no stack trace)
SilentErrorClass@fuzdev/gro/task.tsExit silently (error already logged)
InvokeTaskType@fuzdev/gro/task.ts(task_name, args?, config?) => Promise<void>