Luna is a polyglot monorepo starter built around moonrepo for the task graph, proto for pinned runtimes, and the luna CLI (Rust, Starbase + Clap) for orchestration across all toolchain layers. Each stack brings its own runtime — Bun for JS/TS, Go for Hugo, Python for FastAPI — managed and pinned by Proto. It layers three application stacks: a SolidStart interactive app with SSR and streaming, a Hugo static site for Markdown and templates, and a FastAPI service with Pydantic and Pydantic AI. Shared libraries live under packages/*. Ports, environment files, and moon tasks are covered in the sections and READMEs below.
- 🌙 Moon (documentation) — task orchestration and project graph
- ⚙️ Proto — installs and pins all tools from
.prototools - 🦀 Rust — powers the
lunaCLI (packages/cli); pinned in.prototools
SolidStart on Vite and Nitro is the stack for SPA-style apps that need SSR, client-side fine-grained signals (SolidJS), routing (Solid Router), and streaming responses. Nitro gives you a server runtime alongside the browser bundle, so the same project can grow full-stack APIs and server logic without leaving the framework. Bun is pinned in .prototools as the JS/TS runtime and package manager for this stack.
- 🟢 Bun (documentation) — JS/TS runtime, package manager, and workspace resolver for
apps/appand shared packages - ⚛️ SolidStart (documentation) — full-stack app framework for
apps/app - 🧩 SolidJS (documentation) — reactive UI library
- 🔀 Solid Router — routing for Solid apps
- ⚡ Vite (guide) — dev server and build tooling
- 🔥 Nitro (guide) — server runtime used by SolidStart
- 🎨 Tailwind CSS v4 (documentation) — utility CSS used by
@luna/ds(consumed from the interactive app and the static site pipeline)
The apps/web project is a static-site workflow: Markdown and front matter in src/content/, Go HTML templates in src/layouts/, and the published site in dist/. @luna/ds styles are compiled by the Tailwind CSS v4 CLI into src/assets/css/bundle.css before each build (same design system as apps/app). Go is pinned in .prototools; the Hugo CLI version is pinned in apps/web/go.mod as a go tool (same idea as Python deps living under apps/api). See apps/web/README.md.
- 📰 Hugo (documentation) — static site generator; HTML, RSS, and sitemap from content + templates
- ✍️ Goldmark — CommonMark-compatible Markdown (Hugo’s default renderer)
- 🧩 Go HTML templates — partials, blocks, and
baseoflayouts undersrc/layouts/ - 🎨 Tailwind CSS v4 (documentation) — utility CSS for
@luna/ds(package);@tailwindcss/cliemitssrc/assets/css/bundle.css(same tokens asapps/app) - 🖍️ Chroma — syntax highlighting for fenced code blocks (
[markup.highlight]inhugo.toml)
The apps/api stack centers on FastAPI, Pydantic, and Pydantic AI for a pure backend HTTP API: validation, settings, and agent-style features stay on the server. The same patterns extend to larger deployments (multiple services, workers, or runtimes) when you outgrow a single process; this repo keeps one API project as the starting point.
- 🐍 Python — runtime (version pinned in
.prototools) - 📦 uv — environments and lockfiles
- 🚀 FastAPI — API framework
- 🤖 Pydantic AI — AI agent patterns on the backend
- ✅ Pydantic — schemas and models
- ⚙️ pydantic-settings — environment-driven settings
- 🌐 Uvicorn — ASGI server
- 🧹 OXC (
oxlint+oxfmt) (documentation) — linting and formatting for JS/TS at the repo root - 🟦 TypeScript (documentation) — static typing and project references across workspaces
Run commands from the repository root unless an app README says otherwise.
Install Proto and Moon on your PATH before setup (Moon is only needed until luna is installed; Proto pins moon, rust, bun, python, and go via .prototools).
After moon run cli:install, add ~/.cargo/bin to your PATH so the luna command is found (or use the full path in moon run luna:install below).
One command from the repo root (fresh clone or after luna clean):
moon run luna:installThat runs proto install → cli:build → cli:install → luna install (toolchains, CLI, bun workspaces, web, api). Then:
luna devEquivalent steps (if you prefer to run them separately):
proto install
moon run cli:build
moon run cli:install
luna installWithout luna on PATH yet — stop after cli:install, then:
./target/debug/luna install # or ~/.cargo/bin/luna installSubsequent refreshes: luna install. New pins in .prototools are picked up by proto install (or luna update, which refreshes pins then re-runs install). .moon/toolchains.yml disables javascript.installDependencies and sets bun.installArgs: ["--ignore-scripts"].
Full reset: luna clean, then moon run luna:install again. Each project’s inherited :clean task clears its own outputs; root luna:clean clears repo-root artifacts (node_modules, .venv, target, …); .moon/cache is removed last. Lockfiles are kept (bun.lock, uv.lock, Cargo.lock, go.work / go.sum).
For a full compile of every application project first, run luna build.
Local dev ports (copy .env.example → .env.local to customize): FastAPI / Uvicorn 8000 (common tutorial default — avoids stealing 3000 from the SPA), SolidStart 3000, Hugo static site 3001. Details and CORS URLs are in each app README.
apps/api/— FastAPI + Pydantic AI · READMEapps/app/— SolidStart (SSR, Vite, Nitro) · READMEapps/web/— Hugo + Tailwind v4 +@luna/ds· READMEpackages/cli/—lunaCLI (Rust + Starbase; orchestrates Moon/Proto/Bun commands;outdated/updateacross all toolchains)packages/ds/— design system / Tailwind · README- Entrypoints:
packages/ds/src/tailwind.css(main import),packages/ds/src/components.css,packages/ds/src/layouts.css,packages/ds/src/primitives.css - Modules:
packages/ds/src/{components,layouts,primitives}/*.css(authored as modular CSS) - Patterns: scoped root + nested layers (
@scope+@layer base|variants|patterns); see DS README
- Entrypoints:
packages/ui/— shared Solid UI · READMEpackages/py-demo/— demo Python uv workspace member · README (reference layout for new Python packages)packages/go-demo/— demo Go workspace library (not for production);apps/weblinks it viaworkspace/link.go+replaceingo.mod(same idea as uvworkspace = true)
When updating packages/ds, follow the scoped root + nested layers CSS module pattern (@scope + @layer base|variants|patterns). The DS README is the source of truth: DS CSS patterns.
Moon wires build and dev tasks per project; api:dev depends on api:build (uv sync at the workspace root). luna install runs root uv sync (one shared .venv + uv.lock) and go work sync before luna dev. For step-by-step task graphs (luna build, Uvicorn, SolidStart), follow each workspace README above.
All day-to-day commands go through the luna CLI (packages/cli). It orchestrates Moon, Proto, and Bun directly — there are no root package.json scripts to remember.
luna install # bootstrap workspace
luna clean # :clean per project → moon clean --all → root luna:clean → .moon/cache last
luna dev # moon run :dev --query "projectLayer=application"
luna build # moon run :build --query "projectLayer=application"
luna start # moon run :start --query "projectLayer=application"
luna test # moon run :test --query "projectLayer=application"All build/dev/start/test commands accept an optional project name (luna build app) and --affected flag (luna build --affected).
luna orchestrates quality across all stacks in one command — TS (oxlint/oxfmt/tsc), Python (ruff at root), Rust (cargo clippy/fmt), and Go (hugo config via moon):
luna lint # TS: oxlint, Python: ruff check, Rust: clippy
luna format # TS: oxfmt, Python: ruff format, Rust: cargo fmt
luna typecheck # TS: tsc --build, Go: hugo config
luna check # lint + format:check + typecheck (all stacks)
luna lint --fix # apply fixes (oxlint --fix, ruff --fix, clippy --fix)
luna format --check # check only (oxfmt --list-different, ruff --check, fmt --check)
luna fix # lint:fix + format (all stacks)Pass multiple project:task targets to run them in one invocation:
moon run app:dev api:dev # interactive app + API only (no web)
moon run app:build api:build web:buildUse --query to filter the graph instead of listing every target (same query language as moon query projects):
moon run :dev --query 'project=[app,api]'
moon run :typecheck --query "projectLayer=library" # shared packages (per-project inherited tasks)
moon query projects --help # filters: --id, --language, --layer, etc.Examples for shared packages (see each package README for inherited tasks):
moon run ds:typecheck
moon run ui:typecheck-
Tool/version pins:
.prototools -
Workspace manifest + dev dependencies:
package.json— Bun workspaces and dev tool versions only; all orchestration goes throughluna -
Python workspace (uv):
pyproject.toml— virtual root, shareduv.lock+.venv; membersapps/api,packages/py-demo; shared ruff rules and dev deps in root; per-member run/test/build config in each member'spyproject.tomland moon tasks -
Go workspace:
go.work— membersapps/web,packages/go-demo -
Rust workspace:
Cargo.toml— memberpackages/cli -
Moon workspace graph + VCS:
.moon/workspace.yml -
Moon toolchains:
.moon/toolchains.yml—javascript.installDependencies: false,bun.installArgs: ["--ignore-scripts"]; bootstrap withluna install. After changing JS deps, runbun installorluna install. -
Shared TS app tasks:
.moon/tasks/ts-app.yml(language: typescript,layer: application,stack: frontend) -
Shared TS lib tasks:
.moon/tasks/ts-lib.yml(language: typescript,layer: library) -
Shared Python app tasks:
.moon/tasks/py-api.yml(language: python,layer: application,stack: backend) -
Shared Python lib tasks:
.moon/tasks/py-lib.yml(language: python,layer: library) -
Shared Go web tasks:
.moon/tasks/go-web.yml(language: go,stack: frontend) —go tool hugofromapps/web/go.mod -
Shared Go lib tasks:
.moon/tasks/go-lib.yml(language: go,layer: library) -
Root workspace tasks:
moon.yml—luna:install(first-time bootstrap),luna:clean(repo-root outputs only;.moon/cachedropped last byluna clean) -
Shared Rust bin tasks:
.moon/tasks/rs-bin.yml(language: rust) — build/install/test/lint/format-check/clean viacargo -
Root moon config:
moon.yml -
TypeScript project graph:
tsconfig.json -
Shared TypeScript options:
tsconfig.options.json -
OXC formatter config:
.oxfmtrc.json -
OXC linter config:
.oxlintrc.json -
Bun install security:
bunfig.toml—minimumReleaseAge(14 days), trusted-package excludes -
npm registry security:
.npmrc—ignore-scripts,allow-git=none,min-release-age=14
Repo-wide outdated checks and upgrades go through the luna CLI so every toolchain stays in sync.
The luna CLI (packages/cli, built with Rust + Starbase) probes every toolchain — proto, Rust / Cargo (cargo outdated), Bun, Python / uv (lockfile dry-run), and Go per go.mod — in parallel behind a Luna-owned status panel, then prints one grouped table with toolchain divider rows (in the order proto → rust → bun → uv → go; up-to-date toolchains are omitted). Hugo-style modules (tool in go.mod, local code only under workspace/) use go list -m -u on tool lines; other modules use direct go list -m -u (not the full workspace graph). Set LUNA_FULL_GRAPH=1 to scan or update with go list -m -u all / go get -u all.
luna outdated exits 0 (findings are informational) and always overwrites a snapshot at .cache/outdated.snapshot.json. luna update is snapshot-first: it reuses that snapshot when it is < 8h old and still valid (matching repo root, policy flags, schema, and manifest fingerprints), otherwise it runs the same probe phase as a preflight. It then updates only the toolchains marked outdated (others show as skipped), and re-runs workspace bootstrap (bun install, uv sync / api:build, go work sync, web:setup). After luna update, review diffs and run luna check before committing.
luna outdated # parallel probes → grouped table → snapshot (exits 0)
luna update # reuse/preflight snapshot, update only outdated toolchains, then bootstrap
luna update --major # also apply major-version bumps where supportedluna update and luna install apply a 14-day minimum release age so recently published packages are not pulled in immediately:
| Ecosystem | Mechanism |
|---|---|
| Bun | bunfig.toml minimumReleaseAge + --minimum-release-age on bun install |
| npm (via Bun registry reads) | .npmrc min-release-age=14, ignore-scripts=true, allow-git=none |
| uv | uv lock --upgrade --exclude-newer <date> (date computed at runtime, 14 days ago) |
| Cargo / Go | No native cooldown; use optional Socket Firewall (below) |
Luna CLI environment variables (optional; copy from .env.example or export in your shell):
| Variable | Default | Purpose |
|---|---|---|
LUNA_FIREWALL |
unset | When non-empty, wrap uv and cargo installs/updates with Socket Firewall (sfw). Same as --firewall. Install sfw globally first (npm i -g sfw). Bun and Go are not wrapped. |
LUNA_MIN_RELEASE_AGE |
14 |
Minimum package age in days before luna install / luna update apply cooldowns (Bun --minimum-release-age, uv --exclude-newer, plus bunfig.toml / .npmrc). |
LUNA_FULL_GRAPH |
unset | When non-empty, Go outdated / update use go list -m -u all and go get -u all instead of the faster tool-only or direct-deps path. Slower; use when you need the full workspace module graph. |
Socket Firewall (sfw) — opt-in real-time blocking of confirmed malware during package fetches (docs):
# Install once (global)
npm i -g sfw # or: bun add -g sfw
# Enable for a single command
luna --firewall update
luna --firewall install
# Or persist for the shell session
export LUNA_FIREWALL=1
luna updateWhen --firewall / LUNA_FIREWALL is set, luna wraps uv and cargo commands with sfw (supported by Socket Firewall Free). Bun and Go are not sfw-wrapped (Bun uses native cooldown + --ignore-scripts; Go has no release-age gate). CI runs socketdev/action@v1 with mode: firewall before moon ci.
luna outdated and luna update render a grouped table whose Release Age column shows how many days ago the Newest/Latest versions were published (looked up from the npm and PyPI registries). Newest is shown green when it is at least LUNA_MIN_RELEASE_AGE days old (installable under the cooldown) and red when younger; Latest is yellow when it is exactly one major version ahead of Current. A footer explains the policy and one-off bypass tips. Packages held back by the cooldown are noted as blocked in the update results.
pnpm (not used in this repo): if you add pnpm, set minimumReleaseAge: 20160 (minutes) in pnpm-workspace.yaml — see npm security best practices.
Per stack (manual add / remove) — use these when you are changing one project, not refreshing everything:
- Toolchain (proto) — edit
.prototools, thenluna installorproto installindividually. Removing a tool line drops it from proto’s install set for this repo. - Hugo (
apps/web) —luna updatebumps thetoolline withgo get -u=patch(orgo get -tool …@latestwithluna update --major). To pin a specific release manually:cd apps/webthengo get -tool github.com/gohugoio/hugo@vX.Y.Z(updatesapps/web/go.mod/go.sum). SetLUNA_FULL_GRAPH=1to restore slow full-graphgo get -u allon tool-only modules. - Bun / workspaces — from the repo root, add to a workspace with
bun add <pkg> --cwd apps/app(or--cwd packages/ui, etc.); usebun add -d <pkg> --cwd <path>for devDependencies. Remove withbun remove <pkg> --cwd <path>. Root-only deps:bun add <pkg>at the root. - Python (uv workspace) — from the repo root:
uv add <package> --package api/uv remove <package> --package api(updates memberpyproject.tomland rootuv.lock); sync withuv syncorluna install. Add a new member in rootpyproject.toml[tool.uv.workspace]and use[tool.uv.sources] name = { workspace = true }for inter-member deps.
Install the CLI (~/.cargo/bin must be on your PATH):
moon run cli:build
moon run cli:install
luna --helpTo run without installing, build and invoke the debug binary ( target/ is gitignored):
moon run cli:build --force
./target/debug/luna --helpAnother process is still bound to the port (often after stopping a dev server).
lsof -i :8000 # API (Uvicorn) default; :3000 app; :3001 webNote the PID from lsof, then:
kill -9 <PID>If you know the command line, you can narrow cleanup:
ps aux | grep -E "(uvicorn|vite)" | grep -v grep
pkill -f "uvicorn main:app" # API (adjust if your entrypoint differs)