@uniweb/unipress 0.2.2 → 0.2.4
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.
- package/CHANGELOG.md +97 -8
- package/README.md +18 -8
- package/RELEASING.md +3 -1
- package/docs/for-developers.md +113 -0
- package/docs/templates/book.md +2 -3
- package/docs/templates/data-report.md +2 -2
- package/docs/templates/directory.md +3 -3
- package/docs/templates/monograph.md +1 -2
- package/docs/templates/report.md +2 -3
- package/package.json +6 -6
- package/src/content-loader.js +101 -1
- package/src/foundation-loader.js +19 -2
- package/src/foundations-data.js +32 -18
- package/src/templates-data.js +42 -35
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Release binaries are now signed.** macOS binaries are signed with
|
|
8
|
+
Apple Developer ID Application + Apple-notarized; Windows binaries
|
|
9
|
+
are signed via Azure Trusted Signing. macOS users no longer see a
|
|
10
|
+
Gatekeeper warning on first run. The 0.2.3 release assets were
|
|
11
|
+
re-signed in place — re-download from the release page if you
|
|
12
|
+
grabbed an unsigned copy before this change landed.
|
|
13
|
+
|
|
14
|
+
### Removed
|
|
15
|
+
|
|
16
|
+
- `.github/workflows/release.yml`. Binaries are no longer built in
|
|
17
|
+
this public repo — they're built and signed in a private Proximify
|
|
18
|
+
pipeline. Pushing a `unipress@<version>` tag no longer triggers a
|
|
19
|
+
binary build; the maintainer dispatches the signed pipeline manually
|
|
20
|
+
after each tag, and the resulting binaries are uploaded to the same
|
|
21
|
+
GitHub Release on this repo. Source code, docs, npm publishing, and
|
|
22
|
+
the foundation-deploy workflow are unchanged.
|
|
23
|
+
|
|
24
|
+
## 0.2.3
|
|
25
|
+
|
|
26
|
+
The first complete-out-of-the-box release. `npm i -g @uniweb/unipress`
|
|
27
|
+
+ `unipress create my-book --template book` + `unipress compile my-book`
|
|
28
|
+
produces a real PDF on the first try, no setup, no auth, no local
|
|
29
|
+
servers, no `--foundation` overrides.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **GitHub Pages distribution for bundled foundations.**
|
|
34
|
+
`https://uniweb.github.io/unipress/foundations/<name>/<version>/foundation.js`
|
|
35
|
+
serves every published foundation. A new workflow
|
|
36
|
+
(`.github/workflows/deploy-foundations.yml`) builds each
|
|
37
|
+
`foundations/<name>/` on every push to main and accumulates versions
|
|
38
|
+
on the `gh-pages` branch — older versions stay reachable indefinitely.
|
|
39
|
+
No registry namespace claim, no Cloudflare worker, no platform
|
|
40
|
+
authorization needed for the open-source foundations.
|
|
41
|
+
- **`scripts/ci-flip-workspace-deps.js`** — pre-install rewrite that
|
|
42
|
+
translates `workspace:*` deps to npm version ranges in CI. Both
|
|
43
|
+
workflows (foundation deploy, binary release) call it as the first
|
|
44
|
+
step. Closes the standalone-install gap that the workspace:*
|
|
45
|
+
resolution introduced.
|
|
46
|
+
- **`prepublishOnly` regenerates `templates-data.js`.** `pnpm publish`
|
|
47
|
+
now refreshes the bundled template data from `documents/<name>/`
|
|
48
|
+
before packing, so a stale generated file can't ship even if a
|
|
49
|
+
developer forgot to regenerate before commit.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- **EPUB cover image no longer skipped.** The foundation's
|
|
54
|
+
`buildEpubOptions` was using a `fetch`-based URL resolver that
|
|
55
|
+
silently 404'd in unipress's Node compile context. Replaced with
|
|
56
|
+
the same `loadAsset` abstraction the typst path uses. Cover image
|
|
57
|
+
now embeds at `OEBPS/images/<hash>.jpg` alongside a synthesized
|
|
58
|
+
`cover.xhtml` page.
|
|
59
|
+
- **DOCX inline-image path threaded through `loadAsset`.**
|
|
60
|
+
`framework/press`'s docx adapter walker now plumbs the host-supplied
|
|
61
|
+
byte loader from `buildDocument` → `convertChildren` →
|
|
62
|
+
`irToImageParagraph` → `fetchImageData` → `fetchAsset`. Future
|
|
63
|
+
docx-emitting templates with inline images compile cleanly in
|
|
64
|
+
unipress.
|
|
65
|
+
- **Cover round-trip in scaffolded projects** — front + back JPGs
|
|
66
|
+
ship via the binary-asset support added to the template generator.
|
|
67
|
+
EPUB readers + most PDF viewers now show the book's thumbnail
|
|
68
|
+
without the user adding any artwork.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- Catalog URLs in `src/foundations-data.js` flipped from local-registry
|
|
73
|
+
(`http://localhost:4001/...`) to GH Pages
|
|
74
|
+
(`https://uniweb.github.io/unipress/foundations/...`). The 0.2.0
|
|
75
|
+
caveat in the changelog is resolved.
|
|
76
|
+
- `foundation-loader.js`'s `DEFAULT_REGISTRY_BASE` flipped to
|
|
77
|
+
`https://uniweb.github.io/unipress`. `UNIWEB_REGISTRY_URL` env var
|
|
78
|
+
remains the override for local dev / private alternatives.
|
|
79
|
+
- `@uniweb/*` deps in `package.json` are `workspace:*` again (dev
|
|
80
|
+
resolves local siblings); `pnpm publish` translates them to real
|
|
81
|
+
version specs in the published tarball.
|
|
82
|
+
|
|
83
|
+
### Notes
|
|
84
|
+
|
|
85
|
+
- Binary downloads (darwin-arm64, linux-x64, windows-x64) ship via
|
|
86
|
+
the existing `unipress@<version>` tag-driven release workflow. The
|
|
87
|
+
npm path is now equivalent for users with Node 20+.
|
|
88
|
+
- See `RELEASING.md` for the framework-publish + workspace:* flip-back
|
|
89
|
+
dance the next maintainer release follows.
|
|
90
|
+
|
|
3
91
|
## 0.2.0
|
|
4
92
|
|
|
5
93
|
The credibility-shifting release. Two production-shaped foundations
|
|
@@ -64,15 +152,16 @@ separates create-time templates from runtime foundations.
|
|
|
64
152
|
- `templates/` directory; `package.json`'s `files:` array now ships
|
|
65
153
|
`documents/`.
|
|
66
154
|
|
|
67
|
-
### Caveat
|
|
155
|
+
### Caveat (resolved in a follow-up release)
|
|
68
156
|
|
|
69
|
-
Catalog entries
|
|
70
|
-
(`http://localhost:4001
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
157
|
+
Catalog entries in 0.2.0 pointed at a local registry
|
|
158
|
+
(`http://localhost:4001/...`), so scaffolded documents required the
|
|
159
|
+
user to run a registry locally or to pass `--foundation <path>`
|
|
160
|
+
explicitly. A subsequent release switches the catalog URLs to GitHub
|
|
161
|
+
Pages (`https://uniweb.github.io/unipress/foundations/...`); the
|
|
162
|
+
foundations are deployed there by `.github/workflows/deploy-foundations.yml`
|
|
163
|
+
on every push to main, and `unipress create + compile` works end-to-end
|
|
164
|
+
from a fresh npm install.
|
|
76
165
|
|
|
77
166
|
## 0.1.0 (unreleased)
|
|
78
167
|
|
package/README.md
CHANGED
|
@@ -26,15 +26,25 @@ Equations work in the standard LaTeX style — `$E = mc^2$` for inline, `$$...$$
|
|
|
26
26
|
|
|
27
27
|
**For authors.** unipress is a tool. Pick a template, write markdown, compile. The template knows the conventions for its kind of document — typography, structure, the bits that make a book look like a book and a directory look like a directory. You handle the content; the template handles everything else.
|
|
28
28
|
|
|
29
|
-
**For developers.** unipress is an engine. A *foundation*
|
|
29
|
+
**For developers.** unipress is an engine. A *foundation* you write declares what section types exist (`type: Chapter`, `type: Bibliography`), where their data comes from (markdown content, file-based or API-backed collections, [Loom](https://github.com/uniweb/loom) expressions templating against hierarchical data), and what output formats it can emit (`outputs: { docx, xlsx, pdf, custom-format }`). Authors mix prose with structured records and computed values in the same document; unipress runs your foundation against their content and produces whatever your foundation declares — typeset book, accessible EPUB, regulatory report, structured data feed. The same foundation can also drive a [Uniweb](https://uniweb.io) website, so the work is never single-purpose.
|
|
30
30
|
|
|
31
|
-
Foundations consume [
|
|
31
|
+
Foundations consume [@uniweb/press](https://github.com/uniweb/press) for the bytes-emitting work. Press is the output layer (Word, Excel, Typst today; more formats shipping); the foundation is your vocabulary.
|
|
32
|
+
|
|
33
|
+
→ For the deeper pitch — mixed prose-and-data, Loom templating, custom output adapters — see [docs/for-developers.md](./docs/for-developers.md).
|
|
32
34
|
|
|
33
35
|
## Install
|
|
34
36
|
|
|
35
|
-
**
|
|
37
|
+
**Homebrew (macOS and Linux — recommended).** No Node, no manual download:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
brew install uniweb/unipress/unipress
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Updates land via `brew upgrade unipress`. The macOS binary is signed with the Proximify Inc. Developer ID and Apple-notarized — no Gatekeeper warnings.
|
|
44
|
+
|
|
45
|
+
**Manual download (Windows, or any platform without Homebrew).** Grab the asset for your platform from [the releases page](https://github.com/uniweb/unipress/releases), make it executable (Unix), and put it somewhere on your `PATH`. On macOS and Linux, `/usr/local/bin/unipress` is a common location.
|
|
36
46
|
|
|
37
|
-
**npm.** If you already have Node:
|
|
47
|
+
**npm.** If you already have Node ≥ 20.19:
|
|
38
48
|
|
|
39
49
|
```bash
|
|
40
50
|
npm i -g @uniweb/unipress
|
|
@@ -52,7 +62,7 @@ Five built-in templates ship with the binary. Run `unipress list-templates` for
|
|
|
52
62
|
| `data-report` | `@uniweb/data` | xlsx, docx | Aggregate metrics across structured records |
|
|
53
63
|
| `directory` | `@uniweb/data` | xlsx, docx | Flat records listing with a filterable surface |
|
|
54
64
|
|
|
55
|
-
Per-template guides: [
|
|
65
|
+
Per-template guides: [docs/templates/](./docs/templates/).
|
|
56
66
|
|
|
57
67
|
More templates land as more foundations ship — `cv`, `resume`, `paper`, `thesis` are on the roadmap for upcoming releases.
|
|
58
68
|
|
|
@@ -200,12 +210,12 @@ unipress is **pre-1.0**. The CLI is stable enough to write real documents with
|
|
|
200
210
|
|
|
201
211
|
## Troubleshooting
|
|
202
212
|
|
|
203
|
-
See [
|
|
213
|
+
See [docs/troubleshooting.md](./docs/troubleshooting.md) for common errors and fixes — every named error class maps to a cause and a concrete next step.
|
|
204
214
|
|
|
205
215
|
## See also
|
|
206
216
|
|
|
207
|
-
- [
|
|
208
|
-
- [
|
|
217
|
+
- [@uniweb/press](https://github.com/uniweb/press) — the output layer foundations consume to produce Word/Excel/Typst/EPUB. Foundation authors interact with it directly.
|
|
218
|
+
- [@uniweb/build](https://github.com/uniweb/build) — the framework's content-collection pipeline. unipress consumes the sharp-free `/content` entry.
|
|
209
219
|
- [Uniweb](https://uniweb.io) — the larger framework. Foundations originate here; unipress brings them to the command line.
|
|
210
220
|
|
|
211
221
|
## License
|
package/RELEASING.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Notes for cutting a release tag (`unipress@<version>`). The release workflow at `.github/workflows/release.yml` watches for tag pushes matching that pattern, builds three binaries (darwin-arm64, linux-x64, windows-x64), and creates a GitHub Release with the binaries and SHA-256 sidecars attached.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A separate workflow at `.github/workflows/deploy-foundations.yml` builds and deploys the bundled foundations (`foundations/<name>/`) to the unipress repo's GitHub Pages site under `https://uniweb.github.io/unipress/foundations/<name>/<version>/`. It runs on every push to main, accumulating versions on the `gh-pages` branch — older versions stay reachable indefinitely. No tag needed; foundation deployment is decoupled from the binary-release tag cadence.
|
|
6
|
+
|
|
7
|
+
This file mostly exists to flag one thing the binary-release workflow currently can't handle on its own.
|
|
6
8
|
|
|
7
9
|
---
|
|
8
10
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# unipress for developers
|
|
2
|
+
|
|
3
|
+
The developer-track companion to the README. Read this if you're considering writing a foundation that compiles to documents — or to documents *and* websites from the same source.
|
|
4
|
+
|
|
5
|
+
## What you actually get
|
|
6
|
+
|
|
7
|
+
A foundation is a [Uniweb](https://uniweb.io) component system. unipress is the **headless runtime** for it: run `unipress compile <dir>` and the same foundation that drives a Uniweb website produces a downloadable file from a content directory.
|
|
8
|
+
|
|
9
|
+
That means everything the framework does for websites, you can do for documents. The visual editor goes away; the compile pipeline stays.
|
|
10
|
+
|
|
11
|
+
## Section types are the vocabulary
|
|
12
|
+
|
|
13
|
+
A foundation declares **section types** — addressable React components that content authors reference by name from markdown frontmatter:
|
|
14
|
+
|
|
15
|
+
```markdown
|
|
16
|
+
---
|
|
17
|
+
type: Chapter
|
|
18
|
+
title: "Chapter One"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
Prose body here.
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The foundation decides what `Chapter` means: its layout, its typography, what data it expects, how it renders across each output format the foundation declares. Authors don't think in components — they think in *kinds of section*. The component is the foundation developer's responsibility; the content is the author's.
|
|
25
|
+
|
|
26
|
+
## Mix prose with structured data
|
|
27
|
+
|
|
28
|
+
A document is rarely just prose. Foundations declare data inputs; authors fill them in. The data can come from any of these surfaces:
|
|
29
|
+
|
|
30
|
+
- **Markdown content** — the prose body of each `.md` file, parsed into a structured tree (title, paragraphs, items, links, images, code blocks).
|
|
31
|
+
- **Frontmatter params** — declared in each section's YAML frontmatter, typed by the foundation's `meta.js`.
|
|
32
|
+
- **File-based collections** — `collections/publications.json`, `collections/team.yml`, or markdown frontmatter swept into a list at build time. Compile to one `/data/<name>.json` per collection.
|
|
33
|
+
- **API-backed collections** — declare a fetcher in `document.yml`; the build pipeline resolves it at compile time. The foundation reads `content.data` and renders.
|
|
34
|
+
- **Computed values via [Loom](https://github.com/uniweb/loom)** — Loom is an expression language for instantiating templates against hierarchical data. Pull a publications list from a collection, format each entry with a Loom expression, and the result lands typeset in your output.
|
|
35
|
+
|
|
36
|
+
All of these flow through the same foundation pipeline — same primitives whether the data came from a YAML file, a JSON dump, or an API endpoint.
|
|
37
|
+
|
|
38
|
+
### Academic writing: bibliographies and citations
|
|
39
|
+
|
|
40
|
+
For scholarly work specifically, [`citestyle`](https://github.com/uniweb/csl) handles the formatting layer Loom is too general for. CSL-based, lightweight, used directly from foundations: pick from APA, MLA, Chicago author-date, IEEE, Vancouver, Harvard at the section level (`style: chicago-author-date`); citestyle dynamically loads only the styles you reference and formats every entry to spec — the author ordering, journal abbreviations, punctuation, and conventions academic readers expect.
|
|
41
|
+
|
|
42
|
+
The combination is the powerful part. Loom interpolates ("In {section.title}, we describe…"); citestyle formats bibliographic entries (a complete back-matter `Bibliography`, or inline `[Author Year]` citation marks). A thesis, monograph, or annual research report uses both: Loom for dynamic prose and per-section metadata, citestyle for every bibliographic touch in the document.
|
|
43
|
+
|
|
44
|
+
Working example: the `data-report` template ships a `PublicationsList` section backed by YAML bib data in `collections/members/*.yml`, with per-section style selection. Run `unipress create my-report --template data-report` to see it. Bibliography support in long-form prose templates (`book`, `monograph`, future `thesis`/`paper`) lands across upcoming releases.
|
|
45
|
+
|
|
46
|
+
## Outputs are foundation-declared
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// foundation/src/foundation.js
|
|
50
|
+
export default {
|
|
51
|
+
outputs: {
|
|
52
|
+
pdf: { extension: 'pdf', via: 'typst', getOptions: buildTypstOptions },
|
|
53
|
+
epub: { extension: 'epub', getOptions: buildEpubOptions },
|
|
54
|
+
docx: { getOptions: buildDocxOptions },
|
|
55
|
+
'tax-form-2024': { getOptions: buildTaxFormOptions }, // your own adapter
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each entry says: which Press adapter handles the format (`via:`), which file extension to default to (`extension:`), and how to assemble adapter-specific options from the website's resolved content (`getOptions(website, hostHints)`).
|
|
61
|
+
|
|
62
|
+
Press ships adapters for Word, Excel, Typst (PDF), and HTML-shaped output. For a format Press doesn't ship — a CSV feed, a domain-specific XML, a one-of-a-kind regulatory submission — write a custom adapter using [`@uniweb/press/ir`](https://github.com/uniweb/press) and declare it on the foundation. unipress dispatches by name; nothing in unipress itself needs to know about your format.
|
|
63
|
+
|
|
64
|
+
## Same foundation, two runtimes
|
|
65
|
+
|
|
66
|
+
`unipress compile` is one consumer. The Uniweb website pipeline (`uniweb dev`, `uniweb build`) is the other. Same foundation, same content shape, same data declarations — different runtime.
|
|
67
|
+
|
|
68
|
+
That's the structural payoff: the work of defining section types, theming, and rendering isn't single-purpose. A "regulatory report" foundation can drive both an HTML site that stakeholders browse and a PDF/XLSX/DOCX bundle filed quarterly. A "research output" foundation can drive a public-facing journal site and a print-ready PDF for archival. Authoring once produces every modality.
|
|
69
|
+
|
|
70
|
+
## A worked example
|
|
71
|
+
|
|
72
|
+
A research-group annual-report foundation (`@yourorg/annual-report`) might ship:
|
|
73
|
+
|
|
74
|
+
- **Section types**: `Cover`, `ExecutiveSummary`, `Members`, `PublicationsList`, `FundingTable`, `Charts`, `Appendix`, `Bibliography`.
|
|
75
|
+
- **Data**: a `publications.yml` collection (file-based) with full bibliographic fields per paper; a `funding.json` collection with grant data; an API-backed `metrics:` collection pulling live citation counts.
|
|
76
|
+
- **citestyle for bibliographic precision**: pick a style at the section level (`style: chicago-author-date`) and citestyle formats every entry to spec — author ordering, journal abbreviations, the punctuation that marks the difference between a draft and a polished publication.
|
|
77
|
+
- **Loom templates**: dynamic prose elsewhere — running headers (`{book.title} — Chapter {n}`), section subtitles, the table-of-contents introductions. Loom and citestyle cooperate: citestyle owns bibliographic formatting; Loom owns everything else.
|
|
78
|
+
- **Outputs**: `pdf` (typeset for the dean's office), `xlsx` (one sheet per data block, for finance), `docx` (for collaborators who don't open PDFs), `pagedjs` (HTML for the public site).
|
|
79
|
+
|
|
80
|
+
A content author writes:
|
|
81
|
+
|
|
82
|
+
```markdown
|
|
83
|
+
---
|
|
84
|
+
type: PublicationsList
|
|
85
|
+
title: "Publications"
|
|
86
|
+
data: publications
|
|
87
|
+
filter:
|
|
88
|
+
year: 2024
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
The group published 47 papers in 2024, a 30% increase over 2023.
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`unipress compile annual-2024 --format pdf` walks the content through the foundation, resolves Loom expressions over the filtered collection, runs the typst adapter, ships a typeset PDF. `unipress compile annual-2024 --format xlsx` produces a workbook with one sheet per data section. The same source directory; two file types, both honest.
|
|
95
|
+
|
|
96
|
+
## Where to go from here
|
|
97
|
+
|
|
98
|
+
- **Foundation contract** — the `outputs:` map, `meta.js` section-type discovery, the `getOptions(website, hostHints)` signature: see [foundation configuration](https://github.com/uniweb/docs/blob/main/reference/foundation-config.md).
|
|
99
|
+
- **Data fetching** — collections, fetchers, predicates, where-objects, deferred fields: see the [data fetching reference](https://github.com/uniweb/docs/blob/main/reference/data-fetching.md).
|
|
100
|
+
- **Loom** — expression language for instantiating templates against hierarchical data: [`@uniweb/loom`](https://github.com/uniweb/loom).
|
|
101
|
+
- **citestyle** — CSL-based citation and bibliography formatter. Domain-specific complement to Loom: Loom interpolates, citestyle formats academic-style references: [`citestyle`](https://github.com/uniweb/csl).
|
|
102
|
+
- **Press (the output layer)** — [`@uniweb/press`](https://github.com/uniweb/press) docs cover the registration pattern foundations consume, the IR layer for custom adapters, and per-adapter notes (docx invariants, typst conventions, the EPUB pipeline).
|
|
103
|
+
- **Building a foundation from scratch** — [Uniweb framework docs](https://github.com/uniweb/docs) cover the full authoring story; everything written for sites applies to documents.
|
|
104
|
+
|
|
105
|
+
## A few things worth knowing upfront
|
|
106
|
+
|
|
107
|
+
**Documents are static at compile time.** unipress produces bytes; once the file is on disk, it doesn't change. "Dynamic data" in a document means *the data backing the document can vary between compiles* (re-run `unipress compile` after the API has new data; get a new PDF). It does not mean the PDF live-updates while someone reads it. That's website territory.
|
|
108
|
+
|
|
109
|
+
**Foundations stay environment-agnostic.** unipress runs in Node; the Uniweb editor runs in the browser. Foundations should not branch on `typeof window` or do their own asset I/O. The compile pipeline supplies a `loadAsset(src)` helper that hosts implement appropriately for their runtime; foundations just call it. The book foundation in this repo is the reference example.
|
|
110
|
+
|
|
111
|
+
**Compile failures are debuggable.** `unipress compile <dir> --verbose` streams every step; `unipress inspect <dir>` dumps the parsed content tree as JSON before the foundation runs (useful when output looks wrong and you want to know whether the input was right). Press emits structured warnings when an adapter receives unexpected shapes.
|
|
112
|
+
|
|
113
|
+
**Pre-1.0.** The contract is settling but not frozen. The foundation `outputs:` map, the `meta.js` discovery model, the `compileDocument` API, the section-type vocabulary — all stable enough to build against, with breaking changes possible (and documented in the CHANGELOG) before 1.0.
|
package/docs/templates/book.md
CHANGED
|
@@ -15,8 +15,7 @@ unipress compile . --format pdf --out my-book.pdf
|
|
|
15
15
|
```
|
|
16
16
|
my-book/
|
|
17
17
|
├── document.yml pinned to @uniweb/book@<version>
|
|
18
|
-
├──
|
|
19
|
-
│ ├── folder.yml activates folder mode
|
|
18
|
+
├── content/
|
|
20
19
|
│ ├── 01-preface.md type: BackMatter — unnumbered front matter
|
|
21
20
|
│ ├── 02-chapter-one.md
|
|
22
21
|
│ └── 03-chapter-two.md
|
|
@@ -42,7 +41,7 @@ my-book/
|
|
|
42
41
|
|
|
43
42
|
## Add chapters
|
|
44
43
|
|
|
45
|
-
Drop a markdown file under `
|
|
44
|
+
Drop a markdown file under `content/` and add its base name (without `.md`) to `document.yml`'s `content:` list in the order you want it to appear. Chapters need no frontmatter — the foundation's `Chapter` section handles them by default. For non-chapter pages (acknowledgments, colophon, appendices), use `type: BackMatter` in frontmatter to skip chapter numbering.
|
|
46
45
|
|
|
47
46
|
## Common customizations
|
|
48
47
|
|
|
@@ -20,7 +20,7 @@ q3-metrics/
|
|
|
20
20
|
├── collections/
|
|
21
21
|
│ ├── members/ three sample records (19th-century naturalists)
|
|
22
22
|
│ └── queries/ saved query examples for the Population dropdown
|
|
23
|
-
└──
|
|
23
|
+
└── content/
|
|
24
24
|
└── report/ cover, members, publications-by-{type,journal,year},
|
|
25
25
|
publications-list, funding, supervisions
|
|
26
26
|
```
|
|
@@ -33,7 +33,7 @@ The starter ships three sample members so the first compile produces a non-empty
|
|
|
33
33
|
|----------------------------|--------------------------------------------------------|
|
|
34
34
|
| `name`, `author`, `year` | Workbook metadata (shown in the file's properties). |
|
|
35
35
|
| `format` | `xlsx` (default) or `docx`. Override on the CLI. |
|
|
36
|
-
| `index` | Routes `/` → `
|
|
36
|
+
| `index` | Routes `/` → `content/<index>` (here: `report`). |
|
|
37
37
|
| `collections.members.path` | Where the records live (default `collections/members`).|
|
|
38
38
|
| `collections.members.queryable` | Filterable surface — drives the FilterPanel UI. |
|
|
39
39
|
| `collections.queries.path` | Saved-view dropdown (Population selector). |
|
|
@@ -17,7 +17,7 @@ faculty/
|
|
|
17
17
|
├── document.yml pinned to @uniweb/data@<version>; one queryable collection
|
|
18
18
|
├── collections/
|
|
19
19
|
│ └── members/ three sample records
|
|
20
|
-
└──
|
|
20
|
+
└── content/
|
|
21
21
|
└── directory/ cover (Loom-rendered count) + listing
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ faculty/
|
|
|
27
27
|
|-----------------------------|--------------------------------------------------------|
|
|
28
28
|
| `name` | Workbook title. |
|
|
29
29
|
| `format` | `xlsx` (default) or `docx`. |
|
|
30
|
-
| `index` | Routes `/` → `
|
|
30
|
+
| `index` | Routes `/` → `content/directory`. |
|
|
31
31
|
| `collections.members.path` | Where the records live (`collections/members`). |
|
|
32
32
|
| `collections.members.queryable` | Filterable fields — drives the FilterPanel UI. |
|
|
33
33
|
|
|
@@ -58,7 +58,7 @@ Add a `joined_year:` range field, for example, if filtering by start year matter
|
|
|
58
58
|
|
|
59
59
|
## Hand-pick the displayed columns
|
|
60
60
|
|
|
61
|
-
The Members section reads every key from each record by default. To restrict the columns shown in the web preview and the xlsx export, set `columns:` on the section frontmatter in `
|
|
61
|
+
The Members section reads every key from each record by default. To restrict the columns shown in the web preview and the xlsx export, set `columns:` on the section frontmatter in `content/directory/members.md`.
|
|
62
62
|
|
|
63
63
|
## When to pick `directory` over `data-report`
|
|
64
64
|
|
|
@@ -15,8 +15,7 @@ unipress compile . --format pdf --out my-monograph.pdf
|
|
|
15
15
|
```
|
|
16
16
|
my-monograph/
|
|
17
17
|
├── document.yml pinned to @uniweb/book@<version>; royal-octavo, EB Garamond, tocDepth: 3
|
|
18
|
-
├──
|
|
19
|
-
│ ├── folder.yml
|
|
18
|
+
├── content/
|
|
20
19
|
│ ├── 01-preface.md type: BackMatter
|
|
21
20
|
│ ├── 02-introduction.md
|
|
22
21
|
│ └── 03-chapter-one.md
|
package/docs/templates/report.md
CHANGED
|
@@ -15,8 +15,7 @@ unipress compile . --format pdf --out q3-report.pdf
|
|
|
15
15
|
```
|
|
16
16
|
q3-report/
|
|
17
17
|
├── document.yml pinned to @uniweb/book@<version>; trade-7x10, block paragraphs
|
|
18
|
-
├──
|
|
19
|
-
│ ├── folder.yml
|
|
18
|
+
├── content/
|
|
20
19
|
│ ├── 01-summary.md executive summary + recommendations table
|
|
21
20
|
│ ├── 02-findings.md body, with code listing and pull-quote
|
|
22
21
|
│ └── 03-methodology.md
|
|
@@ -46,7 +45,7 @@ Same shape as `book` (see [book.md](./book.md)). The differences are defaults:
|
|
|
46
45
|
|
|
47
46
|
## Common customizations
|
|
48
47
|
|
|
49
|
-
- **Add an appendix**: a new markdown file with `type: BackMatter` in frontmatter, listed in `
|
|
48
|
+
- **Add an appendix**: a new markdown file with `type: BackMatter` in frontmatter, listed in `content:`.
|
|
50
49
|
- **Wider code blocks**: bump `book.typography.codeMarginRelief` to `0.5in` (code extends 0.5in past each side of the body column).
|
|
51
50
|
- **No TOC** for short reports: set `book.structure.toc: false`.
|
|
52
51
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/unipress",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Compile a content directory into a document (PDF, EPUB, Paged.js HTML, Typst source bundle, DOCX, XLSX) using a Uniweb foundation. Five built-in templates: book, monograph, report, data-report, directory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"prompts": "^2.4.2",
|
|
50
50
|
"react": "^18.0.0 || ^19.0.0",
|
|
51
51
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
52
|
-
"@uniweb/
|
|
53
|
-
"@uniweb/
|
|
54
|
-
"@uniweb/
|
|
55
|
-
"@uniweb/
|
|
56
|
-
"@uniweb/
|
|
52
|
+
"@uniweb/build": "0.11.3",
|
|
53
|
+
"@uniweb/runtime": "0.8.5",
|
|
54
|
+
"@uniweb/core": "0.7.4",
|
|
55
|
+
"@uniweb/semantic-parser": "1.1.12",
|
|
56
|
+
"@uniweb/content-reader": "1.1.6"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"test": "echo \"no tests yet\" && exit 0",
|
package/src/content-loader.js
CHANGED
|
@@ -10,10 +10,108 @@
|
|
|
10
10
|
|
|
11
11
|
import { existsSync } from 'node:fs'
|
|
12
12
|
import { resolve, join } from 'node:path'
|
|
13
|
-
import { collectSiteContent } from '@uniweb/build/content'
|
|
13
|
+
import { collectSiteContent, processCollections } from '@uniweb/build/content'
|
|
14
14
|
import { detectConfigFile, CONFIG_FILE_NAMES } from './document-yml.js'
|
|
15
15
|
import { ContentDirectoryError, DocumentYmlError } from './errors.js'
|
|
16
16
|
|
|
17
|
+
// Match a parsed collection-backed fetch path. parseFetchConfig (in
|
|
18
|
+
// @uniweb/build) normalises `{ collection: <name> }` into
|
|
19
|
+
// `{ path: '/data/<name>.json', schema: <name>, ... }` — so this regex is
|
|
20
|
+
// the inverse: pull the collection name back out of the resolved path.
|
|
21
|
+
const COLLECTION_PATH_RE = /^\/data\/(.+)\.json$/
|
|
22
|
+
|
|
23
|
+
function attachData(section, schema, data) {
|
|
24
|
+
if (!section || !schema) return
|
|
25
|
+
if (!section.parsedContent) section.parsedContent = {}
|
|
26
|
+
if (!section.parsedContent.data) section.parsedContent.data = {}
|
|
27
|
+
// Don't clobber a section-level value with a cascaded page-level one.
|
|
28
|
+
// The Block constructor will spread parsedContent.data through, so the
|
|
29
|
+
// first writer wins for a given schema.
|
|
30
|
+
if (section.parsedContent.data[schema] === undefined) {
|
|
31
|
+
section.parsedContent.data[schema] = data
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function findCollectionRecords(fetchConfig, resolved) {
|
|
36
|
+
if (!fetchConfig?.path || !fetchConfig?.schema) return null
|
|
37
|
+
const m = COLLECTION_PATH_RE.exec(fetchConfig.path)
|
|
38
|
+
if (!m) return null
|
|
39
|
+
const records = resolved[m[1]]
|
|
40
|
+
if (!Array.isArray(records)) return null
|
|
41
|
+
return records
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function attachSectionFetches(sections, resolved) {
|
|
45
|
+
if (!Array.isArray(sections)) return
|
|
46
|
+
for (const section of sections) {
|
|
47
|
+
const records = findCollectionRecords(section.fetch, resolved)
|
|
48
|
+
if (records) attachData(section, section.fetch.schema, records)
|
|
49
|
+
if (Array.isArray(section.subsections) && section.subsections.length) {
|
|
50
|
+
attachSectionFetches(section.subsections, resolved)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Materialize file-based collections into each section's
|
|
57
|
+
* `parsedContent.data` so the SSR render pipeline reads populated data
|
|
58
|
+
* synchronously (no `useFetched` round-trip; no `public/` directory).
|
|
59
|
+
*
|
|
60
|
+
* In a regular Uniweb site build, the Vite plugin runs `processCollections`
|
|
61
|
+
* + `writeCollectionFiles` and the runtime resolves `fetch:` declarations
|
|
62
|
+
* over HTTP at render time. Under `unipress compile` neither of those
|
|
63
|
+
* happens — there's no public dir, and SSR skips effects. We close the
|
|
64
|
+
* gap by resolving collections in-memory and attaching the records
|
|
65
|
+
* directly to each block's `parsedContent.data.<schema>`. The Block
|
|
66
|
+
* constructor (framework/core/src/block.js) preserves that field, and
|
|
67
|
+
* `prepareProps` then surfaces it as `content.data.<schema>` to the
|
|
68
|
+
* component — same shape the runtime would produce.
|
|
69
|
+
*
|
|
70
|
+
* Page-level fetch cascades to every section on the page; section-level
|
|
71
|
+
* fetch overrides on a per-section basis. Only collection-backed fetches
|
|
72
|
+
* (parsed `path: '/data/<name>.json'`) are resolved here — remote URL
|
|
73
|
+
* fetches, refine configs, and array-form `fetch: [...]` declarations
|
|
74
|
+
* are left untouched (those have their own gaps; out of scope here).
|
|
75
|
+
*/
|
|
76
|
+
async function resolveLocalCollections(siteContent, sitePath) {
|
|
77
|
+
const collectionsConfig = siteContent?.config?.collections
|
|
78
|
+
if (!collectionsConfig || typeof collectionsConfig !== 'object') return
|
|
79
|
+
if (Object.keys(collectionsConfig).length === 0) return
|
|
80
|
+
|
|
81
|
+
const resolved = await processCollections(
|
|
82
|
+
sitePath,
|
|
83
|
+
collectionsConfig,
|
|
84
|
+
sitePath,
|
|
85
|
+
'/',
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
for (const page of siteContent.pages || []) {
|
|
89
|
+
const pageRecords = findCollectionRecords(page.fetch, resolved)
|
|
90
|
+
if (pageRecords) {
|
|
91
|
+
for (const section of page.sections || []) {
|
|
92
|
+
attachData(section, page.fetch.schema, pageRecords)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
attachSectionFetches(page.sections, resolved)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Stash the resolved arrays on the website config too, so any section
|
|
99
|
+
// (regardless of its own page's fetch declaration) can self-bootstrap
|
|
100
|
+
// — e.g., a Cite inset rendering inside a Chapter on page A needs the
|
|
101
|
+
// bibliography records that the Bibliography section declared on page
|
|
102
|
+
// B. Foundations read this via `block.website.config.collections.<name>.records`
|
|
103
|
+
// as a synchronous fallback.
|
|
104
|
+
if (!siteContent.config) siteContent.config = {}
|
|
105
|
+
if (!siteContent.config.collections) siteContent.config.collections = {}
|
|
106
|
+
for (const name of Object.keys(resolved)) {
|
|
107
|
+
const existing = siteContent.config.collections[name]
|
|
108
|
+
siteContent.config.collections[name] = {
|
|
109
|
+
...(existing && typeof existing === 'object' ? existing : {}),
|
|
110
|
+
records: resolved[name],
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
17
115
|
export async function loadContent(dir, options = {}) {
|
|
18
116
|
const sitePath = resolve(dir)
|
|
19
117
|
|
|
@@ -52,5 +150,7 @@ export async function loadContent(dir, options = {}) {
|
|
|
52
150
|
throw err
|
|
53
151
|
}
|
|
54
152
|
|
|
153
|
+
await resolveLocalCollections(content, sitePath)
|
|
154
|
+
|
|
55
155
|
return { content, configFile, sitePath }
|
|
56
156
|
}
|
package/src/foundation-loader.js
CHANGED
|
@@ -41,7 +41,17 @@ const PATH_PATTERN = /^(\.\.?[\/\\]|[\/\\]|[A-Za-z]:[\/\\])/
|
|
|
41
41
|
// Version is anything semver-shaped — exact match isn't enforced here; the
|
|
42
42
|
// registry decides what's valid.
|
|
43
43
|
const REGISTRY_REF_PATTERN = /^@([a-z0-9][a-z0-9._-]*)\/([a-z0-9][a-z0-9._-]*)@([0-9a-z][0-9a-z.\-+]*)$/i
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
// unipress's bundled foundations are distributed as static artifacts on
|
|
46
|
+
// the unipress repo's GitHub Pages site. URL pattern:
|
|
47
|
+
//
|
|
48
|
+
// https://uniweb.github.io/unipress/foundations/<name>/<version>/foundation.js
|
|
49
|
+
//
|
|
50
|
+
// The namespace portion of a registry ref is implicit — every foundation
|
|
51
|
+
// served from this base is under @uniweb/. Set UNIWEB_REGISTRY_URL to
|
|
52
|
+
// override (e.g., for testing against a local http.server during foundation
|
|
53
|
+
// development, or for users with a private alternative).
|
|
54
|
+
const DEFAULT_REGISTRY_BASE = 'https://uniweb.github.io/unipress'
|
|
45
55
|
const DEFAULT_BUILT_ENTRY = 'dist/foundation.js'
|
|
46
56
|
|
|
47
57
|
function getRegistryBase() {
|
|
@@ -49,8 +59,15 @@ function getRegistryBase() {
|
|
|
49
59
|
return raw.replace(/\/$/, '')
|
|
50
60
|
}
|
|
51
61
|
|
|
62
|
+
// Build the URL for a registry ref. The namespace is preserved in the
|
|
63
|
+
// returned URL only when the base wants it; for the GH Pages distribution
|
|
64
|
+
// the namespace is implicit and the path is <base>/foundations/<name>/<ver>/.
|
|
65
|
+
// If a custom UNIWEB_REGISTRY_URL needs the namespace in the path, it can
|
|
66
|
+
// hold a path prefix that resolves accordingly (e.g., set
|
|
67
|
+
// `UNIWEB_REGISTRY_URL=https://my.host/by-ns/uniweb` and live without the
|
|
68
|
+
// per-namespace split, or maintain a redirect rule on that host).
|
|
52
69
|
function buildRegistryUrl(namespace, name, version) {
|
|
53
|
-
return `${getRegistryBase()}/
|
|
70
|
+
return `${getRegistryBase()}/foundations/${name}/${version}/foundation.js`
|
|
54
71
|
}
|
|
55
72
|
|
|
56
73
|
export async function resolveFoundationRef(ref, { anchorDir, onProgress = () => {} } = {}) {
|