@uniweb/unipress 0.4.18 → 0.4.19
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/README.md +2 -2
- package/docs/troubleshooting.md +3 -3
- package/package.json +3 -3
- package/src/foundation-fetch.js +78 -21
- package/src/foundation-loader.js +8 -8
- package/src/foundations-data.js +2 -2
- package/src/orchestrator.js +1 -1
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ The first PDF run downloads Typst 0.14.2 to `~/Library/Caches/unipress/typst/0.1
|
|
|
90
90
|
Any foundation that declares an `outputs: { … }` map on its default export can drive unipress. Point `document.yml`'s `foundation:` at:
|
|
91
91
|
|
|
92
92
|
- a registry ref: `@<namespace>/<name>@<version>` — fetched from the Uniweb registry, cached locally,
|
|
93
|
-
- a URL: `https://…/
|
|
93
|
+
- a URL: `https://…/entry.js`,
|
|
94
94
|
- a local filesystem path: `./foundation`, `/abs/path`, etc.
|
|
95
95
|
|
|
96
96
|
The local-path form is the everyday dev loop — point unipress at a foundation directory you're iterating on, no publish step needed:
|
|
@@ -113,7 +113,7 @@ unipress compile <dir> [options]
|
|
|
113
113
|
Overrides the format: field in document.yml.
|
|
114
114
|
--foundation <ref> Override document.yml's foundation. Accepts:
|
|
115
115
|
- registry ref: @<namespace>/<name>@<version>
|
|
116
|
-
- URL: https://…/
|
|
116
|
+
- URL: https://…/entry.js
|
|
117
117
|
- path: ./foundation, /abs/path, …
|
|
118
118
|
--out <path> Output file (default: ./<dir-basename>.<ext>).
|
|
119
119
|
--config <path> Explicit config file (default: <dir>/unipress.config.js).
|
package/docs/troubleshooting.md
CHANGED
|
@@ -54,7 +54,7 @@ Set `foundation:` in `document.yml`, or pass `--foundation <ref>`. Four ref form
|
|
|
54
54
|
foundation: "@uniweb/book@0.1.0"
|
|
55
55
|
|
|
56
56
|
# Or a full URL.
|
|
57
|
-
foundation: "https://example.com/foundations/my-foundation/
|
|
57
|
+
foundation: "https://example.com/foundations/my-foundation/entry.js"
|
|
58
58
|
|
|
59
59
|
# Or a local filesystem path (relative to document.yml).
|
|
60
60
|
foundation: "./path/to/foundation"
|
|
@@ -72,7 +72,7 @@ The package isn't installed anywhere `Node` can see from the content directory.
|
|
|
72
72
|
|
|
73
73
|
Inside a pnpm workspace, foundations linked via `pnpm-workspace.yaml` globs resolve automatically.
|
|
74
74
|
|
|
75
|
-
## `FoundationResolutionError: expected built foundation at <path>/dist/
|
|
75
|
+
## `FoundationResolutionError: expected built foundation at <path>/dist/entry.js`
|
|
76
76
|
|
|
77
77
|
The foundation package isn't built. From the workspace root:
|
|
78
78
|
|
|
@@ -80,7 +80,7 @@ The foundation package isn't built. From the workspace root:
|
|
|
80
80
|
pnpm --filter <foundation-name> build
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
unipress consumes the built artifact (`dist/
|
|
83
|
+
unipress consumes the built artifact (`dist/entry.js`), not the source.
|
|
84
84
|
|
|
85
85
|
## `CompileError: foundation does not expose compileDocument`
|
|
86
86
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/unipress",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.19",
|
|
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,10 +49,10 @@
|
|
|
49
49
|
"prompts": "^2.4.2",
|
|
50
50
|
"react": "^19.0.0",
|
|
51
51
|
"react-dom": "^19.0.0",
|
|
52
|
-
"@uniweb/runtime": "0.8.18",
|
|
53
52
|
"@uniweb/build": "0.14.13",
|
|
54
|
-
"@uniweb/
|
|
53
|
+
"@uniweb/runtime": "0.8.18",
|
|
55
54
|
"@uniweb/content-reader": "1.1.12",
|
|
55
|
+
"@uniweb/core": "0.7.13",
|
|
56
56
|
"@uniweb/semantic-parser": "1.1.17"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
package/src/foundation-fetch.js
CHANGED
|
@@ -2,23 +2,29 @@
|
|
|
2
2
|
// full dist tree so Node can import it.
|
|
3
3
|
//
|
|
4
4
|
// Cache layout:
|
|
5
|
-
// <unipress cache>/foundations/<host>/<pathish>/
|
|
5
|
+
// <unipress cache>/foundations/<host>/<pathish>/entry.js
|
|
6
6
|
// <unipress cache>/foundations/<host>/<pathish>/<chunk>.js
|
|
7
7
|
// ... etc
|
|
8
8
|
//
|
|
9
9
|
// where <pathish> is the URL's path with leading slashes stripped and
|
|
10
|
-
// the trailing
|
|
10
|
+
// the trailing entry filename stripped — the same URL always maps to
|
|
11
11
|
// the same cache path.
|
|
12
12
|
//
|
|
13
|
-
// A built foundation is a small entry (`
|
|
14
|
-
//
|
|
15
|
-
// dynamically imported from those).
|
|
16
|
-
// import graph: it downloads
|
|
17
|
-
// (static and dynamic), fetches each, and
|
|
18
|
-
// cover @uniweb/build's output — all sibling
|
|
19
|
-
//
|
|
20
|
-
// (external like 'react') or absolute URLs and are not part of
|
|
21
|
-
// fetch graph.
|
|
13
|
+
// A built foundation is a small entry (`entry.js`, the @uniweb/build
|
|
14
|
+
// standard output name) plus a set of sibling chunks referenced by
|
|
15
|
+
// relative imports (and further chunks dynamically imported from those).
|
|
16
|
+
// `fetchFoundationToCache` walks the import graph: it downloads the entry,
|
|
17
|
+
// scans for `./*.js` imports (static and dynamic), fetches each, and
|
|
18
|
+
// recurses. That's enough to cover @uniweb/build's output — all sibling
|
|
19
|
+
// chunks live next to the entry. Imports NOT starting with './' are bare
|
|
20
|
+
// specifiers (external like 'react') or absolute URLs and are not part of
|
|
21
|
+
// the fetch graph.
|
|
22
|
+
//
|
|
23
|
+
// Entry name: the framework standard is `entry.js`. Foundations published
|
|
24
|
+
// before @uniweb/build's entry rename serve `foundation.js`, so the fetch
|
|
25
|
+
// probes `entry.js` first and falls back to `foundation.js` (see
|
|
26
|
+
// ENTRY_CANDIDATES) — both when probing the registry and when reusing a
|
|
27
|
+
// cached download.
|
|
22
28
|
//
|
|
23
29
|
// Integrity: the fetch is unverified today. Once the Uniweb registry
|
|
24
30
|
// publishes integrity data, the catalog entry can carry an `integrity:`
|
|
@@ -52,6 +58,13 @@ const EXTERNAL_PACKAGES = [
|
|
|
52
58
|
// picks up Node-core strings embedded in bundled commonjs shims.
|
|
53
59
|
const RELATIVE_JS_IMPORT = /(?:from\s*|\bimport\s*\(?\s*)['"](\.\/[^'"]+\.(?:js|mjs|cjs))['"]/g
|
|
54
60
|
|
|
61
|
+
// Foundation entry filename. The framework standard is `entry.js`;
|
|
62
|
+
// `foundation.js` is the legacy name some already-published foundations
|
|
63
|
+
// still serve. Prefer the standard, fall back to the legacy name — both
|
|
64
|
+
// when probing the registry and when reusing a cached download.
|
|
65
|
+
const ENTRY_CANDIDATES = ['entry.js', 'foundation.js']
|
|
66
|
+
const ENTRY_FILENAME_RE = /\/(?:entry|foundation)\.js$/i
|
|
67
|
+
|
|
55
68
|
export function getFoundationCacheDir(url) {
|
|
56
69
|
let parsed
|
|
57
70
|
try {
|
|
@@ -63,33 +76,49 @@ export function getFoundationCacheDir(url) {
|
|
|
63
76
|
)
|
|
64
77
|
}
|
|
65
78
|
const host = parsed.host.replace(/:/g, '_')
|
|
66
|
-
|
|
79
|
+
// Strip a leading slash and any trailing entry filename or slash so the
|
|
80
|
+
// same foundation maps to one cache dir whether the URL carries entry.js,
|
|
81
|
+
// foundation.js, or just the containing directory.
|
|
82
|
+
let p = parsed.pathname.replace(/^\/+/, '').replace(ENTRY_FILENAME_RE, '').replace(/\/$/, '')
|
|
67
83
|
return join(getCacheDir(), 'foundations', host, p)
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
export function getFoundationCachePath(url) {
|
|
71
|
-
|
|
87
|
+
const dir = getFoundationCacheDir(url)
|
|
88
|
+
for (const name of ENTRY_CANDIDATES) {
|
|
89
|
+
const candidate = join(dir, name)
|
|
90
|
+
if (existsSync(candidate)) return candidate
|
|
91
|
+
}
|
|
92
|
+
return join(dir, ENTRY_CANDIDATES[0])
|
|
72
93
|
}
|
|
73
94
|
|
|
74
95
|
export async function fetchFoundationToCache(url, { onProgress = () => {} } = {}) {
|
|
75
96
|
const cacheDir = getFoundationCacheDir(url)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
|
|
98
|
+
// Reuse a prior download under either the standard or legacy entry name.
|
|
99
|
+
for (const name of ENTRY_CANDIDATES) {
|
|
100
|
+
const cached = join(cacheDir, name)
|
|
101
|
+
if (existsSync(cached)) {
|
|
102
|
+
onProgress(`using cached foundation: ${cached}`)
|
|
103
|
+
return cached
|
|
104
|
+
}
|
|
80
105
|
}
|
|
81
106
|
onProgress(`downloading foundation graph from ${url}`)
|
|
82
107
|
await mkdir(cacheDir, { recursive: true })
|
|
83
108
|
|
|
84
109
|
// Directory URL the individual chunk files live under. Works whether
|
|
85
|
-
// the input URL ends in
|
|
110
|
+
// the input URL ends in an entry filename or at the containing directory.
|
|
86
111
|
const urlObj = new URL(url)
|
|
87
|
-
const pathname = urlObj.pathname.replace(
|
|
112
|
+
const pathname = urlObj.pathname.replace(ENTRY_FILENAME_RE, '/')
|
|
88
113
|
urlObj.pathname = pathname.endsWith('/') ? pathname : pathname + '/'
|
|
89
114
|
const baseUrl = urlObj.toString()
|
|
90
115
|
|
|
116
|
+
// Prefer entry.js (framework standard); fall back to foundation.js
|
|
117
|
+
// (foundations published before the entry rename).
|
|
118
|
+
const entryName = await pickRemoteEntry(baseUrl, onProgress)
|
|
119
|
+
|
|
91
120
|
const fetched = new Set()
|
|
92
|
-
const queue = [
|
|
121
|
+
const queue = [entryName]
|
|
93
122
|
while (queue.length > 0) {
|
|
94
123
|
const rel = queue.shift()
|
|
95
124
|
if (fetched.has(rel)) continue
|
|
@@ -110,14 +139,42 @@ export async function fetchFoundationToCache(url, { onProgress = () => {} } = {}
|
|
|
110
139
|
}
|
|
111
140
|
}
|
|
112
141
|
await linkExternals(cacheDir, onProgress)
|
|
142
|
+
const entryPath = join(cacheDir, entryName)
|
|
113
143
|
onProgress(`foundation cached at ${cacheDir} (${fetched.size} file(s))`)
|
|
114
144
|
return entryPath
|
|
115
145
|
}
|
|
116
146
|
|
|
147
|
+
// Probe the registry for the foundation's entry file. Returns the first
|
|
148
|
+
// of ENTRY_CANDIDATES the host serves (prefer `entry.js`, tolerate the
|
|
149
|
+
// legacy `foundation.js`). A plain GET is used rather than HEAD so the
|
|
150
|
+
// check works against any static host; only the status is needed, so the
|
|
151
|
+
// body is cancelled — the graph walk re-fetches the entry.
|
|
152
|
+
async function pickRemoteEntry(baseUrl, onProgress) {
|
|
153
|
+
for (const name of ENTRY_CANDIDATES) {
|
|
154
|
+
const probeUrl = new URL(name, baseUrl).toString()
|
|
155
|
+
let res
|
|
156
|
+
try {
|
|
157
|
+
res = await fetch(probeUrl)
|
|
158
|
+
} catch {
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
res.body?.cancel?.()
|
|
162
|
+
if (res.ok) return name
|
|
163
|
+
if (name !== ENTRY_CANDIDATES[ENTRY_CANDIDATES.length - 1]) {
|
|
164
|
+
onProgress(` ${name} not found, trying legacy entry name…`)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
throw new FoundationFetchError(
|
|
168
|
+
`foundation entry not found under ${baseUrl}\n` +
|
|
169
|
+
`tried: ${ENTRY_CANDIDATES.join(', ')}\n` +
|
|
170
|
+
`hint: check the foundation is published at that version`
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
117
174
|
// Make unipress's own copies of the externalized packages reachable
|
|
118
175
|
// from the cache dir. Node's ESM loader walks up from the importing
|
|
119
176
|
// file looking for `node_modules/<name>` — by placing a node_modules
|
|
120
|
-
// directory next to the cached
|
|
177
|
+
// directory next to the cached entry with symlinks to each
|
|
121
178
|
// external's package directory, bare imports inside the foundation
|
|
122
179
|
// resolve to unipress's already-installed copies. This keeps a single
|
|
123
180
|
// React instance (unipress's) across host and foundation.
|
package/src/foundation-loader.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
//
|
|
8
8
|
// Five accepted ref forms:
|
|
9
9
|
//
|
|
10
|
-
// 1. URL ............ https://.../
|
|
11
|
-
// 2. Local path ..... ./foo, ../foo, /abs/foo, ./foo/dist/
|
|
10
|
+
// 1. URL ............ https://.../entry.js — downloaded + cached
|
|
11
|
+
// 2. Local path ..... ./foo, ../foo, /abs/foo, ./foo/dist/entry.js
|
|
12
12
|
// 3. Registry ref ... @<namespace>/<name>@<version> — constructed into a
|
|
13
13
|
// URL via the registry base (UNIWEB_REGISTRY_URL or
|
|
14
14
|
// the production default), then resolved as URL
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
//
|
|
25
25
|
// The chosen entry for a package is its `exports['./dist']` map, NOT the
|
|
26
26
|
// default `.` entry — the default points at source (_entry.generated.js)
|
|
27
|
-
// and is only valid inside a Vite build. `dist/
|
|
27
|
+
// and is only valid inside a Vite build. `dist/entry.js` is the
|
|
28
28
|
// built artifact (the federated module) that Node can import.
|
|
29
29
|
|
|
30
30
|
import { existsSync, statSync, readFileSync } from 'node:fs'
|
|
@@ -45,14 +45,14 @@ const REGISTRY_REF_PATTERN = /^@([a-z0-9][a-z0-9._-]*)\/([a-z0-9][a-z0-9._-]*)@(
|
|
|
45
45
|
// unipress's bundled foundations are distributed as static artifacts on
|
|
46
46
|
// the unipress repo's GitHub Pages site. URL pattern:
|
|
47
47
|
//
|
|
48
|
-
// https://uniweb.github.io/unipress/foundations/<name>/<version>/
|
|
48
|
+
// https://uniweb.github.io/unipress/foundations/<name>/<version>/entry.js
|
|
49
49
|
//
|
|
50
50
|
// The namespace portion of a registry ref is implicit — every foundation
|
|
51
51
|
// served from this base is under @uniweb/. Set UNIWEB_REGISTRY_URL to
|
|
52
52
|
// override (e.g., for testing against a local http.server during foundation
|
|
53
53
|
// development, or for users with a private alternative).
|
|
54
54
|
const DEFAULT_REGISTRY_BASE = 'https://uniweb.github.io/unipress'
|
|
55
|
-
const DEFAULT_BUILT_ENTRY = 'dist/
|
|
55
|
+
const DEFAULT_BUILT_ENTRY = 'dist/entry.js'
|
|
56
56
|
|
|
57
57
|
function getRegistryBase() {
|
|
58
58
|
const raw = process.env.UNIWEB_REGISTRY_URL || DEFAULT_REGISTRY_BASE
|
|
@@ -67,7 +67,7 @@ function getRegistryBase() {
|
|
|
67
67
|
// `UNIWEB_REGISTRY_URL=https://my.host/by-ns/uniweb` and live without the
|
|
68
68
|
// per-namespace split, or maintain a redirect rule on that host).
|
|
69
69
|
function buildRegistryUrl(namespace, name, version) {
|
|
70
|
-
return `${getRegistryBase()}/foundations/${name}/${version}/
|
|
70
|
+
return `${getRegistryBase()}/foundations/${name}/${version}/entry.js`
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export async function resolveFoundationRef(ref, { anchorDir, onProgress = () => {} } = {}) {
|
|
@@ -188,7 +188,7 @@ async function resolvePackageRef(name, anchorDir) {
|
|
|
188
188
|
: null
|
|
189
189
|
|
|
190
190
|
// Prefer the explicit `./dist` exports subpath if declared; otherwise
|
|
191
|
-
// fall back to the convention `dist/
|
|
191
|
+
// fall back to the convention `dist/entry.js`.
|
|
192
192
|
const distExport = pickBuiltEntry(pkg?.exports)
|
|
193
193
|
const file = distExport
|
|
194
194
|
? resolve(pkgRoot, distExport)
|
|
@@ -218,7 +218,7 @@ function findPackageDir(name, fromDir) {
|
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
// A foundation's `exports['./dist']` entry points at the built `
|
|
221
|
+
// A foundation's `exports['./dist']` entry points at the built `entry.js`.
|
|
222
222
|
// The value can be a string or a conditional-exports object; we accept both.
|
|
223
223
|
function pickBuiltEntry(exports) {
|
|
224
224
|
if (!exports || typeof exports !== 'object') return null
|
package/src/foundations-data.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* Foundations are deployed as static artifacts to the unipress repo's
|
|
17
17
|
* GitHub Pages site. URL pattern:
|
|
18
18
|
*
|
|
19
|
-
* https://uniweb.github.io/unipress/foundations/<name>/<version>/
|
|
19
|
+
* https://uniweb.github.io/unipress/foundations/<name>/<version>/entry.js
|
|
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
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
const PUBLIC_FOUNDATIONS_BASE = 'https://uniweb.github.io/unipress/foundations'
|
|
69
69
|
|
|
70
70
|
function publicUrl(name, version) {
|
|
71
|
-
return `${PUBLIC_FOUNDATIONS_BASE}/${name}/${version}/
|
|
71
|
+
return `${PUBLIC_FOUNDATIONS_BASE}/${name}/${version}/entry.js`
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const BOOK_FOUNDATION = {
|
package/src/orchestrator.js
CHANGED
|
@@ -37,7 +37,7 @@ export async function importFoundation(resolvedPath) {
|
|
|
37
37
|
throw new FoundationResolutionError(
|
|
38
38
|
`failed to import foundation at ${resolvedPath}\n` +
|
|
39
39
|
`cause: ${err.message}\n` +
|
|
40
|
-
`hint: confirm the foundation was built with @uniweb/build (dist/
|
|
40
|
+
`hint: confirm the foundation was built with @uniweb/build (dist/entry.js)`
|
|
41
41
|
)
|
|
42
42
|
}
|
|
43
43
|
}
|