litestar-org/litestar-skills/litestar-build
Claude Code and agent skill by litestar-org/litestar-skills
- community
- ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
Checking that the SKILL.md file is still in the repository.
Use when working with uv build, hatch build, PyApp, PYAPP_*, wheel assets, GitHub release matrices, cargo-zigbuild, or python-build-standalone. Not for runtime deployment.
Install
npx vskill@latest install litestar-org/litestar-skills/litestar-buildSemi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard
SKILL.md
litestar-build
Build-side packaging patterns for Litestar applications: how to produce a self-contained wheel that embeds the Vite/Bun frontend, how to wrap that wheel in a PyApp onefile binary, and how to wire the whole pipeline into GitHub Actions CI and releases.
This skill is the counterpart to litestar-deployment — build is about producing artifacts, deployment is about running them.
The Core Idea: One Wheel, Self-Contained
A Litestar wheel is the single source of truth for a release. It contains:
- Python code (
src/py/app/orapp/) - SQL migrations, Jinja templates, INI configs
- The built Vite/Bun frontend bundle (JS, CSS, HTML, images)
- Email templates rendered from React/MJX to static HTML
Once produced, that wheel can be:
pip installed into a container (litestar-deployment).- Wrapped in a PyApp binary (
dist/<app>,dist/app-x86_64-linux-gnu) for zero-dep distribution. - Uploaded to PyPI or a private index.
All three paths assume the wheel is already complete — no bun run build happens at deploy/install time.
Why bundle assets into the wheel (and not serve from a CDN)
| Property | Bundled wheel | External CDN | |
|---|---|---|---|
| Deploy artifacts | 1 (.whl or binary) | 2+ (wheel + CDN upload) | |
| Version alignment | Atomic — API and UI lock-step | Easy to skew; rollback is painful | |
| PyApp onefile | Required — the binary embeds the wheel | Not possible — binary can't fetch CDN URLs at install time | |
| Offline/air-gapped | Works | Doesn't | |
| Dev server startup | Instant (files on disk next to package) | Fine | |
| Frontend-only deploys | Rebuild + redeploy wheel | Push to CDN only |
For most Litestar apps that ship as a product (CLIs, internal tools, enterprise installers), bundled-in-wheel is correct. Projects like litestar-fullstack-inertia and litestar-fullstack all bundle.
Why litestar-vite configs look the way they do in reference apps
This is the piece most developers miss. The Vite/litestar-vite configs in the reference apps are deliberately set up so the Vite output lands inside the Python package directory — because that's what makes the wheel pick them up automatically.
litestar-fullstack (src/js/web/vite.config.ts):
litestar-fullstack-inertia — in litestar-vite v0.30+, Python ViteConfig(paths=PathConfig(bundle_dir=...)) is the single source of truth and writes .litestar.json on startup/CLI invocation so litestar({ input: [...] }) in vite.config.ts inherits bundleDir, hotFile, and assetUrl automatically (and litestar assets build exports OpenAPI/routes metadata, runs TypeGenConfig codegen once with LITESTAR_VITE_SKIP_BUILD_TYPEGEN=1, installs node_modules if missing, and runs the Vite build):
Advanced reference pattern — same approach: Vite and the offline-report build write to src/py/<app>/server/public/ and src/py/<app>/domain/web/static/reports/offline/, both under the package root.
Contrast with a naïve vite build that writes to ./dist/ at the repo root: those files are outside the package directory listed in [tool.hatch.build.targets.wheel] packages = [...], so Hatchling silently drops them. The wheel ships without a frontend.
Rule: Vite's `outDir` and litestar-vite's `bundle_dir` must point inside one of the Python packages that Hatchling is told to include. Everything else flows from that.
Code Style Rules
- Emit all compiled frontend bundles (
PathConfig.bundle_dir/ Vitebuild.outDir) inside a Python package directory included by Hatchling (app/...orsrc/py/<pkg>/...). - Choose exactly one Hatchling asset inclusion strategy (
[tool.hatch.build.targets.wheel.force-include]ORignore-vcs = true) — never combine both. - Guard custom Hatchling build hooks (
BuildHookInterface.initialize) withif version == "editable":(warning tosys.stderr, creating the placeholder directory, and returning early when compiled assets do not exist yet) souv syncsucceeds before frontend assets are built, and excludetools/buildfrommypyandpyright. - Run asset compilation, static template generation, and license collection before
uv build --wheel --clear. - Treat
PYAPP_*configuration variables (PYAPP_PROJECT_PATH,PYAPP_PYTHON_VERSION,PYAPP_DISTRIBUTION_EMBED,PYAPP_DISTRIBUTION_VARIANT_GIL,PYAPP_SKIP_INSTALL) as compile-time inputs tocargo build/cargo zigbuild; at runtime, usePYAPP_INSTALL_DIR_<PROJECT_NAME>(where<PROJECT_NAME>is the uppercasedPYAPP_PROJECT_NAMEwith-replaced by_) to override the extraction directory and inspectos.getenv("PYAPP")("1"or path when built withPYAPP_PASS_LOCATION=1) to detect PyApp execution. - Use
cargo zigbuild --release --target <arch>-unknown-linux-gnu.2.17withBZIP2_SYS_STATIC=1andLZMA_API_STATIC=1when building portable Linux PyApp binaries, and ensure both$(uv tool dir --bin)($HOME/.local/bin) and$HOME/.cargo/binare onPATH/$GITHUB_PATHso the build never silently falls back tocargo build --releaseand binds to the runner's hostglibc.
Quick Reference
| Topic | Reference | Key Commands | |
|---|---|---|---|
| Wheel build + asset bundling | references/wheel-assets.md | uv build --wheel --clear, [tool.hatch.build.targets.wheel.force-include], ignore-vcs = true | |
| PyApp — simple (hatch-binary) | references/pyapp-simple.md | uv run hatch build --target binary | |
| PyApp — advanced (offline + custom install dir) | references/pyapp-advanced.md | tools/bundler.py build, cargo zigbuild | |
| GitHub Actions CI (test matrix) | references/github-ci.md | astral-sh/setup-uv@v7, oven-sh/setup-bun@v2, composite actions | |
| GitHub Actions release | references/github-release.md | matrix onefiles, cargo-zigbuild, gh release create | |
| Upgrading Python / PyApp | references/upgrading.md | Files to edit in sync |
Canonical Makefile Build Graph
Every Litestar app with bundled assets has some variant of this:
The dependency chain is load-bearing: build-onefile depends on build-wheel, which depends on build-assets. Running them out of order produces an empty or broken artifact.
The two-variant story
Real projects have multiple JS build outputs that all need to land in the wheel:
Each js-build-* target emits into a distinct subdirectory of the Python package (src/py/<app>/server/public, src/py/<app>/domain/web/static/reports/offline, etc.). Because they're all inside the package, a single uv build --wheel --clear captures everything.
<workflow>
Workflow
Step 1: Point Vite output inside the Python package
Configure ViteConfig(paths=PathConfig(bundle_dir=Path("app/domain/web/public"))) in Python so .litestar.json bridges the target directory to litestar-vite-plugin. If vite.config.ts overrides build.outDir directly (monorepo or standalone Vite build), set build.outDir and litestar({ bundleDir, hotFile }) to an absolute path inside your Python package (src/py/<pkg>/... or <pkg>/...). Do not let Vite default to ./dist/.
Step 2: Choose a Hatchling bundling strategy
- `force-include` (inertia): List the built-asset directory explicitly under
[tool.hatch.build.targets.wheel.force-include]. Built assets stay.gitignored. Explicit, auditable. - `ignore-vcs = true` (SPA): Tell Hatchling to ignore
.gitignore. All package files ship. Simpler; requires discipline to keep dev junk out of package dirs. - Optional custom Hatchling hook: If you add a
BuildHookInterfacesubclass (e.g.,tools/build/hatch_build.py) to validate or build frontend assets duringuv build, guardinitialize(version, build_data)withif version == "editable":(warning tosys.stderr, creating the placeholder directory, and returning early instead of raisingValueErroror running a frontend build) souv syncsucceeds before frontend assets exist, and excludetools/buildfrommypyandpyright.
See references/wheel-assets.md for full config.
Step 3: Wire Makefile targets
Create install, build-assets, build-wheel. Make the wheel target depend on the asset target. Pass --clear to uv build --wheel --clear so stale wheels in dist/ never get picked up by downstream packaging steps.
Step 4: Add PyApp (if shipping a binary)
Decide which flavor:
- Simple: add
[tool.hatch.build.targets.binary]topyproject.tomland runuv run hatch build --target binary(runtime install location can still be overridden viaPYAPP_INSTALL_DIR_<PROJECT_NAME>). Good when end-users have PyPI access. See pyapp-simple.md. - Advanced: write a
tools/bundler.pythat pre-installs deps into apython-build-standalonearchive, patches PyApp'ssrc/app.rsfor a custom compiled-in default install dir, then runscargo zigbuild. Good for air-gapped distribution or bespoke default install locations. See pyapp-advanced.md.
Step 5: Add GitHub Actions CI
Start with a reusable test.yml that accepts python-version + coverage inputs. Call it from ci.yml across a matrix. Use astral-sh/setup-uv@v7 and oven-sh/setup-bun@v2. See github-ci.md.
For larger projects, factor setup-python and setup-node into .github/actions/ composite actions.
Step 6: Add release workflow
Trigger on v* tags. Run the test matrix first. Then build the wheel once. Then build PyApp onefiles in a per-target matrix (x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, Apple, Windows). Upload to gh release create. See github-release.md.
</workflow>
<guardrails>
Guardrails
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/litestar-build/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Extension points
hookPre/post lifecycle hooks
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/litestar-org/litestar-skills/litestar-build)