trik-run
v0.1.1
Published
A universal task runner with smart caching and file-based task discovery
Readme
trik
A universal task runner with smart caching and file-based task discovery. Built for Bun.
Tasks live in a .trik/ directory as plain TypeScript files — no config files, no plugins, just code.
Quick Start
bun install -g trik
trik init # creates .trik/ with an example task
trik hello # runs the exampleDefining Tasks
Tasks are TypeScript files inside .trik/. Each file exports a default class whose @Task()-decorated methods are procedures. Calling trik <name> runs the default() procedure.
// .trik/build.ts
export class Build {
@Task()
async compile() {
await $`tsc`;
}
@Task()
async lint() {
await $`eslint src/`;
}
@Task()
async test() {
await Promise.all([this.compile(), this.lint()]);
await $`vitest run`;
}
@Task()
async default() {
await this.test();
}
}
export default Build;Run specific procedures with task:proc syntax:
trik build # runs default()
trik build:compile # runs compile()
trik build:lint # runs lint()Function-based Tasks
For simple single-step tasks, export a plain async function:
// .trik/clean.ts
export default async function clean() {
await $`rm -rf dist`;
}Caching
@SuccessCache
Skip a procedure if it succeeded last time:
@SuccessCache()
async install() {
await $`npm ci`;
}@FunctionCache + FileCache
Re-run only when input files change:
@FunctionCache(() => FileCache("compile", {
inputs: ["src/**/*.ts"],
outputs: ["dist/"],
}))
async compile() {
await $`tsc`;
}CacheGate Combinators
Combine multiple cache conditions:
@FunctionCache(() => CacheGate.all([
FileCache("build", { inputs: ["src/**/*.ts"] }),
PreviousSuccessCache("install"),
]))
async build() {
// ...
})CacheGate.all([...])— skip if ALL assertors passCacheGate.any([...])— skip if ANY assertor passesCacheGate.not(assertor)— negate an assertor
Custom Assertor
@FunctionCache(() => async (ctx) => {
return ctx.cache["my-task"]?.["my-proc"]?.status === "success";
})
async deploy() {
// ...
}Shell Commands
The $ tagged template literal runs shell commands:
await $`echo hello`;
await $`npm run build`;
// Capture output
const { stdout } = await $`git rev-parse HEAD`.capture();
// Quiet mode — suppress all output
await $`some-command`.quiet();
// Per-command shell override
await $.withShell("pwsh")`Write-Host "hello"`;
// Project-wide default shell
$.setShell("pwsh");Supported shells: "sh", "cmd", "pwsh", "nu", custom string[], or false for direct execution.
Task DAG
@Task() methods deduplicate automatically — calling the same method twice returns the same Promise. Express dependencies naturally:
export class Ci {
@Task() async lint() { await $`eslint .`; }
@Task() async typecheck() { await $`tsc --noEmit`; }
@Task() async test() { await $`vitest run`; }
@Task()
async default() {
// lint + typecheck in parallel, then test
await Promise.all([this.lint(), this.typecheck()]);
await this.test();
}
}Cross-file Task References
Reference tasks across .trik/ directories (useful in monorepos):
const build = await trik.task("build");
await build.default();
// Discover all matching instances across packages
const allBuilds = await trik.tasks("build");
await Promise.all(allBuilds.map(b => b.default()));Context
Ctx provides runtime info inside any task:
Ctx.env // process.env
Ctx.os // process.platform
Ctx.args // positional args after --
Ctx.flags // parsed --key=value flags
Ctx.git // Promise<{ branch, sha, isDirty }>CLI Reference
trik <task> Run a task (calls default())
trik <task>:<proc> Run a specific procedure
trik <task> -- --flag Pass flags/args (Ctx.flags, Ctx.args)
trik list List all tasks
trik list <task> List procedures in a task
trik init Initialize .trik/ in current directory
trik clean Delete the task cache file
trik help Show help
Options:
-v, --verbose Show all logs
-f, --force Ignore cache, re-run everything
-n, --dry-run Show what would run without executing
-w, --watch Re-run on file changes
-j <n> Max concurrent procedures
--silent Suppress all output
--log <level> Set log level (default: 3)Task Config
Export a config object for metadata:
export const config: TaskConfig = {
name: "deploy",
description: "Deploy to production",
scope: "root", // only runnable from workspace root
cwd: "workspace-root", // chdir before execution
cache: "previous-success", // or FileCache config, or custom function
};
export default async function deploy() {
await $`npm run deploy`;
}Monorepo Support
trik searches for .trik/ directories both upward and downward from the current directory. Place .trik/ in each package and run tasks from anywhere:
monorepo/
├── packages/
│ ├── app/
│ │ └── .trik/
│ │ └── build.ts
│ └── lib/
│ └── .trik/
│ └── build.ts
└── .trik/
└── ci.tstrik ci from the root runs the top-level task. Tasks can reference sub-package tasks via trik.task("build").
License
MIT
