A minimal bootstrap scaffold for TypeScript + Python + Rust monorepos.
This repository is intentionally small. It provides the shared project hygiene that most repositories need before application code exists: tool and package manager pinning, strict TypeScript defaults, a modern Python toolchain (uv + Ruff), a Rust toolchain (Cargo + rustfmt + Clippy), formatting, linting, staged-file checks, and CI.
Shared:
- mise config for unified tool management (Node.js, pnpm, uv, AutoCorrect; Rust is opt-in)
- Prettier formatting for docs and config file types (Markdown, JSON, YAML, HTML,
CSS, Vue, GraphQL, and more — see the globs in
package.json) - AutoCorrect CJK copywriting cleanup
- Husky pre-commit and pre-push hooks with lint-staged
- GitHub Actions lint and AI code-review workflows
- Basic
.env.exampleand.gitignorefiles AGENTS.mdfor AI coding agents (CLAUDE.mdreferences it)
TypeScript:
- pnpm workspace over
apps/*andpackages/* - Strict
tsconfig.jsondefaults - Oxlint with type-aware rules
(
oxlint-tsgolint) — one root.oxlintrc.jsonlints the whole monorepo - Oxfmt formatting for the JS/TS family
(Prettier-compatible output, configured in
.oxfmtrc.jsonc)
Python:
- uv workspace under
apps/andpackages/with a single shared lockfile - Ruff linter + formatter configured in the root
pyproject.toml - Python version pinned in
.python-version(uv installs it automatically)
Rust:
- Cargo workspace under
apps/andpackages/with a single sharedCargo.lock - rustfmt formatting (
rustfmt.toml, edition 2024) - Clippy linting, run with
-D warningsso any lint failspnpm run lint - Toolchain opt-in via mise: uncomment
rustinmise.tomlwhen you add your first crate (it pinsrust = "1.93"with the rustfmt + clippy components). Until then the Rust lint/format steps are no-ops, so repos without Rust don't install the toolchain.
- No app framework
- No build tool
- No test runner
- No runtime entrypoint
- No generated source tree
Add those only when a real project needs them.
apps/ # deployable applications (TypeScript, Python, or Rust)
packages/ # shared libraries (TypeScript, Python, or Rust)
TypeScript, Python, and Rust members share the same two directories, and a member can even
belong to more than one ecosystem. pnpm discovers members through the apps/* / packages/*
globs in pnpm-workspace.yaml: a directory joins by having a package.json. Python and Rust
members are instead listed explicitly — Python in [tool.uv.workspace] in the root
pyproject.toml, Rust in [workspace] members in the root Cargo.toml. Both uv and Cargo
require every member-glob match to be a project of their own kind, so a glob like apps/*
would break the moment a member of another ecosystem appears; explicit lists avoid that.
uv init and cargo new maintain their respective member lists for you.
With mise (recommended — installs Node.js, pnpm, uv, and AutoCorrect):
mise run setupActivate mise in your shell so the managed
tools are on PATH. AutoCorrect is managed through mise, and package scripts call
mise run / mise x for those checks.
Package scripts require mise because AutoCorrect runs through the repo-managed
toolchain. If you manage Node.js, pnpm, or uv yourself, still run mise install
so the configured tools are available before running package scripts:
mise install
pnpm install
uv sync --lockedRust is opt-in (see Adding Workspace Members). Once a toolchain is available, Cargo resolves Rust dependencies on the first build — there is no separate install step.
Format files (Oxfmt + Prettier + Ruff + rustfmt + AutoCorrect):
pnpm run formatRun all lint checks (Oxlint + Oxfmt + Prettier, Ruff, rustfmt + Clippy, AutoCorrect):
pnpm run lintRun lint checks for one ecosystem:
pnpm run lint:js
pnpm run lint:py
pnpm run lint:rust
pnpm run lint:textThe Rust steps are no-ops until the first crate exists, so pnpm run lint stays green on a
fresh checkout before any Rust code is added.
Run lint fixes and formatting:
pnpm run lint:fixA TypeScript package:
mkdir -p packages/my-lib && cd packages/my-lib && pnpm initGive it a tsconfig.json extending the root one. The root pnpm run lint already lints
every workspace file with Oxlint (one root .oxlintrc.json, run once for the whole repo);
add a nested .oxlintrc.json with "extends": ["../../.oxlintrc.json"] only when the
member needs different rules, and lint / lint:fix scripts only for extra member-specific
checks — pnpm -r run --if-present lint picks them up.
A Python package:
uv init --package packages/my-tool && uv syncuv init appends the member to [tool.uv.workspace] in the root pyproject.toml
automatically; Ruff settings are inherited from the root, and all members share one
lockfile and virtual environment.
A Rust crate (first enable the opt-in toolchain by uncommenting the rust line in
mise.toml, then mise install):
cargo new packages/my-crate # use --lib for a library, default is a binary
cargo generate-lockfile # create Cargo.lock so the locked lint passes; commit itcargo new appends the crate to [workspace] members in the root Cargo.toml
automatically, and — because the root declares [workspace.package] — the generated
Cargo.toml already inherits shared metadata via edition.workspace = true and
rust-version.workspace = true. rustfmt and Clippy settings are inherited from the root too,
and all members share one Cargo.lock and target/ directory. lint:rust runs Clippy with
--locked, so commit Cargo.lock (CI fails if it is missing or stale). Until the toolchain
is enabled, the Rust lint/format steps stay no-ops and CI never installs Rust.
pre-commitruns lint-staged: Oxlint + Oxfmt on staged JS/TS, Ruff on staged Python, rustfmt on staged Rust, Prettier on staged docs/config files, plus AutoCorrect on all staged files.pre-pushruns Git LFS, sogit-lfsmust be installed.
The hooks need pnpm, uv, and AutoCorrect on PATH (plus rustfmt only when committing Rust).
If a GUI Git client doesn't load your shell profile, provide them via husky's
~/.config/husky/init.sh, e.g. eval "$(mise activate bash --shims)".
Start from this scaffold when you want a clean TypeScript + Python + Rust repository foundation without choosing an application framework up front. Keep the base small, add project-specific tools deliberately, and prefer extending the existing config over replacing it.