Skip to content

Task Conventions

Every OHF project should feel the same to work with. An engineer should be able to clone any repository, run mise run setup, then mise run dev, and be productive immediately — without reading a bespoke README or figuring out which package manager commands to chain together.

This convention follows GitHub's Scripts to Rule Them All pattern: a script/ directory at the repo root with predictable names that every project implements. Mise en Place wraps these scripts as tasks, providing runtime management, dependency ordering, and a uniform mise run <task> interface.

AreaRequirementWhy
Scriptsscript/ directory in every repoPortable, language-agnostic entry points. Work without mise installed.
Task RunnerMise en PlaceWraps scripts with runtime management and depends. Declared in .mise.toml at the root.
CI LayerCI calls scripts directlyGitHub Actions runs script/cibuild. Same scripts locally and in CI.

Mise is the single entry point for engineers. Running mise run lists every available task in the project — no need to dig through package.json, script/, or CI config to find what's available.

LayerLocationRoleExample
Entry point.mise.tomlGlobal task registry. mise run lists everythingmise run dev, mise run setup
Lifecycle scriptsscript/Standardized, portable scripts across all reposscript/setup, script/server, script/cibuild
CI scriptspackage.jsonThin scripts consumed by GitHub Actions"lint", "build", "typecheck"
Git hooks.husky/Automated checks on commitpre-commit → runs lint-staged

Every project must have a script/ directory with these files. Optional scripts should be added when relevant.

ScriptPurposeRequired
script/setupFirst-time project setup: install deps, generate .env, run migrationsYes
script/updateUpdate project after git pull (deps, migrations, codegen)Yes
script/serverStart the local development serverYes
script/testRun the test suiteYes
script/cibuildRun the full CI suite (lint, format, typecheck, test, build)Yes
script/consoleOpen an interactive REPL or shellOptional
script/bootstrapInstall/update dependencies only (called by setup and update)Optional

Mise tasks in .mise.toml unify everything under one interface. Some tasks call into the script/ directory, others call package.json scripts directly for convenience. Engineers always interact with mise run <task>.

Mise TaskCallsNotes
setupscript/setupFirst-time setup
updatescript/updateSync after pulling changes
devscript/serverAlias matches JS ecosystem convention (pnpm run dev)
testscript/testDirect mapping
ciscript/cibuildDirect mapping
consolescript/consoleDirect mapping
buildConvenience task; part of cibuild
lintConvenience task; part of cibuild
formatConvenience task; part of cibuild
typecheckConvenience task; part of cibuild
checkRuns all quality checks (lint, format, typecheck) without build

Convenience tasks like lint, format, and check don't need a dedicated script — they are defined directly in .mise.toml for developer ergonomics.

  • Scripts must be executable (chmod +x script/*)
  • Scripts should be bash (#!/usr/bin/env bash) with set -e at the top
  • Scripts should be idempotent — safe to run multiple times
  • script/setup should call script/bootstrap (or inline the same logic) before doing first-time config
  • script/update should call script/bootstrap to refresh dependencies

Sub-tasks use colon-separated names for variants of a base task:

  • lint:fix — run linter with auto-fix
  • format:check — check formatting without writing changes
  • dev:host — start dev server accessible on the network
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
script/bootstrap
cp -n .env.example .env || true
pnpm exec prisma migrate dev
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
pnpm install
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
pnpm exec nest start --watch
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
pnpm run test
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
script/bootstrap
pnpm run lint
pnpm run format:check
pnpm run typecheck
pnpm run test
pnpm run build
[tools]
node = "24"
[tasks.setup]
description = "First-time project setup"
run = "script/setup"
[tasks.update]
description = "Update project after git pull"
run = "script/update"
[tasks.dev]
description = "Start development server"
run = "script/server"
[tasks.test]
description = "Run tests"
run = "script/test"
[tasks.ci]
description = "Run full CI suite"
run = "script/cibuild"
[tasks.console]
description = "Open interactive console"
run = "script/console"
# Convenience tasks for day-to-day development
[tasks.install]
description = "Install dependencies"
run = "pnpm install"
[tasks.build]
description = "Build for production"
run = "pnpm run build"
depends = ["install"]
[tasks.lint]
description = "Run ESLint"
run = "pnpm run lint"
depends = ["install"]
[tasks."lint:fix"]
description = "Run ESLint with auto-fix"
run = "pnpm exec eslint . --fix"
depends = ["install"]
[tasks.format]
description = "Format code with Prettier"
run = "pnpm exec prettier --write ."
depends = ["install"]
[tasks."format:check"]
description = "Check formatting"
run = "pnpm run format:check"
depends = ["install"]
[tasks.typecheck]
description = "Run TypeScript type checking"
run = "pnpm run typecheck"
depends = ["install"]
[tasks.check]
description = "Run all quality checks"
depends = ["lint", "format:check", "typecheck"]