Skip to content
/ task Public

A lightweight turborepo like task runner

hackwaly/task

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@hackwaly/task

A lightweight, TypeScript-native task runner inspired by Turborepo. Define your build pipeline with code, not configuration files.

Features

  • πŸš€ TypeScript-first: Define tasks in TypeScript with full type safety
  • πŸ“¦ Dependency management: Automatic task dependency resolution and execution
  • πŸ‘€ Watch mode: File watching with intelligent task re-execution
  • ⚑ Parallel execution: Run independent tasks concurrently
  • 🎯 Persistent tasks: Support for long-running processes (servers, watchers)
  • πŸ”„ Interruptible tasks: Graceful handling of task interruption
  • πŸ“ Input/Output tracking: File-based change detection for efficient rebuilds

Installation

npm install @hackwaly/task
# or
pnpm add @hackwaly/task
# or
yarn add @hackwaly/task

Quick Start

  1. Create a taskfile.js in your project root:
import { configInit } from "@hackwaly/task";

const { defineTask } = configInit(import.meta);

export const build = defineTask({
  name: "build",
  command: "tsc --build",
  inputs: ["src/**/*.ts", "tsconfig.json"],
  outputs: ["dist/**/*.js"],
});

export const test = defineTask({
  name: "test",
  command: "vitest run",
  dependsOn: [build],
});

export const dev = defineTask({
  name: "dev",
  command: "tsc --watch",
  persistent: true,
});

export default build;
  1. Run tasks:
# Run a single task
npx task run build

# Run multiple tasks
npx task run build test

# Run with watch mode
npx task run build --watch

# List available tasks
npx task list

Task Configuration

Tasks are defined using the defineTask function with the following options:

interface TaskConfig {
  name: string;              // Task name (required)
  description?: string;      // Task description for help text
  command?: string | string[] | { program: string; args?: string[] };
  env?: Record<string, string>;  // Environment variables
  cwd?: string;              // Working directory (defaults to taskfile location)
  inputs?: string[];         // Input file patterns (for change detection)
  outputs?: string[];        // Output file patterns
  persistent?: boolean;      // Whether task runs continuously (like servers)
  interruptible?: boolean;   // Whether task can be interrupted safely
  dependsOn?: TaskDef[];     // Task dependencies
}

Command Formats

Commands can be specified in multiple formats:

// String (parsed with shell-like parsing)
command: "tsc --build --verbose"

// Array
command: ["tsc", "--build", "--verbose"]

// Object
command: {
  program: "tsc",
  args: ["--build", "--verbose"]
}

Dependencies

Tasks can depend on other tasks. Dependencies are resolved automatically:

export const generateTypes = defineTask({
  name: "generate-types",
  command: "generate-types src/schema.json",
  outputs: ["src/types.ts"],
});

export const build = defineTask({
  name: "build",
  command: "tsc --build",
  inputs: ["src/**/*.ts"],
  dependsOn: [generateTypes],  // Runs generateTypes first
});

Persistent Tasks

For long-running processes like development servers:

export const server = defineTask({
  name: "server",
  command: "node server.js",
  persistent: true,        // Runs continuously
  interruptible: true,     // Can be stopped gracefully
});

Watch Mode

Watch mode automatically re-runs tasks when their input files change:

npx task run build --watch

Features:

  • Monitors all input patterns defined in tasks
  • Ignores node_modules by default
  • Propagates changes through the dependency graph
  • Only reruns tasks that are affected by changes

Commands

run <tasks...>

Run one or more tasks:

# Single task
npx task run build

# Multiple tasks
npx task run lint test build

# With watch mode
npx task run build --watch

list / ls

List all available tasks:

npx task list

Shows task names and descriptions in a formatted table.

Examples

Basic Build Pipeline

import { configInit } from "@hackwaly/task";

const { defineTask } = configInit(import.meta);

export const lint = defineTask({
  name: "lint",
  description: "Lint TypeScript files",
  command: "eslint src/**/*.ts",
  inputs: ["src/**/*.ts", ".eslintrc.json"],
});

export const typecheck = defineTask({
  name: "typecheck",
  description: "Type check TypeScript files",
  command: "tsc --noEmit",
  inputs: ["src/**/*.ts", "tsconfig.json"],
});

export const build = defineTask({
  name: "build",
  description: "Build the project",
  command: "tsc --build",
  inputs: ["src/**/*.ts", "tsconfig.json"],
  outputs: ["dist/**/*.js"],
  dependsOn: [lint, typecheck],
});

export const test = defineTask({
  name: "test",
  description: "Run tests",
  command: "vitest run",
  dependsOn: [build],
});

Development Workflow

export const generateSchema = defineTask({
  name: "generate-schema",
  command: "generate-schema api.yaml",
  inputs: ["api.yaml"],
  outputs: ["src/generated/schema.ts"],
});

export const dev = defineTask({
  name: "dev",
  description: "Start development server",
  command: "vite dev",
  persistent: true,
  interruptible: true,
  dependsOn: [generateSchema],
});

export const buildWatch = defineTask({
  name: "build:watch",
  description: "Build in watch mode",
  command: "tsc --watch",
  persistent: true,
  dependsOn: [generateSchema],
});

Advanced Usage

Monorepo Support

Each package can have its own taskfile.js:

// packages/ui/taskfile.js
import { configInit } from "@hackwaly/task";

const { defineTask } = configInit(import.meta);

export const build = defineTask({
  name: "build:ui",
  command: "rollup -c",
  inputs: ["src/**/*", "rollup.config.js"],
  outputs: ["dist/**/*"],
});

// packages/app/taskfile.js
import { build as buildUI } from "../ui/taskfile.js";

export const build = defineTask({
  name: "build:app",
  command: "vite build",
  dependsOn: [buildUI],  // Cross-package dependency
});

Custom Task Logic

For complex tasks, you can provide custom logic:

export const customTask = defineTask({
  name: "custom",
  async run(ctx) {
    // Custom async logic
    console.log("Running custom task...");

    // Check if aborted
    if (ctx.abort.aborted) {
      return;
    }

    // Your custom logic here
  },
  inputs: ["src/**/*"],
});

License

MIT

About

A lightweight turborepo like task runner

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published