@uniweb/unipress 0.8.11 → 0.8.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/unipress",
3
- "version": "0.8.11",
3
+ "version": "0.8.13",
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": "^19.0.0",
51
51
  "react-dom": "^19.0.0",
52
- "@uniweb/content-reader": "^1.2.3",
53
- "@uniweb/runtime": "^0.12.6",
54
- "@uniweb/semantic-parser": "^1.2.3",
55
- "@uniweb/build": "^0.25.0",
56
- "@uniweb/core": "^0.11.0"
52
+ "@uniweb/runtime": "^0.12.10",
53
+ "@uniweb/content-reader": "^1.2.4",
54
+ "@uniweb/build": "^0.26.0",
55
+ "@uniweb/core": "^0.12.0",
56
+ "@uniweb/semantic-parser": "^1.3.1"
57
57
  },
58
58
  "devDependencies": {
59
59
  "vitest": "^4.1.7"
@@ -33,6 +33,7 @@ import { resolve, isAbsolute, join, dirname } from 'node:path'
33
33
  import { FoundationResolutionError } from './errors.js'
34
34
  import { findCatalogEntry } from './catalog.js'
35
35
  import { fetchFoundationToCache } from './foundation-fetch.js'
36
+ import { foundationUrl } from './registry-base.js'
36
37
 
37
38
  const URL_PATTERN = /^https?:\/\//
38
39
  const PATH_PATTERN = /^(\.\.?[\/\\]|[\/\\]|[A-Za-z]:[\/\\])/
@@ -42,32 +43,15 @@ const PATH_PATTERN = /^(\.\.?[\/\\]|[\/\\]|[A-Za-z]:[\/\\])/
42
43
  // registry decides what's valid.
43
44
  const REGISTRY_REF_PATTERN = /^@([a-z0-9][a-z0-9._-]*)\/([a-z0-9][a-z0-9._-]*)@([0-9a-z][0-9a-z.\-+]*)$/i
44
45
 
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>/entry.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'
55
46
  const DEFAULT_BUILT_ENTRY = 'dist/entry.js'
56
47
 
57
- function getRegistryBase() {
58
- const raw = process.env.UNIWEB_REGISTRY_URL || DEFAULT_REGISTRY_BASE
59
- return raw.replace(/\/$/, '')
60
- }
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).
48
+ // Build the URL for a registry ref. The base + layout live in registry-base.js,
49
+ // shared with the catalog so the URL we FETCH and the URL we SHOW cannot drift
50
+ // (and so both honour UNIWEB_REGISTRY_URL). The `namespace` is accepted and
51
+ // unused: every foundation under this layout is @uniweb/, so a ref's scope does
52
+ // not appear in the path — see foundationPath's note on per-namespace bases.
69
53
  function buildRegistryUrl(namespace, name, version) {
70
- return `${getRegistryBase()}/foundations/${name}/${version}/entry.js`
54
+ return foundationUrl(name, version)
71
55
  }
72
56
 
