Skip to content

Commit fcc238b

Browse files
authored
pkg(workspace): add sphinx-vite-builder, retire gp-sphinx-vite (#29)
A new `sphinx-vite-builder` package consolidates the workspace's Vite story into one place with three orthogonal activation paths sharing one async-subprocess core: a PEP 517 build backend, a hatchling build hook, and a Sphinx extension. Replaces `gp-sphinx-vite` (deleted) and fixes the `0.0.1a15` regression where `gp-furo-theme` published wheels with an empty `static/` tree, leaving docs sites unstyled. The whole product is the wheel-vs-source asymmetry: a `web/` directory triggers strict orchestration with fast-fail diagnostics (`PnpmMissingError`, `NodeModulesInstallError`, `ViteFailedError`, each carrying a copy-pasteable hint); an absent `web/` (the unpacked-sdist case) short-circuits cleanly so wheels published to PyPI need zero toolchain on the consumer side. Errors are self-healing in CI — detected providers (GitHub Actions, CircleCI, Azure Pipelines, GitLab CI) get the right setup recipe inlined into the error message. **New package — three activation paths:** - **PEP 517 backend** at `sphinx_vite_builder.build`. Drop-in for `hatchling.build`; runs `pnpm exec vite build` before delegating wheel/sdist construction. - **Hatchling build hook** at `[tool.hatch.build.hooks.vite]`. Same orchestration via hatchling's plugin system; composes with other hatchling hooks. - **Sphinx extension** registered by `setup()`. One-shot vite build under `sphinx-build`; long-running `vite build --watch` under `sphinx-autobuild` with graceful SIGTERM→SIGKILL teardown via `os.killpg` on the POSIX session group. **Bug fix — `gp-furo-theme` wheels now ship vite-built CSS and JS.** `0.0.1a15` published wheels with an empty `static/` because hatchling's `force-include` rejected the gitignored Vite output; `sphinx-build` then silently skipped the missing entries. The backend now builds assets at release time and hatchling packs them via `[tool.hatch.build] artifacts`. **Workspace migration.** Consumers using `merge_sphinx_config(vite_orchestration=True)` continue to work unchanged — the auto-injection points at `sphinx_vite_builder` now. Power users with raw `extensions = ["gp_sphinx_vite"]` or `gp_sphinx_vite_root = ...` in `conf.py` rename to `sphinx_vite_builder` / `sphinx_vite_builder_root`. **Released as `0.0.1a16.dev4`.** All workspace packages bumped in lockstep; `gp-sphinx-vite` retired (issue #32 tracks the manual PyPI yank). **Cross-repo validation.** Production deploys verified live across the consumer corpus — `cihai.git-pull.com`, `libtmux.git-pull.com`, `tmuxp.git-pull.com`, `vcspull.git-pull.com` and 6 others all serve the new theme assets (curl against `_static/styles/furo-tw.css` returns 200 across the board, fixing the `a15` 404 regression). Breaking changes: - `gp-sphinx-vite` package retired. Workspace consumers via `merge_sphinx_config(vite_orchestration=True)` are unaffected; hand-rolled `extensions = ["gp_sphinx_vite"]` callers must rename to `"sphinx_vite_builder"`. Config keys renamed: `gp_sphinx_vite_{mode,root}` → `sphinx_vite_builder_{mode,root}`. Closes #28. Closes #30 (Phase 2). Closes #31 (Phase 3). Tracks #32 for the manual PyPI yank. See also: - Cross-repo integration: tmux-python/libtmux-mcp#33 - Consumer rollout: cihai/cihai#399, cihai/cihai-cli#348, cihai/unihan-db#364, cihai/unihan-etl#356, vcs-python/vcspull#547, vcs-python/libvcs#526, vcs-python/g#55, git-pull/gp-libs#69, tmux-python/libtmux#666, tmux-python/tmuxp#1037 - maturin (Rust+Cargo analog): https://github.com/PyO3/maturin - sphinx-theme-builder (webpack analog): https://github.com/pradyunsg/sphinx-theme-builder
2 parents 5c7079b + d24e3b0 commit fcc238b

76 files changed

Lines changed: 3624 additions & 1081 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,25 @@ jobs:
4242
print(f"{key}={value}")
4343
PY
4444
45+
# `gp-furo-theme`'s build backend (`sphinx_vite_builder.build`)
46+
# runs `pnpm exec vite build` during sdist + wheel construction.
47+
# Without pnpm + Node the backend fast-fails with PnpmMissingError
48+
# and the release pipeline aborts before publish — exactly the
49+
# invariant we want, but it requires the toolchain to be set up
50+
# first. The backend short-circuits cleanly inside the unpacked
51+
# sdist (no `web/` → assume pre-baked) so end users `pip install`
52+
# without pnpm/Node.
53+
- name: Set up pnpm
54+
uses: pnpm/action-setup@v6
55+
with:
56+
version: 10
57+
58+
- name: Set up Node
59+
uses: actions/setup-node@v6
60+
with:
61+
node-version: 22
62+
cache: pnpm
63+
4564
- name: Install workspace dependencies
4665
run: uv sync --all-packages --all-extras --group dev
4766

.github/workflows/tests.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ on:
77
jobs:
88
qa:
99
runs-on: ubuntu-latest
10+
# `gp-furo-theme`'s build backend (`sphinx_vite_builder.build`) runs
11+
# `pnpm exec vite build` during editable installs, which the qa
12+
# runners can't satisfy (no pnpm/Node). qa's purpose is lint/types/
13+
# tests against Python sources — the rendered theme isn't exercised
14+
# here — so we set the documented escape-hatch env var on the whole
15+
# job. The backend then short-circuits and the install succeeds.
16+
# Test suites that build a Sphinx project with html_theme="gp-furo"
17+
# behave the same as they do on a stock checkout without vite —
18+
# Sphinx silently skips the missing static entries (no -W in qa).
19+
env:
20+
SPHINX_VITE_BUILDER_SKIP: "1"
1021
strategy:
1122
fail-fast: false
1223
matrix:
@@ -75,6 +86,21 @@ jobs:
7586
with:
7687
enable-cache: true
7788

89+
# `gp-furo-theme`'s build backend (`sphinx_vite_builder.build`)
90+
# runs vite during editable install, populating the gitignored
91+
# `static/` tree so the docs build below picks up real CSS / JS.
92+
# The pnpm/Node steps below give the backend a working toolchain.
93+
- name: Set up pnpm
94+
uses: pnpm/action-setup@v6
95+
with:
96+
version: 10
97+
98+
- name: Set up Node
99+
uses: actions/setup-node@v6
100+
with:
101+
node-version: 22
102+
cache: pnpm
103+
78104
- name: Install workspace dependencies
79105
run: uv sync --all-packages --all-extras --group dev
80106

@@ -97,6 +123,25 @@ jobs:
97123
with:
98124
enable-cache: true
99125

126+
# `gp-furo-theme`'s sdist + wheel both go through
127+
# `sphinx_vite_builder.build`, which runs vite. The wheels
128+
# produced here are consumed by the smoke matrix below — they
129+
# MUST contain populated `static/`, otherwise downstream smoke
130+
# installs render unstyled. pnpm + Node satisfy that toolchain
131+
# requirement; the backend's wheel-from-sdist short-circuit
132+
# (no `web/` in unpacked sdist → assume pre-baked) means smoke
133+
# jobs themselves don't need this setup.
134+
- name: Set up pnpm
135+
uses: pnpm/action-setup@v6
136+
with:
137+
version: 10
138+
139+
- name: Set up Node
140+
uses: actions/setup-node@v6
141+
with:
142+
node-version: 22
143+
cache: pnpm
144+
100145
- name: Install workspace dependencies
101146
run: uv sync --all-packages --all-extras --group dev
102147

@@ -135,6 +180,7 @@ jobs:
135180
- sphinx-autodoc-typehints-gp
136181
- sphinx-ux-badges
137182
- sphinx-ux-autodoc-layout
183+
- sphinx-vite-builder
138184
steps:
139185
- uses: actions/checkout@v6
140186

@@ -157,6 +203,12 @@ jobs:
157203

158204
- name: Smoke test root bootstrap install
159205
if: matrix.target == 'root-install'
206+
# The root install transitively builds `gp-furo-theme`, which
207+
# routes through `sphinx_vite_builder.build`. The smoke target
208+
# only verifies that imports resolve — it doesn't exercise the
209+
# rendered theme — so we skip the vite invocation.
210+
env:
211+
SPHINX_VITE_BUILDER_SKIP: "1"
160212
run: python scripts/ci/package_tools.py smoke root-install
161213

162214
- name: Smoke test built package artifact

CHANGES

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,30 @@ $ uv add gp-sphinx --prerelease allow
1818

1919
<!-- To maintainers and contributors: Please add notes for the forthcoming version below -->
2020

21+
### What's new
22+
23+
#### New package: `sphinx-vite-builder`
24+
25+
PEP 517 backend, hatchling build hook (`[tool.hatch.build.hooks.vite]`),
26+
and Sphinx extension that orchestrate Vite via pnpm. Wheels ship with
27+
the static tree pre-baked; source builds error loudly when pnpm or
28+
Node isn't on PATH, with copy-pasteable CI setup recipes for GitHub
29+
Actions, CircleCI, Azure Pipelines, and GitLab CI inlined into the
30+
error. `SPHINX_VITE_BUILDER_SKIP=1` short-circuits the orchestration
31+
when an external pipeline owns Vite. Replaces and supersedes
32+
`gp-sphinx-vite`; `merge_sphinx_config(vite_orchestration=True)`
33+
auto-injects the new extension. (#29)
34+
35+
### Bug fixes
36+
37+
#### `gp-furo-theme`: Wheels now ship with vite-built CSS and JS
38+
39+
`0.0.1a15` published wheels with an empty `static/` tree, leaving
40+
docs sites across every consumer unstyled. The new
41+
`sphinx-vite-builder.build` backend runs Vite at release time and
42+
hatchling packs the resulting assets, so a `pip install` from PyPI
43+
gets styled docs without the consumer rebuilding assets locally. (#29)
44+
2145
## gp-sphinx 0.0.1a15 (2026-05-02)
2246

2347
### What's new

README.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# gp-sphinx &middot; [![Python Package](https://img.shields.io/pypi/v/gp-sphinx.svg)](https://pypi.org/project/gp-sphinx/) [![License](https://img.shields.io/github/license/git-pull/gp-sphinx.svg)](https://github.com/git-pull/gp-sphinx/blob/main/LICENSE)
22

3-
Integrated autodoc design system for Sphinx. Twelve packages in three tiers
4-
that replace ~300 lines of duplicated `docs/conf.py` with ~10 lines and
5-
produce beautiful, consistent API documentation.
3+
An integrated autodoc design system for Sphinx that replaces ~300 lines
4+
of duplicated `docs/conf.py` with ~10 lines and produces beautiful,
5+
consistent API documentation.
66

77
## Requirements
88

@@ -50,21 +50,25 @@ Out of the box, `merge_sphinx_config()` activates:
5050
- **Componentized layouts** (`sphinx-ux-autodoc-layout`) — card containers, parameter folding, managed signatures
5151
- **Clean type hints** (`sphinx-autodoc-typehints-gp`) — simplified annotations with cross-referenced links, replacing `sphinx-autodoc-typehints` and `sphinx.ext.napoleon`
5252
- **Unified badge system** (`sphinx-ux-badges`) — type and modifier badges with a shared colour palette
53-
- **Six domain autodocumenters** — Python API, argparse CLIs, pytest fixtures, FastMCP tools, docutils directives, Sphinx config values
53+
- **Autodoc extensions** — Python API, argparse CLIs, pytest fixtures, FastMCP tools, docutils directives, Sphinx config values
5454
- **IBM Plex fonts** via Fontsource with preloaded web fonts
5555
- **Full dark mode** theming via CSS custom properties
5656

5757
See the [Gallery](https://gp-sphinx.git-pull.com/gallery.html) for live demos of every component.
5858

59-
## Three-tier architecture
59+
## Workspace architecture
6060

61-
The workspace is organized into three tiers — lower layers never depend on higher ones:
61+
Lower layers never depend on higher ones:
6262

63-
- **Shared infrastructure**: `sphinx-ux-badges`, `sphinx-ux-autodoc-layout`, `sphinx-autodoc-typehints-gp`
64-
- **Domain packages**: `sphinx-autodoc-api-style`, `sphinx-autodoc-docutils`, `sphinx-autodoc-fastmcp`, `sphinx-autodoc-pytest-fixtures`, `sphinx-autodoc-sphinx`
65-
- **Theme and coordinator**: `gp-sphinx`, `sphinx-gp-theme`, `sphinx-fonts`, `sphinx-autodoc-argparse`
63+
- **Common libraries**`sphinx-ux-badges`, `sphinx-ux-autodoc-layout`, `sphinx-autodoc-typehints-gp`, `sphinx-fonts`
64+
- **Autodoc extensions**`sphinx-autodoc-api-style`, `sphinx-autodoc-argparse`, `sphinx-autodoc-docutils`, `sphinx-autodoc-fastmcp`, `sphinx-autodoc-pytest-fixtures`, `sphinx-autodoc-sphinx`
65+
- **Build utils**`sphinx-vite-builder` ([PEP 517](https://peps.python.org/pep-0517/) backend + hatchling build hook + Sphinx extension that runs Vite via pnpm; publishable to PyPI for use outside this workspace)
66+
- **Theme and coordinator**`gp-sphinx`, `sphinx-gp-theme`, `gp-furo-theme`
67+
- **SEO**`sphinx-gp-opengraph`, `sphinx-gp-sitemap` (auto-loaded by `gp-sphinx` when `docs_url` is set)
6668

67-
See the [Architecture](https://gp-sphinx.git-pull.com/architecture.html) page for the full package map.
69+
See the [Architecture](https://gp-sphinx.git-pull.com/architecture.html)
70+
and [Packages](https://gp-sphinx.git-pull.com/packages/) pages for the
71+
full package map; the docs site auto-enumerates as the workspace grows.
6872

6973
## More information
7074

docs/_ext/package_reference.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
>>> package = workspace_packages()[0]
3939
>>> package["name"] in {
4040
... "gp-furo-theme",
41-
... "gp-sphinx-vite",
41+
... "sphinx-vite-builder",
4242
... "sphinx-gp-opengraph",
4343
... "sphinx-gp-sitemap",
4444
... "gp-sphinx",

docs/architecture.md

Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22

33
# Architecture
44

5-
Twelve workspace packages in three tiers. Lower layers never depend on
6-
higher ones — domain packages consume shared infrastructure, and the
5+
Workspace packages organized in tiers. Lower layers never depend on
6+
higher ones — autodoc extensions consume shared infrastructure, and the
77
presentation layer wires everything together for downstream projects.
88

9-
The sidebar groups these twelve packages into four navigation buckets
10-
(Domain Packages, UX, Utils, Internal) — a reader-facing grouping that
11-
is orthogonal to the dependency-ordered tier map below.
9+
The sidebar groups these packages into navigation buckets (Domain Packages,
10+
UX, Utils, Internal) — a reader-facing grouping that is orthogonal to the
11+
dependency-ordered tier map below.
1212

1313
## Tier 1: Shared infrastructure
1414

15-
The rendering pipeline that all domain packages consume:
15+
The rendering pipeline that every autodoc extension consumes:
1616

1717
::::{grid} 1 1 3 3
1818
:gutter: 2
@@ -43,39 +43,121 @@ Replaces `sphinx-autodoc-typehints` + `sphinx.ext.napoleon`.
4343

4444
::::
4545

46-
## Tier 2: Domain packages
46+
## Tier 2: Autodoc extensions
4747

48-
Domain-specific autodoc extensions that consume Tier 1 and add
49-
project-specific rendering logic:
48+
Domain-specific [autodoc extensions](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html)
49+
that consume Tier 1 and add project-specific rendering logic. Each
50+
ships directives that generate documentation from a particular
51+
source-construct family:
5052

51-
| Package | Domain | Directives |
52-
|---------|--------|------------|
53-
| {doc}`sphinx-autodoc-api-style <packages/sphinx-autodoc-api-style>` | Standard Python | `autofunction`, `autoclass`, `automodule` |
54-
| {doc}`sphinx-autodoc-argparse <packages/sphinx-autodoc-argparse>` | Custom `argparse` domain — programs, options, subcommands, positionals | `argparse` |
55-
| {doc}`sphinx-autodoc-docutils <packages/sphinx-autodoc-docutils>` | docutils | `autodirective`, `autorole` |
56-
| {doc}`sphinx-autodoc-fastmcp <packages/sphinx-autodoc-fastmcp>` | FastMCP tools | `fastmcp-tool`, `fastmcp-tool-summary` |
57-
| {doc}`sphinx-autodoc-pytest-fixtures <packages/sphinx-autodoc-pytest-fixtures>` | pytest fixtures (extends `py` domain) | `autofixture`, `autofixtures`, `auto-pytest-plugin` |
58-
| {doc}`sphinx-autodoc-sphinx <packages/sphinx-autodoc-sphinx>` | Sphinx config | `autoconfigvalue`, `autoconfigvalues` |
53+
::::{grid} 1 1 2 3
54+
:gutter: 2
55+
56+
:::{grid-item-card} sphinx-autodoc-api-style
57+
:link: packages/sphinx-autodoc-api-style
58+
:link-type: doc
59+
60+
**Subject**: standard Python.
61+
**Directives**: `autofunction`, `autoclass`, `automodule`.
62+
:::
63+
64+
:::{grid-item-card} sphinx-autodoc-argparse
65+
:link: packages/sphinx-autodoc-argparse
66+
:link-type: doc
67+
68+
**Subject**: argparse parsers — programs, options, subcommands, positionals.
69+
**Directives**: `argparse` (custom `argparse` domain).
70+
:::
71+
72+
:::{grid-item-card} sphinx-autodoc-docutils
73+
:link: packages/sphinx-autodoc-docutils
74+
:link-type: doc
75+
76+
**Subject**: docutils directives and roles.
77+
**Directives**: `autodirective`, `autorole`.
78+
:::
79+
80+
:::{grid-item-card} sphinx-autodoc-fastmcp
81+
:link: packages/sphinx-autodoc-fastmcp
82+
:link-type: doc
83+
84+
**Subject**: FastMCP tools, prompts, resources.
85+
**Directives**: `fastmcp-tool`, `fastmcp-tool-summary`.
86+
:::
87+
88+
:::{grid-item-card} sphinx-autodoc-pytest-fixtures
89+
:link: packages/sphinx-autodoc-pytest-fixtures
90+
:link-type: doc
5991

60-
Each domain package calls `app.setup_extension()` to auto-register its
92+
**Subject**: pytest fixtures (extends the `py` domain).
93+
**Directives**: `autofixture`, `autofixtures`, `auto-pytest-plugin`.
94+
:::
95+
96+
:::{grid-item-card} sphinx-autodoc-sphinx
97+
:link: packages/sphinx-autodoc-sphinx
98+
:link-type: doc
99+
100+
**Subject**: Sphinx config values.
101+
**Directives**: `autoconfigvalue`, `autoconfigvalues`.
102+
:::
103+
104+
::::
105+
106+
Each autodoc extension calls `app.setup_extension()` to auto-register its
61107
infrastructure dependencies — downstream projects only need to add the
62-
domain package to their `extensions` list.
108+
package to their `extensions` list.
63109

64110
## Tier 3: Theme and coordinator
65111

66112
| Package | Role |
67113
|---------|------|
68114
| {doc}`gp-sphinx <packages/gp-sphinx>` | Coordinator. `merge_sphinx_config()` wires up the full stack. |
69115
| {doc}`sphinx-gp-theme <packages/sphinx-gp-theme>` | Furo-based theme with CSS variables and SPA navigation. |
116+
| {doc}`gp-furo-theme <packages/gp-furo-theme>` | Tailwind v4 port of upstream Furo for git-pull projects. |
70117
| {doc}`sphinx-fonts <packages/sphinx-fonts>` | IBM Plex via Fontsource — preloaded web fonts. |
71118

119+
## Build tooling
120+
121+
Cross-cutting build utilities that operate outside the docs-build
122+
runtime — one is a [PEP 517](https://peps.python.org/pep-0517/) build
123+
backend invoked when wheels are produced; the other is an opt-in
124+
extension that drives the Vite watcher during `sphinx-autobuild`.
125+
Both let theme authors keep build artefacts (`static/styles/*.css`,
126+
`static/scripts/*.js`) out of VCS while still shipping working wheels
127+
and seamless live-reload during authoring.
128+
129+
::::{grid} 1 1 2 2
130+
:gutter: 2
131+
132+
:::{grid-item-card} sphinx-vite-builder
133+
:link: packages/sphinx-vite-builder
134+
:link-type: doc
135+
136+
[PEP 517](https://peps.python.org/pep-0517/) build backend (or
137+
hatchling build hook via `[tool.hatch.build.hooks.vite]`) that runs
138+
`pnpm exec vite build` before delegating wheel/sdist construction to
139+
hatchling. Also a Sphinx extension that auto-orchestrates
140+
`vite build --watch` during `sphinx-autobuild` and one-shot
141+
`vite build` during plain `sphinx-build`.
142+
Source builds error loudly without pnpm/Node; wheels ship turn-key.
143+
**Publishable for use outside this workspace** — any vite + Sphinx
144+
project can adopt either activation path without depending on the
145+
gp-sphinx coordinator.
146+
:::
147+
148+
::::
149+
72150
## How the tiers connect
73151

74-
Every domain package shares the same badge palette, the same componentized
75-
HTML output structure, and the same type annotation pipeline — so Python
76-
APIs, pytest fixtures, Sphinx config values, docutils directives, and
77-
FastMCP tools all look like they belong together.
152+
Every autodoc extension shares the same badge palette, the same
153+
componentized HTML output structure, and the same type annotation
154+
pipeline — so [Python APIs](packages/sphinx-autodoc-api-style.md),
155+
[pytest fixtures](packages/sphinx-autodoc-pytest-fixtures.md),
156+
[Sphinx config values](packages/sphinx-autodoc-sphinx.md),
157+
[docutils directives](packages/sphinx-autodoc-docutils.md), and
158+
[FastMCP tools](packages/sphinx-autodoc-fastmcp.md) all look like
159+
they belong together.
78160

79161
This is the **one autodoc design system** principle: a change to the shared
80-
infrastructure propagates instantly and consistently across all six
81-
domain packages.
162+
infrastructure propagates instantly and consistently across every autodoc
163+
extension in the workspace.

docs/conf.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,12 @@
9696
pytest_fixture_lint_level="none",
9797
rediraffe_redirects="redirects.txt",
9898
intersphinx_mapping=intersphinx_mapping,
99-
# Enable Vite orchestration: under `sphinx-autobuild`, gp-sphinx-vite
100-
# spawns `pnpm exec vite build --watch` so contributors editing
101-
# gp-furo-theme/web/src see fresh CSS/JS on disk without remembering
102-
# a separate command. No-op for `sphinx-build` (mode resolves to
103-
# "prod"), so wheel publishes carry no Node runtime requirement.
99+
# Enable Vite orchestration: under `sphinx-autobuild`,
100+
# sphinx-vite-builder spawns `pnpm exec vite build --watch` so
101+
# contributors editing gp-furo-theme/web/src see fresh CSS/JS on
102+
# disk without remembering a separate command. No-op for
103+
# `sphinx-build` (mode resolves to "prod"), so wheel publishes
104+
# carry no Node runtime requirement.
104105
vite_orchestration=True,
105106
)
106107
globals().update(conf)

0 commit comments

Comments
 (0)