paperkit 0.1.0__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.
Files changed (34) hide show
  1. paperkit-0.1.0/.gitignore +10 -0
  2. paperkit-0.1.0/CHANGELOG.md +56 -0
  3. paperkit-0.1.0/LICENSE +21 -0
  4. paperkit-0.1.0/PKG-INFO +381 -0
  5. paperkit-0.1.0/README.md +355 -0
  6. paperkit-0.1.0/ROADMAP.md +3 -0
  7. paperkit-0.1.0/pyproject.toml +103 -0
  8. paperkit-0.1.0/src/paperkit/__init__.py +6 -0
  9. paperkit-0.1.0/src/paperkit/archive.py +183 -0
  10. paperkit-0.1.0/src/paperkit/bibliography.py +321 -0
  11. paperkit-0.1.0/src/paperkit/builder.py +186 -0
  12. paperkit-0.1.0/src/paperkit/checks.py +563 -0
  13. paperkit-0.1.0/src/paperkit/cli.py +595 -0
  14. paperkit-0.1.0/src/paperkit/config.py +437 -0
  15. paperkit-0.1.0/src/paperkit/docker.py +265 -0
  16. paperkit-0.1.0/src/paperkit/pages.py +254 -0
  17. paperkit-0.1.0/src/paperkit/profiles/__init__.py +56 -0
  18. paperkit-0.1.0/src/paperkit/profiles/acm.py +10 -0
  19. paperkit-0.1.0/src/paperkit/py.typed +0 -0
  20. paperkit-0.1.0/src/paperkit/report.py +246 -0
  21. paperkit-0.1.0/src/paperkit/reproducible.py +158 -0
  22. paperkit-0.1.0/src/paperkit/texlive.py +310 -0
  23. paperkit-0.1.0/src/paperkit/text_export.py +165 -0
  24. paperkit-0.1.0/tests/test_archive.py +219 -0
  25. paperkit-0.1.0/tests/test_bibliography.py +353 -0
  26. paperkit-0.1.0/tests/test_bibtidy.py +103 -0
  27. paperkit-0.1.0/tests/test_cli.py +25 -0
  28. paperkit-0.1.0/tests/test_docker.py +33 -0
  29. paperkit-0.1.0/tests/test_environment.py +269 -0
  30. paperkit-0.1.0/tests/test_pages.py +148 -0
  31. paperkit-0.1.0/tests/test_reproducible.py +180 -0
  32. paperkit-0.1.0/tests/test_texlive.py +217 -0
  33. paperkit-0.1.0/tests/test_texlive_update.py +158 -0
  34. paperkit-0.1.0/tests/test_text_export.py +168 -0
