sphinx-vite-builder 0.0.1a16.dev1__tar.gz → 0.0.1a16.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/AGENTS.md +9 -3
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/PKG-INFO +11 -9
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/README.md +10 -7
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/pyproject.toml +1 -2
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/src/sphinx_vite_builder/__init__.py +18 -1
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/src/sphinx_vite_builder/_internal/process.py +23 -5
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/src/sphinx_vite_builder/_internal/vite.py +21 -4
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/src/sphinx_vite_builder/build.py +57 -2
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/.gitignore +0 -0
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/CLAUDE.md +0 -0
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/src/sphinx_vite_builder/_internal/__init__.py +0 -0
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/src/sphinx_vite_builder/_internal/bus.py +0 -0
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/src/sphinx_vite_builder/_internal/errors.py +0 -0
- {sphinx_vite_builder-0.0.1a16.dev1 → sphinx_vite_builder-0.0.1a16.dev2}/src/sphinx_vite_builder/py.typed +0 -0
|
@@ -29,6 +29,12 @@ asyncio loop in a daemon thread for sync↔async bridging),
|
|
|
29
29
|
spawn install/build), and `errors.py` (`PnpmMissingError`,
|
|
30
30
|
`NodeModulesInstallError`, `ViteFailedError`).
|
|
31
31
|
|
|
32
|
+
**Phase 1 status:** The PEP 517 backend is fully implemented and
|
|
33
|
+
tested. The Sphinx extension `setup()` is a placeholder — it
|
|
34
|
+
registers cleanly in `conf.py` but doesn't yet hook the docs build
|
|
35
|
+
lifecycle. The full extension implementation (event handlers, vite
|
|
36
|
+
watch, teardown) lands in a follow-up release.
|
|
37
|
+
|
|
32
38
|
## The design contract — keep this invariant
|
|
33
39
|
|
|
34
40
|
> **Sources should check for node, pnpm, etc and error if it's not
|
|
@@ -89,9 +95,9 @@ runs the wheel-from-sdist chain:
|
|
|
89
95
|
Net: **sdist install also requires zero toolchain**. The
|
|
90
96
|
`web/`-absent short-circuit is the load-bearing primitive.
|
|
91
97
|
|
|
92
|
-
##
|
|
98
|
+
## QA permutations — keep them green
|
|
93
99
|
|
|
94
|
-
|
|
100
|
+
The install-path permutations every change must keep green:
|
|
95
101
|
|
|
96
102
|
| # | Path | Toolchain | Expected |
|
|
97
103
|
|---|---|---|---|
|
|
@@ -152,7 +158,7 @@ resolution or distribution metadata, so wrapping them would be wrong.
|
|
|
152
158
|
|
|
153
159
|
The workspace's `release.yml` MUST keep the pnpm + Node setup steps
|
|
154
160
|
that run before `uv build`, otherwise the wheels published to PyPI
|
|
155
|
-
would be empty of static (the
|
|
161
|
+
would be empty of static (the prior broken-release pattern that
|
|
156
162
|
motivated this whole package). The required steps are:
|
|
157
163
|
|
|
158
164
|
```yaml
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sphinx-vite-builder
|
|
3
|
-
Version: 0.0.1a16.
|
|
3
|
+
Version: 0.0.1a16.dev2
|
|
4
4
|
Summary: PEP 517 build backend + Sphinx extension that orchestrates Vite via pnpm
|
|
5
5
|
Project-URL: Repository, https://github.com/git-pull/gp-sphinx
|
|
6
6
|
Author-email: Tony Narlock <tony@git-pull.com>
|
|
@@ -22,7 +22,6 @@ Classifier: Topic :: Documentation :: Sphinx
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Build Tools
|
|
23
23
|
Classifier: Typing :: Typed
|
|
24
24
|
Requires-Python: <4.0,>=3.10
|
|
25
|
-
Requires-Dist: hatchling>=1.0
|
|
26
25
|
Requires-Dist: sphinx>=8.1
|
|
27
26
|
Description-Content-Type: text/markdown
|
|
28
27
|
|
|
@@ -70,8 +69,9 @@ backend at release time. The PEP 517 chain doesn't run on the
|
|
|
70
69
|
consumer side. No backend invocation. No `pnpm`. No Node. The end
|
|
71
70
|
user sees Python and only Python.
|
|
72
71
|
|
|
72
|
+
No pnpm, no Node — just Python:
|
|
73
|
+
|
|
73
74
|
```console
|
|
74
|
-
$ # No pnpm, no Node, no problem
|
|
75
75
|
$ pip install gp-furo-theme
|
|
76
76
|
```
|
|
77
77
|
|
|
@@ -145,14 +145,16 @@ exclude = ["web/"] # so the sdist→wheel chain hits the short-circuit
|
|
|
145
145
|
artifacts = ["src/<your-theme>/theme/<theme-name>/static/"]
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
### Sphinx extension
|
|
148
|
+
### Sphinx extension (Phase 1: placeholder)
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
The extension entry point is currently a placeholder registered in
|
|
151
|
+
`conf.py` to prevent import errors. Full lifecycle integration —
|
|
152
|
+
running Vite before the docs build and spawning a watched Vite
|
|
153
|
+
process during `sphinx-autobuild` — lands in a follow-up release.
|
|
151
154
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
rebuilds and graceful teardown on signal / `atexit`
|
|
155
|
+
For now, the [PEP 517](https://peps.python.org/pep-0517/) backend
|
|
156
|
+
handles all Vite orchestration during source builds and wheel
|
|
157
|
+
generation; that path is fully implemented and tested.
|
|
156
158
|
|
|
157
159
|
```python
|
|
158
160
|
# docs/conf.py
|
|
@@ -42,8 +42,9 @@ backend at release time. The PEP 517 chain doesn't run on the
|
|
|
42
42
|
consumer side. No backend invocation. No `pnpm`. No Node. The end
|
|
43
43
|
user sees Python and only Python.
|
|
44
44
|
|
|
45
|
+
No pnpm, no Node — just Python:
|
|
46
|
+
|
|
45
47
|
```console
|
|
46
|
-
$ # No pnpm, no Node, no problem
|
|
47
48
|
$ pip install gp-furo-theme
|
|
48
49
|
```
|
|
49
50
|
|
|
@@ -117,14 +118,16 @@ exclude = ["web/"] # so the sdist→wheel chain hits the short-circuit
|
|
|
117
118
|
artifacts = ["src/<your-theme>/theme/<theme-name>/static/"]
|
|
118
119
|
```
|
|
119
120
|
|
|
120
|
-
### Sphinx extension
|
|
121
|
+
### Sphinx extension (Phase 1: placeholder)
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
The extension entry point is currently a placeholder registered in
|
|
124
|
+
`conf.py` to prevent import errors. Full lifecycle integration —
|
|
125
|
+
running Vite before the docs build and spawning a watched Vite
|
|
126
|
+
process during `sphinx-autobuild` — lands in a follow-up release.
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
rebuilds and graceful teardown on signal / `atexit`
|
|
128
|
+
For now, the [PEP 517](https://peps.python.org/pep-0517/) backend
|
|
129
|
+
handles all Vite orchestration during source builds and wheel
|
|
130
|
+
generation; that path is fully implemented and tested.
|
|
128
131
|
|
|
129
132
|
```python
|
|
130
133
|
# docs/conf.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sphinx-vite-builder"
|
|
3
|
-
version = "0.0.1a16.
|
|
3
|
+
version = "0.0.1a16.dev2"
|
|
4
4
|
description = "PEP 517 build backend + Sphinx extension that orchestrates Vite via pnpm"
|
|
5
5
|
requires-python = ">=3.10,<4.0"
|
|
6
6
|
authors = [
|
|
@@ -31,7 +31,6 @@ keywords = ["sphinx", "extension", "vite", "pnpm", "pep517", "build", "backend"]
|
|
|
31
31
|
# required at build time of consumer packages but not at runtime, so it
|
|
32
32
|
# stays in [build-system].requires of *consumers* rather than here.
|
|
33
33
|
dependencies = [
|
|
34
|
-
"hatchling>=1.0",
|
|
35
34
|
"sphinx>=8.1",
|
|
36
35
|
]
|
|
37
36
|
|
|
@@ -15,9 +15,13 @@ under :mod:`sphinx_vite_builder._internal`.
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import logging
|
|
18
19
|
import typing as t
|
|
19
20
|
|
|
20
|
-
__version__ = "0.0.1a16.
|
|
21
|
+
__version__ = "0.0.1a16.dev2"
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
logger.addHandler(logging.NullHandler())
|
|
21
25
|
|
|
22
26
|
if t.TYPE_CHECKING:
|
|
23
27
|
from sphinx.application import Sphinx
|
|
@@ -31,6 +35,19 @@ def setup(app: Sphinx) -> dict[str, t.Any]:
|
|
|
31
35
|
on ``sphinx-build``) lands in a follow-up commit. For now this
|
|
32
36
|
stub registers the extension so consumers can declare it without
|
|
33
37
|
a no-such-module error, and returns the safety metadata.
|
|
38
|
+
|
|
39
|
+
Examples
|
|
40
|
+
--------
|
|
41
|
+
The Phase-1 stub returns the parallel-safety metadata Sphinx
|
|
42
|
+
expects, regardless of the application object passed in:
|
|
43
|
+
|
|
44
|
+
>>> class FakeApp:
|
|
45
|
+
... pass
|
|
46
|
+
>>> metadata = setup(FakeApp()) # type: ignore[arg-type]
|
|
47
|
+
>>> metadata["parallel_read_safe"]
|
|
48
|
+
True
|
|
49
|
+
>>> metadata["parallel_write_safe"]
|
|
50
|
+
True
|
|
34
51
|
"""
|
|
35
52
|
del app
|
|
36
53
|
return {
|
|
@@ -9,9 +9,11 @@ backend and extension heads need:
|
|
|
9
9
|
- ``PYTHONUNBUFFERED=1`` is forced into the child env so Python tools
|
|
10
10
|
invoked via the package-manager bridge don't withhold their output.
|
|
11
11
|
- On POSIX, the child runs in a new session (``start_new_session=True``)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
and :meth:`AsyncProcess.terminate` signals the whole process group
|
|
13
|
+
via :func:`os.killpg` so ``pnpm exec`` plus every descendant exits
|
|
14
|
+
together. ``asyncio.subprocess.Process.terminate`` would only signal
|
|
15
|
+
the leader's PID, leaving the vite child orphaned (pnpm does not
|
|
16
|
+
forward signals to its ``exec`` target).
|
|
15
17
|
- :meth:`AsyncProcess.terminate` is graceful-then-forceful: SIGTERM,
|
|
16
18
|
await up to ``timeout`` seconds, escalate to SIGKILL if the child is
|
|
17
19
|
still alive. Idempotent: calling on an already-exited process is a
|
|
@@ -32,6 +34,7 @@ import contextlib
|
|
|
32
34
|
import logging
|
|
33
35
|
import os
|
|
34
36
|
import pathlib
|
|
37
|
+
import signal
|
|
35
38
|
import sys
|
|
36
39
|
import typing as t
|
|
37
40
|
|
|
@@ -196,7 +199,19 @@ class AsyncProcess:
|
|
|
196
199
|
if self._process.returncode is not None:
|
|
197
200
|
return self._process.returncode
|
|
198
201
|
|
|
199
|
-
|
|
202
|
+
# POSIX: the child was spawned with ``start_new_session=True``,
|
|
203
|
+
# so ``self._process.pid`` is the leader of its own session and
|
|
204
|
+
# equals its process-group ID. SIGTERM the whole group so
|
|
205
|
+
# ``pnpm exec`` plus all its descendants (including the vite
|
|
206
|
+
# process pnpm doesn't forward signals to) exit. asyncio's
|
|
207
|
+
# ``Process.terminate()`` is PID-only — it would leave vite
|
|
208
|
+
# orphaned. Windows has no ``killpg``; the asyncio default is
|
|
209
|
+
# the right primitive there.
|
|
210
|
+
with contextlib.suppress(ProcessLookupError):
|
|
211
|
+
if sys.platform != "win32":
|
|
212
|
+
os.killpg(self._process.pid, signal.SIGTERM)
|
|
213
|
+
else:
|
|
214
|
+
self._process.terminate()
|
|
200
215
|
try:
|
|
201
216
|
await asyncio.wait_for(self._process.wait(), timeout=timeout)
|
|
202
217
|
except asyncio.TimeoutError:
|
|
@@ -208,7 +223,10 @@ class AsyncProcess:
|
|
|
208
223
|
# ProcessLookupError race: the child can exit between
|
|
209
224
|
# TimeoutError and kill().
|
|
210
225
|
with contextlib.suppress(ProcessLookupError):
|
|
211
|
-
|
|
226
|
+
if sys.platform != "win32":
|
|
227
|
+
os.killpg(self._process.pid, signal.SIGKILL)
|
|
228
|
+
else:
|
|
229
|
+
self._process.kill()
|
|
212
230
|
await self._process.wait()
|
|
213
231
|
|
|
214
232
|
# Wait for drainers to consume their last buffered line before
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
"""Vite + pnpm orchestration: detection, install, one-shot build
|
|
1
|
+
"""Vite + pnpm orchestration: detection, install, one-shot build.
|
|
2
2
|
|
|
3
3
|
This module is the shared orchestration core consumed by both heads:
|
|
4
4
|
|
|
5
5
|
- The PEP 517 backend (:mod:`sphinx_vite_builder.build`) calls
|
|
6
6
|
:func:`run_vite_build` from each of its hooks, before delegating to
|
|
7
7
|
hatchling.
|
|
8
|
-
- The Sphinx extension (:mod:`sphinx_vite_builder`)
|
|
9
|
-
:func:`run_vite_build`
|
|
10
|
-
``builder-inited
|
|
8
|
+
- The Sphinx extension (:mod:`sphinx_vite_builder`) — Phase 1
|
|
9
|
+
placeholder — will call :func:`run_vite_build` from
|
|
10
|
+
``builder-inited`` once the extension head lands in a follow-up
|
|
11
|
+
release.
|
|
11
12
|
|
|
12
13
|
Fast-fail discipline: every prerequisite is checked up front so the
|
|
13
14
|
caller gets an actionable diagnostic instead of a generic spawn-failure
|
|
@@ -340,6 +341,22 @@ def run_vite_build(
|
|
|
340
341
|
non-zero.
|
|
341
342
|
- :class:`ViteFailedError` if ``pnpm exec vite build`` exits
|
|
342
343
|
non-zero.
|
|
344
|
+
|
|
345
|
+
Examples
|
|
346
|
+
--------
|
|
347
|
+
The ``SPHINX_VITE_BUILDER_SKIP`` environment variable
|
|
348
|
+
short-circuits the whole orchestration before any subprocess is
|
|
349
|
+
spawned — exercising it from a doctest verifies the escape hatch
|
|
350
|
+
keeps working without touching pnpm, Node, or the filesystem
|
|
351
|
+
tree:
|
|
352
|
+
|
|
353
|
+
>>> import os, pathlib, tempfile
|
|
354
|
+
>>> os.environ["SPHINX_VITE_BUILDER_SKIP"] = "1"
|
|
355
|
+
>>> try:
|
|
356
|
+
... with tempfile.TemporaryDirectory() as tmp:
|
|
357
|
+
... run_vite_build(pathlib.Path(tmp))
|
|
358
|
+
... finally:
|
|
359
|
+
... del os.environ["SPHINX_VITE_BUILDER_SKIP"]
|
|
343
360
|
"""
|
|
344
361
|
if os.environ.get("SPHINX_VITE_BUILDER_SKIP"):
|
|
345
362
|
logger.info("SPHINX_VITE_BUILDER_SKIP set; skipping vite build")
|
|
@@ -37,7 +37,41 @@ def build_wheel(
|
|
|
37
37
|
config_settings: dict[str, t.Any] | None = None,
|
|
38
38
|
metadata_directory: str | None = None,
|
|
39
39
|
) -> str:
|
|
40
|
-
"""PEP 517 ``build_wheel``: vite-build, then hatchling-pack.
|
|
40
|
+
"""PEP 517 ``build_wheel``: vite-build, then hatchling-pack.
|
|
41
|
+
|
|
42
|
+
Examples
|
|
43
|
+
--------
|
|
44
|
+
With ``SPHINX_VITE_BUILDER_SKIP=1`` set, the hook short-circuits
|
|
45
|
+
vite and delegates straight to :mod:`hatchling.build` against a
|
|
46
|
+
minimal synthetic project, producing a real ``.whl``:
|
|
47
|
+
|
|
48
|
+
>>> import os, pathlib, tempfile, textwrap
|
|
49
|
+
>>> os.environ["SPHINX_VITE_BUILDER_SKIP"] = "1"
|
|
50
|
+
>>> cwd = os.getcwd()
|
|
51
|
+
>>> with tempfile.TemporaryDirectory() as tmp:
|
|
52
|
+
... project = pathlib.Path(tmp)
|
|
53
|
+
... _ = (project / "pyproject.toml").write_text(textwrap.dedent('''
|
|
54
|
+
... [build-system]
|
|
55
|
+
... requires = ["hatchling"]
|
|
56
|
+
... build-backend = "hatchling.build"
|
|
57
|
+
... [project]
|
|
58
|
+
... name = "doctest-pkg"
|
|
59
|
+
... version = "0.0.0"
|
|
60
|
+
... ''').lstrip())
|
|
61
|
+
... pkg = project / "doctest_pkg"
|
|
62
|
+
... pkg.mkdir()
|
|
63
|
+
... _ = (pkg / "__init__.py").write_text("")
|
|
64
|
+
... dist = project / "dist"
|
|
65
|
+
... dist.mkdir()
|
|
66
|
+
... os.chdir(project)
|
|
67
|
+
... try:
|
|
68
|
+
... name = build_wheel(str(dist))
|
|
69
|
+
... finally:
|
|
70
|
+
... os.chdir(cwd)
|
|
71
|
+
... del os.environ["SPHINX_VITE_BUILDER_SKIP"]
|
|
72
|
+
>>> name.endswith(".whl")
|
|
73
|
+
True
|
|
74
|
+
"""
|
|
41
75
|
run_vite_build()
|
|
42
76
|
return _hatchling.build_wheel(wheel_directory, config_settings, metadata_directory)
|
|
43
77
|
|
|
@@ -47,7 +81,18 @@ def build_editable(
|
|
|
47
81
|
config_settings: dict[str, t.Any] | None = None,
|
|
48
82
|
metadata_directory: str | None = None,
|
|
49
83
|
) -> str:
|
|
50
|
-
"""PEP 660 ``build_editable``: vite-build, then hatchling-pack-editable.
|
|
84
|
+
"""PEP 660 ``build_editable``: vite-build, then hatchling-pack-editable.
|
|
85
|
+
|
|
86
|
+
The delegation pattern matches :func:`build_wheel`; see that
|
|
87
|
+
function's docstring for an end-to-end example exercising the
|
|
88
|
+
``SPHINX_VITE_BUILDER_SKIP=1`` short-circuit.
|
|
89
|
+
|
|
90
|
+
Examples
|
|
91
|
+
--------
|
|
92
|
+
>>> import inspect
|
|
93
|
+
>>> sorted(inspect.signature(build_editable).parameters)
|
|
94
|
+
['config_settings', 'metadata_directory', 'wheel_directory']
|
|
95
|
+
"""
|
|
51
96
|
run_vite_build()
|
|
52
97
|
return _hatchling.build_editable(
|
|
53
98
|
wheel_directory, config_settings, metadata_directory
|
|
@@ -66,6 +111,16 @@ def build_sdist(
|
|
|
66
111
|
the sdist without pnpm or Node — the wheel-from-sdist build will
|
|
67
112
|
skip vite (no ``web/`` in the unpacked tree) and ship the
|
|
68
113
|
pre-baked assets via hatchling's normal file selection.
|
|
114
|
+
|
|
115
|
+
The delegation pattern matches :func:`build_wheel`; see that
|
|
116
|
+
function's docstring for an end-to-end example exercising the
|
|
117
|
+
``SPHINX_VITE_BUILDER_SKIP=1`` short-circuit.
|
|
118
|
+
|
|
119
|
+
Examples
|
|
120
|
+
--------
|
|
121
|
+
>>> import inspect
|
|
122
|
+
>>> sorted(inspect.signature(build_sdist).parameters)
|
|
123
|
+
['config_settings', 'sdist_directory']
|
|
69
124
|
"""
|
|
70
125
|
run_vite_build()
|
|
71
126
|
return _hatchling.build_sdist(sdist_directory, config_settings)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|