73
57
  export async function resolveFoundationRef(ref, { anchorDir, onProgress = () => {} } = {}) {
@@ -14,9 +14,9 @@
14
14
  * ─────────────────────────────────────────────────────────────────────────
15
15
  *
16
16
  * Foundations are deployed as static artifacts to the unipress repo's
17
- * GitHub Pages site. URL pattern:
18
- *
19
- * https://uniweb.github.io/unipress/foundations/<name>/<version>/entry.js
17
+ * GitHub Pages site. **The base and the URL rule live in `registry-base.js`** —
18
+ * one definition, shared with `foundation-loader.js`, so what this catalog shows
19
+ * and what `compile` fetches cannot disagree.
20
20
  *
21
21
  * On every push to main, `.github/workflows/deploy-foundations.yml`
22
22
  * builds each `foundations/<name>/` and layers the resulting `dist/` into
@@ -52,9 +52,23 @@
52
52
  *
53
53
  * Each entry's `foundation.source.url` is the human-meaningful "where this
54
54
  * foundation lives" pointer shown in `list-templates` output and post-create
55
- * messages. It mirrors the URL the loader builds at compile time, but does
56
- * not itself drive resolution — the loader builds its own URL from
57
- * `foundation.ref` + the configured base.
55
+ * messages. It does not itself drive resolution — the loader builds its own URL
56
+ * from `foundation.ref` + the configured base.
57
+ *
58
+ * ⚠️ **It is built from the same helper the loader resolves through**
59
+ * (`registry-base.js::foundationUrl`), and that is load-bearing rather than tidy.
60
+ * Until 2026-08-22 this file held its own hardcoded base, so it ignored
61
+ * `UNIWEB_REGISTRY_URL`: pointing unipress at a local registry left
62
+ * `list-templates` printing the GitHub Pages URL — an address the tool was not
63
+ * using, reported at exactly the moment someone was debugging which address it
64
+ * used. "Display only" is not "free to be wrong."
65
+ *
66
+ * ⛔ There is also a live fallback that DOES resolve from this field:
67
+ * `foundation-loader.js::resolveCatalogRef` fetches `source.url` directly when an
68
+ * entry carries no registry-shaped `foundation.ref` (the v0.1 legacy shape). No
69
+ * current entry hits it — all 8 carry a ref — but an entry added without one
70
+ * would resolve through this URL. Sharing the helper means that path honours the
71
+ * override too, instead of silently pinning to our origin.
58
72
  *
59
73
  * ─────────────────────────────────────────────────────────────────────────
60
74
  * Storage format
@@ -62,14 +76,17 @@
62
76
  *
63
77
  * Stored as a plain JS module (not YAML) so `bun build --compile` can
64
78
  * inline it at bundle time. The compiled binary has no filesystem for
65
- * runtime reads.
79
+ * runtime reads. Its one import (`registry-base.js`) is a zero-import leaf, so
80
+ * the catalog stays inlinable; the base is still read from the environment at
81
+ * call time rather than frozen into the binary.
66
82
  */
67
83
 
68
- const PUBLIC_FOUNDATIONS_BASE = 'https://uniweb.github.io/unipress/foundations'
84
+ import { foundationUrl } from './registry-base.js'
69
85
 
70
- function publicUrl(name, version) {
71
- return `${PUBLIC_FOUNDATIONS_BASE}/${name}/${version}/entry.js`
72
- }
86
+ // `foundationUrl` is the SAME helper foundation-loader.js resolves through, so
87
+ // what we show here is what compile will fetch — including under a
88
+ // UNIWEB_REGISTRY_URL override, which the old local constant ignored.
89
+ const publicUrl = (name, version) => foundationUrl(name, version)
73
90
 
74
91
  const BOOK_FOUNDATION = {
75
92
  ref: '@uniweb/book@0.4.2',
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The unipress foundation registry — its origin and its URL rule.
3
+ *
4
+ * ## Why this is a module and not two constants
5
+ *
6
+ * unipress's bundled foundations are static artifacts on the unipress repo's
7
+ * GitHub Pages site, laid out as:
8
+ *
9
+ * <base>/foundations/<name>/<version>/entry.js
10
+ *
11
+ * That layout is **ours** — `.github/workflows/deploy-foundations.yml` writes it
12
+ * — so a default origin is the correct answer rather than a guessed one, and the
13
+ * filename rule is ours to name. But the base is overridable (`UNIWEB_REGISTRY_URL`),
14
+ * which makes this a **writer/reader pair**:
15
+ *
16
+ * reader foundation-loader.js buildRegistryUrl() — what `compile` FETCHES
17
+ * reader foundations-data.js each entry's `foundation.source.url` — what
18
+ * `create` and `list-templates` SHOW the user
19
+ *
20
+ * Before 2026-08-22 those held two hardcoded spellings of one base, decomposed
21
+ * differently — `foundations-data.js` baked `/foundations` into its constant while
22
+ * the loader kept it out and appended it when building. So the two edits needed to
23
+ * move the base did not look alike, and nothing caught a half-finished move. Worse,
24
+ * only the loader honoured `UNIWEB_REGISTRY_URL`: point unipress at a local
25
+ * registry and `list-templates` still printed the GitHub Pages URL, which is not
26
+ * the URL `compile` would fetch. A tool that reports an address it is not using is
27
+ * a bad instrument, and diagnostics is exactly when the override is in play.
28
+ *
29
+ * Same treatment as `@uniweb/core/icon-corpus`, for the same reason: one helper,
30
+ * no second spelling.
31
+ *
32
+ * ## ⛔ Keep this a LEAF — zero imports
33
+ *
34
+ * The import graph runs `foundation-loader.js` → `catalog.js` → `foundations-data.js`,
35
+ * so the catalog cannot import the loader — that is a cycle, and it is why the
36
+ * constant was duplicated in the first place rather than through carelessness. A
37
+ * leaf both sides import is the only shape that works. Keeping it import-free also
38
+ * keeps `bun build --compile` able to inline the catalog (see foundations-data.js
39
+ * § Storage format).
40
+ *
41
+ * @module unipress/registry-base
42
+ */
43
+
44
+ /**
45
+ * Where unipress publishes its own foundations.
46
+ *
47
+ * Not a fallback for a missing address — it is the address of OUR artifact, and it
48
+ * is what makes `unipress create --template book` work with nothing configured.
49
+ */
50
+ export const DEFAULT_REGISTRY_BASE = 'https://uniweb.github.io/unipress'
51
+
52
+ /**
53
+ * The configured registry base: `UNIWEB_REGISTRY_URL` when set, else ours.
54
+ *
55
+ * Read at call time, not frozen at import, so a caller that sets the variable
56
+ * programmatically before invoking a command still gets it.
57
+ *
58
+ * @returns {string} the base, without a trailing slash
59
+ */
60
+ export function getRegistryBase() {
61
+ const raw = process.env.UNIWEB_REGISTRY_URL || DEFAULT_REGISTRY_BASE
62
+ return String(raw).replace(/\/+$/, '')
63
+ }
64
+
65
+ /**
66
+ * The registry path for one foundation, relative to any base serving it.
67
+ *
68
+ * ⚠️ The **namespace is implicit** — every foundation under this layout is
69
+ * `@uniweb/`, so a ref's scope does not appear in the path. A custom
70
+ * `UNIWEB_REGISTRY_URL` that needs a per-namespace split carries it in the base
71
+ * (e.g. `https://my.host/by-ns/uniweb`) or answers it with a redirect.
72
+ *
73
+ * @param {string} name - the foundation name, unscoped (e.g. 'book')
74
+ * @param {string} version
75
+ * @returns {string}
76
+ */
77
+ export function foundationPath(name, version) {
78
+ return `foundations/${name}/${version}/entry.js`
79
+ }
80
+
81
+ /**
82
+ * The full URL for one foundation against a serving base.
83
+ *
84
+ * @param {string} name - the foundation name, unscoped
85
+ * @param {string} version
86
+ * @param {string} [base] - serving base; defaults to the configured one
87
+ * @returns {string}
88
+ */
89
+ export function foundationUrl(name, version, base = getRegistryBase()) {
90
+ return `${String(base).replace(/\/+$/, '')}/${foundationPath(name, version)}`
91
+ }