Skip to content
Development
Skill

/litestar-build

Auto-activate for uv build, hatch build, PyApp, PYAPP_*, wheel assets, GitHub release matrices, cargo-zigbuild, or python-build-standalone. Not for runtime deployment.

From plugin
litestar
1431 skills1 agent1 hook
Install
$ npx -y skills add litestar-org/litestar-skills --skill litestar-build --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/litestar-build

Context preview

The summary Claude sees to decide when to auto-load this skill.

Auto-activate for uv build, hatch build, PyApp, PYAPP_*, wheel assets, GitHub release matrices, cargo-zigbuild, or python-build-standalone. Not for runtime deployment.

SKILL.md

litestar-build.SKILL.md
name: litestar-build
description: "Auto-activate for uv build, hatch build, PyApp, PYAPP_*, wheel assets, GitHub release matrices, cargo-zigbuild, or python-build-standalone. Not for runtime deployment."

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](../litestar-deployment/SKILL.md) — 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/` or `app/`)
  • 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:

1. `pip install`ed into a container (litestar-deployment). 2. Wrapped in a **PyApp** binary (`dist/<app>`, `dist/app-x86_64-linux-gnu`) for zero-dep distribution. 3. 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](#example-projects) and [litestar-fullstack](#example-projects) 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`):

export default defineConfig({
  build: {
    outDir: path.resolve(__dirname, "../../py/app/server/static/web"),  // ← inside src/py/app/ (the Python package)
    emptyOutDir: true,
  },
  plugins: [
    litestar({
      bundleDir: path.resolve(__dirname, "../../py/app/server/static/web"),
      hotFile: path.resolve(__dirname, "../../py/app/server/static/web/hot"),
    }),
  ],
})

**litestar-fullstack-inertia** — the litestar-vite plugin resolves `bundle_dir` relative to the project root, and Python settings point it at a package-internal path:

# app/lib/settings.py
return ViteConfig(
    paths=PathConfig(
        root=BASE_DIR.parent,
        bundle_dir=Path("app/domain/web/public"),  # ← inside app/ (the Python package)
        resource_dir=Path("resources"),
    ),
)

**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.

Quick Reference

| Topic | Reference | Key Commands | | --- | --- | --- | | Wheel build + asset bundling | [references/wheel-assets.md](references/wheel-assets.md) | `uv build --wheel`, `[tool.hatch.build.targets.wheel.force-include]`, `ignore-vcs = true` | | PyApp — simple (hatch-binary) | [references/pyapp-simple.md](references/pyapp-simple.md) | `uv run hatch build --target binary` | | PyApp — advanced (offline + custom install dir) | [references/pyapp-advanced.md](references/pyapp-advanced.md) | `tools/bundler.py build`, `cargo zigbuild` | | GitHub Actions CI (test matrix) | [references/github-ci.md](references/github-ci.md) | `astral-sh/setup-uv@v7`, `oven-sh/setup-bun@v2`, composite actions | | GitHub Actions release | [references/github-release.md](references/github-release.md) | matrix onefiles, `cargo-zigbuild`, `gh release create` | | Upgrading Python / PyApp | [references/upgrading.md](references/upgrading.md) | Files to edit in sync |

Canonical Makefile Build Graph

Every Litestar app with bundled assets has some variant of this:

.PHONY: install build-assets build-wheel build-onefile

install:                          ## Install Python + JS deps
	@uv sync --all-groups
	@cd src/js/web && bun install --frozen-lockfile

build-assets:                     ## Build frontend into the Python package
	@uv run app assets install
	@uv run app assets build

build-wheel: build-assets         ## Self-contained Python wheel
	@uv build --wheel

build-onefile: build-wheel        ## Single-file PyApp binary
	@./tools/scripts/build-onefile-package.sh

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:

js-build-all: js-build-web js-build-offline-report
build-wheel: generate-licenses build-templates js-
Read more
Ships withlitestar

Opinionated, first-party agent skills, plugins, subagents, slash commands, and MCP servers for the Litestar framework and its ecosystem — publishable to every major AI agent and IDE from a single repo.

Get the whole plugin

Other skills on litestar.