@@ -0,0 +1,10 @@
1
+ .venv/
2
+
3
+ .idea/
4
+
5
+ # Build output of `uv build`.
6
+ dist/
7
+ *.egg-info/
8
+
9
+ __pycache__/
10
+ *.py[cod]
@@ -0,0 +1,56 @@
1
+ # Changelog
2
+
3
+ All notable changes to paperkit will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-10-24
9
+
10
+ First public release.
11
+
12
+ ### Added
13
+
14
+ - `paperkit build`: LaTeX builds with pdflatex, lualatex or xelatex and
15
+ bibtex or biber, in a Docker image composed from `.paperkit.toml` (via
16
+ pythainer) or on the host. Several build targets per project, built in
17
+ parallel, or selected by name.
18
+ - `[texlive] source = "upstream"`: install TeX Live from a dated tlnet
19
+ snapshot (or a closed release's final repository) so every image rebuild
20
+ gets the same package set, optionally pinned by the snapshot's package
21
+ database checksum.
22
+ - `paperkit check`: pre-submission checks on the build output, run in the
23
+ image by default: LaTeX and bibliography log errors, overfull boxes, font
24
+ embedding and Type 3 fonts, non-ASCII characters, trailing whitespace,
25
+ final newlines, unused bibliography entries (per bib file, across every
26
+ target using it), bibtex-tidy, and the toolchain that built the PDF.
27
+ - Page counts and limits: total pages and pages before references and
28
+ acknowledgments, checked against `[page_limits]`. Both sections are exempt
29
+ by default; `ack_counts_as_content = true` counts the acknowledgments as
30
+ content, as at venues that exempt only the references.
31
+ - `paperkit bibtidy`: run bibtex-tidy on the bibliography in place.
32
+ `--sort` / `--no-sort` chooses whether entries and fields are reordered;
33
+ `[checks] bibtex_tidy.sort` sets the default for the command and the
34
+ tidiness check alike.
35
+ - Byte-reproducible builds: every engine, bibtex and biber run is pinned to
36
+ one timestamp (`SOURCE_DATE_EPOCH` and `FORCE_SOURCE_DATE`, forwarded into
37
+ the container), which fixes the PDF dates and font subset tags, and the
38
+ trailer id is pinned too where the toolchain does not derive it from that
39
+ timestamp. `[build] source_date_epoch` takes an epoch, an ISO date, or
40
+ "git" (default: HEAD's committer date), and the report shows the timestamp
41
+ used. The submission archive is pinned to the same timestamp, so the same
42
+ sources zip to the same bytes from a fresh clone.
43
+ - `paperkit zip`: submission archive with configurable include and exclude
44
+ patterns. `[zip] include_bbl` (or `--bbl`) also ships each main's compiled
45
+ bibliography next to its .tex, for publishers that compile the sources
46
+ without running BibTeX; it refuses to build an archive whose .bbl is
47
+ missing and warns when one is older than its sources.
48
+ - Plain-text title and abstract exported next to each PDF, for pasting into
49
+ submission forms. `\input`/`\include` files are resolved in Python, so no
50
+ extra tool is needed, and an export that cannot run says why.
51
+ - `paperkit shell`, `paperkit -V/--version`, and `base` and `acm` profiles.
52
+ - `paperkit init` pins a dated TeX Live snapshot that the release was tested
53
+ against, so a new project is reproducible and `minted` works out of the box;
54
+ `--texlive distro` keeps the base image's packages. `paperkit texlive-update`
55
+ moves a project to the newest published snapshot (`--check` to only report),
56
+ and is the only command that uses the network.
paperkit-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vrije Universiteit Brussel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,381 @@
1
+ Metadata-Version: 2.5
2
+ Name: paperkit
3
+ Version: 0.1.0
4
+ Summary: Reproducible builds and pre-submission checks for research papers
5
+ Project-URL: Homepage, https://github.com/apaolillo/paperkit
6
+ Project-URL: Repository, https://github.com/apaolillo/paperkit
7
+ Project-URL: Issues, https://github.com/apaolillo/paperkit/issues
8
+ Project-URL: Changelog, https://github.com/apaolillo/paperkit/blob/main/CHANGELOG.md
9
+ Author-email: Antonio Paolillo <antonio.paolillo@vub.be>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: camera-ready,docker,latex,paper,reproducible-builds,texlive
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Text Processing :: Markup :: LaTeX
22
+ Requires-Python: >=3.12
23
+ Requires-Dist: click>=8
24
+ Requires-Dist: pythainer>=0.0.7
25
+ Description-Content-Type: text/markdown
26
+
27
+ # paperkit
28
+
29
+ Reproducible builds and pre-submission checks for research papers. paperkit
30
+ builds your paper in a container composed from a config file, then checks it
31
+ for the problems that get submissions desk-rejected or camera-ready PDFs
32
+ bounced: page limits, Type 3 or unembedded fonts, LaTeX errors, overfull
33
+ boxes, unused bibliography entries. It currently supports LaTeX; Typst is on
34
+ the [roadmap](ROADMAP.md).
35
+
36
+ ## Features
37
+
38
+ - **Container builds**: reproducible builds in Docker via
39
+ [pythainer](https://github.com/apaolillo/pythainer), with every engine
40
+ (pdflatex, lualatex, xelatex) and bibliography tool (bibtex, biber)
41
+ installed, or builds on the host.
42
+ - **Pinned TeX Live**: optionally install TeX Live from a dated snapshot, so
43
+ every image rebuild gets the same package set.
44
+ - **Page limits**: report total pages and pages before references and
45
+ acknowledgments, and flag a paper over its configured limits.
46
+ - **Pre-submission checks**: LaTeX and bibliography log errors, overfull
47
+ boxes, font embedding and Type 3 fonts, non-ASCII characters, trailing
48
+ whitespace, final newlines, unused bibliography entries, bibtex-tidy.
49
+ - **Several targets per project**: build a paper and its companion documents
50
+ in parallel, or just one of them; bibliography checks look at every target
51
+ sharing a bib file.
52
+ - **Submission plumbing**: a source archive with configurable include and
53
+ exclude patterns, and the title and abstract exported as plain text for
54
+ pasting into submission forms.
55
+ - **Profiles**: defaults per publisher (base and ACM so far).
56
+
57
+ ## Installation
58
+
59
+ Requires Python 3.12+ and, for container builds, Docker.
60
+
61
+ ```bash
62
+ pip install paperkit
63
+ # or, as an isolated command-line tool:
64
+ uv tool install paperkit
65
+ ```
66
+
67
+ ## Quick start
68
+
69
+ ```bash
70
+ # Generate a config file
71
+ paperkit init
72
+
73
+ # Build the paper (in Docker by default)
74
+ paperkit build
75
+
76
+ # Build on host (no Docker)
77
+ paperkit build --no-docker
78
+
79
+ # Run checks only (on existing build output, in Docker by default)
80
+ paperkit check
81
+
82
+ # Tidy the bibliography (add --no-sort to keep the entry order)
83
+ paperkit bibtidy
84
+
85
+ # Create submission archive
86
+ paperkit zip
87
+ ```
88
+
89
+ ## Configuration
90
+
91
+ paperkit looks for `.paperkit.toml` in the current directory or any parent. Example:
92
+
93
+ ```toml
94
+ [project]
95
+ main = "paper"
96
+ content_dirs = ["content"]
97
+ bib = "references.bib"
98
+
99
+ [build]
100
+ engine = "pdflatex" # pdflatex | lualatex | xelatex
101
+ passes = 3 # 4th pass enables pbalance
102
+ shell_escape = true
103
+ bibtex = true # use bibtex after pass 1
104
+ biber = false # use biber instead of bibtex
105
+ output_dir = "out"
106
+ source_date_epoch = "git" # timestamp builds are pinned to; see below
107
+
108
+ [docker]
109
+ base_image = "ubuntu:26.04"
110
+ image_name = "paper-builder"
111
+ extra_packages = [] # additional APT packages
112
+
113
+ [checks]
114
+ non_ascii = true
115
+ trailing_whitespace = true
116
+ final_newline = true
117
+ unused_bib = true
118
+ font_embedding = true # requires poppler-utils
119
+ no_type3 = true # warn on Type 3 (bitmap) fonts (part of font_embedding)
120
+ # full listing + Type 3 pages: out/<main>/fonts.txt
121
+ overfull_boxes = true
122
+ bibtex_tidy = { enabled = true, version = "1.14.0", sort = true }
123
+
124
+ [page_limits] # optional; without it the pages are only reported
125
+ total = 14 # max pages, references included
126
+ before_refs = 14 # max pages of content before the references
127
+ before_ack = 13 # max pages of content before the acknowledgments
128
+ ack_counts_as_content = false # see below
129
+
130
+ [zip]
131
+ name = "paper"
132
+ include = [
133
+ "{main}.tex",
134
+ "content/*.tex",
135
+ "content/references.bib",
136
+ ]
137
+ exclude = ["*.aux", "*.log", "*.out", "*.blg", "*.bbl"]
138
+ include_bbl = false # also ship each main's compiled bibliography
139
+
140
+ [profile]
141
+ name = "base" # base | acm
142
+ ```
143
+
144
+ ## Commands
145
+
146
+ | Command | Description |
147
+ |---|---|
148
+ | `paperkit build [NAMES]` | Build the paper PDFs (Docker by default) |
149
+ | `paperkit build --no-docker` | Build directly on the host |
150
+ | `paperkit shell` | Open an interactive shell in the Docker container |
151
+ | `paperkit check [NAMES]` | Run checks on existing build output (Docker by default) |
152
+ | `paperkit check --no-docker` | Run checks with the host's tools |
153
+ | `paperkit bibtidy` | Run bibtex-tidy on the bib file (in-place); `--no-sort` keeps the entry and field order |
154
+ | `paperkit zip` | Create submission archive; `--bbl` adds the compiled bibliographies |
155
+ | `paperkit init` | Generate a `.paperkit.toml` template (`--texlive distro` for the base image's packages) |
156
+ | `paperkit texlive-update` | Move `[texlive]` to the newest dated snapshot (`--check` to only report) |
157
+
158
+ Global options: `--config PATH` (config file), `-v/--verbose` (show Docker commands), `-V/--version`.
159
+
160
+ ## Checks
161
+
162
+ | Check | What it does |
163
+ |---|---|
164
+ | LaTeX log | Parse `.log` for errors and warnings |
165
+ | BibTeX/Biber log | Parse `.blg` for errors and warnings |
166
+ | Boxes | Count overfull and underfull boxes |
167
+ | Toolchain | With `[texlive] source = "upstream"`: warn if the log shows the PDF was built with another TeX Live (e.g. the host's, via `--no-docker`) |
168
+ | Fonts | Verify all fonts are embedded (`pdffonts`) |
169
+ | Non-ASCII | Flag non-ASCII characters in `.tex` source |
170
+ | Trailing whitespace | Detect lines with trailing spaces |
171
+ | Final newline | Ensure each file ends with exactly one `\n` |
172
+ | Unused bib entries | Find bib entries no target cites, once per bib file (see below) |
173
+ | BibTeX tidy | Check bibliography formatting with bibtex-tidy (the same arguments `paperkit bibtidy` would apply) |
174
+
175
+ **Unused bib entries across targets.** With several mains sharing a bib file,
176
+ an entry is unused only if no target using that file cites it. paperkit reads
177
+ which bib files each target uses and what it cites from the target's `.aux`
178
+ (`\bibdata`, `\citation`, including `\@input` files and `\nocite{*}`) or, with
179
+ biber, its `.bcf`; no configuration is needed. With several targets, the
180
+ result is printed once per bib file in a "Bibliography" block.
181
+
182
+ When only some targets are built or checked (`paperkit build paper`), the
183
+ others are taken from their existing `.aux` if it is newer than their sources,
184
+ else from one draft LaTeX pass into a scratch directory (no PDF; the real
185
+ output is untouched). If that is disabled or fails, entries not cited by the
186
+ checked targets are listed as "other targets not checked", never as unused.
187
+
188
+ ```toml
189
+ [checks.unused_bib] # or simply: unused_bib = true | false
190
+ ignore = ["some_key"] # never reported as unused (e.g. staged for later)
191
+ draft_pass = true # resolve other targets with a draft pass when needed
192
+ ```
193
+
194
+ **Page limits.** `[page_limits]` reports the total pages and the pages of
195
+ content before the references and the acknowledgments, and flags a paper over
196
+ its limits. A section starting mid-page counts that page. The sections are
197
+ found by their headings, so nothing has to be marked up in the source.
198
+
199
+ By default the acknowledgments and the references are both exempt: the
200
+ counted content ends at whichever comes first. Many venues (most ACM and
201
+ USENIX calls, for instance) exempt only the references and count
202
+ acknowledgments as text. Set `ack_counts_as_content = true` for those, and the
203
+ budget ends at the references heading instead:
204
+
205
+ ```toml
206
+ [page_limits]
207
+ before_refs = 14
208
+ ack_counts_as_content = true # acks sit inside the 14 pages
209
+ ```
210
+
211
+ A check whose tool is missing (e.g. `pdffonts`, `bibtex-tidy`) is reported as
212
+ **skipped** (yellow) and makes the verdict "OK with warnings", so it never
213
+ passes silently. `paperkit check` runs in the Docker image by default, where
214
+ all tools are installed; `--no-docker` uses the host's. The TeX Live line of
215
+ the report comes from the build log, i.e. it names the engine that produced
216
+ the PDF, wherever the report is generated.
217
+
218
+ **Shipping the compiled bibliography.** Publishers often compile a submission
219
+ without running BibTeX, and ask for the `.bbl` alongside the sources (Springer's
220
+ LNCS checklist, for instance). `include_bbl = true`, or `paperkit zip --bbl`,
221
+ copies each included main's `<main>.bbl` out of the build directory and stores
222
+ it next to its `.tex` in the archive, which is where a publisher looks for it.
223
+ Build first: if a `.bbl` is missing the command stops and says so rather than
224
+ shipping an archive that cannot be compiled, and if a `.bbl` is older than its
225
+ `.tex` or the bib file, it warns that the archive may not match the sources.
226
+
227
+ ## Profiles
228
+
229
+ **`base`**: sensible defaults for any paper. With `sort = false` the two
230
+ reordering arguments (`--sort=key`, `--sort-fields`) are dropped and the rest
231
+ still applies. Default bibtex-tidy args: `--curly --blank-lines --sort=key --duplicates=key,doi,citation --sort-fields --trailing-commas --remove-empty-fields --enclosing-braces=title --align=13`.
232
+
233
+ **`acm`**: for ACM conferences and journals. Currently identical to base; reserved for future ACM-specific defaults.
234
+
235
+ ## Docker
236
+
237
+ The Docker image includes all engines and tools regardless of the current config, so switching between pdflatex/lualatex or bibtex/biber requires no rebuild.
238
+
239
+ Pre-installed: TeX Live (all major collections + luatex + xetex), biber, pygments, poppler-utils, npm, bibtex-tidy.
240
+
241
+ ### Reproducible builds
242
+
243
+ Two builds of the same sources differ in three places, all written by the PDF
244
+ writer: the creation and modification dates, the random tags of subsetted
245
+ fonts (`VHDZTM+Calibri` one run, `EJHWCU+Calibri` the next), and the trailer
246
+ `/ID`. paperkit pins all three, so rebuilding gives byte-identical output.
247
+
248
+ Every engine, bibtex and biber run gets `SOURCE_DATE_EPOCH` and
249
+ `FORCE_SOURCE_DATE`, on the host and inside the container alike. The
250
+ timestamp comes from `[build] source_date_epoch`:
251
+
252
+ | Value | Meaning |
253
+ |---|---|
254
+ | `"git"` (default) | the committer date of HEAD |
255
+ | an integer | that epoch |
256
+ | an ISO date or date-time | that moment (dates are UTC midnight) |
257
+
258
+ A `SOURCE_DATE_EPOCH` already set in your environment wins over the config.
259
+ Outside a git repository, or before the first commit, paperkit warns that the
260
+ build is not reproducible and uses the current time. The report prints the
261
+ timestamp it used, so you can tell what a given PDF was built with:
262
+
263
+ ```
264
+ Source date 1789898400 (2026-09-20 10:00:00 UTC, from git)
265
+ ```
266
+
267
+ The archive is pinned the same way: `paperkit zip` stores the build
268
+ timestamp on every entry rather than the files' own modification times, which
269
+ git does not restore, and keeps only the executable bit of each mode. The same
270
+ sources therefore archive to the same bytes from a fresh clone, on another
271
+ machine.
272
+
273
+ The trailer `/ID` needs help on older toolchains: from TeX Live 2024 on,
274
+ xdvipdfmx derives it from the epoch, but earlier versions do not, so paperkit
275
+ pins it from the epoch and the main's name (each main of a project gets its
276
+ own). Your sources are untouched: the definition is passed on the engine's
277
+ command line.
278
+
279
+ ### TeX Live from a dated snapshot
280
+
281
+ `paperkit init` writes a `[texlive]` section pinned to a dated snapshot that
282
+ this release was tested against, so a new project gets a reproducible TeX
283
+ Live and a working `minted` without any network lookup at init time. Use
284
+ `paperkit init --texlive distro` for the base image's `texlive-*` packages
285
+ instead, which is whatever the distribution ships and changes (or disappears)
286
+ as it moves on.
287
+
288
+ With `source = "upstream"` paperkit runs the official `install-tl` against a
289
+ tlnet repository, and a dated snapshot gives the same package set on every
290
+ image rebuild:
291
+
292
+ ```toml
293
+ [texlive]
294
+ source = "upstream" # "distro" (default) | "upstream"
295
+ snapshot = "2026-09-18" # dated snapshot from texlive.info
296
+ tlpdb_sha512 = "bcd7c792..." # optional: pin the snapshot's package database
297
+ scheme = "small" # install-tl scheme (without "scheme-")
298
+ collections = [ # added on top of the scheme (without "collection-")
299
+ "latexextra", "fontsrecommended", "fontsextra", "publishers",
300
+ "mathscience", "pictures", "bibtexextra",
301
+ ]
302
+ packages = [] # extra individual packages (installed with tlmgr)
303
+ docs = false # install documentation files
304
+ sources = false # install source files
305
+ ```
306
+
307
+ Repository selection, highest precedence first:
308
+
309
+ | Key | Repository |
310
+ |---|---|
311
+ | `repository = "https://..."` | Any tlnet URL, e.g. a private mirror |
312
+ | `snapshot = "YYYY-MM-DD"` | `https://www.texlive.info/tlnet-archive/YYYY/MM/DD/tlnet` |
313
+ | `year = 2025` | Frozen final repository of a closed release, from the TUG historic archive |
314
+ | none | Live CTAN mirror: current packages, **not reproducible** (paperkit warns) |
315
+
316
+ `snapshot` and `year` are mutually exclusive. Collection names are TeX Live's,
317
+ not Debian's: Debian's `texlive-science` is `mathscience`, and
318
+ `texlive-latex-extra` is `latexextra`.
319
+
320
+ TeX Live is installed under `/opt/texlive` (added to `PATH`), `tlmgr` is left
321
+ pointing at the same repository so later `tlmgr install` calls cannot pull
322
+ newer packages, and no distro `texlive-*` packages are installed. The TeX Live
323
+ layer comes before paperkit's tools, so it only rebuilds when `[texlive]` or
324
+ the base image changes. `minted` works out of the box: TeX Live ships
325
+ `latexminted` with its Python dependencies, and only a system `python3` is
326
+ needed. This is the recommended setup on `ubuntu:26.04`, whose distro
327
+ `minted` and `latexminted` versions do not work together.
328
+
329
+ **What is pinned.** The snapshot fixes the TeX Live package set: install-tl
330
+ checks every downloaded package against the snapshot's package database
331
+ (`tlpkg/texlive.tlpdb`), and `tlpdb_sha512` fixes that database itself. The
332
+ image build fails before downloading anything if the snapshot's database
333
+ changed. To get the value:
334
+
335
+ ```bash
336
+ curl -fsSL https://www.texlive.info/tlnet-archive/YYYY/MM/DD/tlnet/tlpkg/texlive.tlpdb.sha512
337
+ ```
338
+
339
+ The base image is not pinned by the snapshot. It hardly matters once TeX comes
340
+ from the snapshot (it only provides perl, python3 and paperkit's tools), but it
341
+ can be pinned by digest:
342
+
343
+ ```toml
344
+ [docker]
345
+ base_image = "ubuntu:26.04@sha256:<digest>"
346
+ ```
347
+
348
+ (`docker pull ubuntu:26.04 && docker image inspect --format '{{index .RepoDigests 0}}' ubuntu:26.04`
349
+ prints it.)
350
+
351
+ **Availability.** texlive.info is a single volunteer-run host. If it becomes
352
+ unavailable, use `year = ...` for a closed release (the TUG historic archive is
353
+ mirrored), or keep a copy of the snapshot on your own server and set
354
+ `repository`.
355
+
356
+ **Bumping.** `paperkit texlive-update` looks up the newest published snapshot,
357
+ rewrites `snapshot` and `tlpdb_sha512` in `.paperkit.toml` and leaves the rest
358
+ of the file alone; `--check` reports the move without editing. It is the only
359
+ command that goes online: builds never do. Rebuild afterwards and diff the
360
+ output PDF against the previous build. To move by hand, change `snapshot` and
361
+ update `tlpdb_sha512` with the command above.
362
+
363
+ ## Development
364
+
365
+ Development uses [uv](https://docs.astral.sh/uv/); `uv.lock` pins the
366
+ development tools, so everyone formats and lints with the same versions.
367
+
368
+ ```bash
369
+ uv sync # create .venv with paperkit and the dev tools
370
+ ./scripts/all_checks.sh # headers, format, lint, types, tests (all below)
371
+
372
+ uv run ruff check src tests # linting (--fix applies the safe fixes)
373
+ uv run ruff format src tests # formatting
374
+ uv run mypy src # type checking
375
+ uv run pytest # tests
376
+ ```
377
+
378
+ Patches are welcome: [CONTRIBUTING.md](CONTRIBUTING.md) covers the checks a
379
+ pull request is expected to pass and the sign-off it carries. Releases follow
380
+ [RELEASING.md](RELEASING.md); changes are listed in
381
+ [CHANGELOG.md](CHANGELOG.md).