@uniweb/build 0.14.22 → 0.14.23
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/build",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.23",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
"js-yaml": "^4.1.0",
|
|
60
60
|
"sharp": "^0.33.2",
|
|
61
61
|
"yaml": "^2.5.0",
|
|
62
|
-
"@uniweb/theming": "0.1.
|
|
62
|
+
"@uniweb/theming": "0.1.5",
|
|
63
63
|
"@uniweb/content-writer": "0.2.6"
|
|
64
64
|
},
|
|
65
65
|
"optionalDependencies": {
|
|
66
|
+
"@uniweb/runtime": "0.8.22",
|
|
66
67
|
"@uniweb/content-reader": "1.1.12",
|
|
67
|
-
"@uniweb/schemas": "0.2.4"
|
|
68
|
-
"@uniweb/runtime": "0.8.21"
|
|
68
|
+
"@uniweb/schemas": "0.2.4"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@tailwindcss/vite": "^4.0.0",
|
|
75
75
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
76
76
|
"vite-plugin-svgr": "^4.0.0",
|
|
77
|
-
"@uniweb/core": "0.7.
|
|
77
|
+
"@uniweb/core": "0.7.16"
|
|
78
78
|
},
|
|
79
79
|
"peerDependenciesMeta": {
|
|
80
80
|
"vite": {
|
package/src/prerender.js
CHANGED
|
@@ -138,9 +138,20 @@ async function executeAllFetches(siteContent, siteDir, onProgress, localeInfo) {
|
|
|
138
138
|
* @param {function} onProgress - Progress callback
|
|
139
139
|
* @returns {Array} Expanded pages array with dynamic pages replaced by concrete instances
|
|
140
140
|
*/
|
|
141
|
-
function expandDynamicPages(pages, pageFetchedData, onProgress) {
|
|
141
|
+
export function expandDynamicPages(pages, pageFetchedData, onProgress) {
|
|
142
142
|
const expandedPages = []
|
|
143
143
|
|
|
144
|
+
// Static pages win over the dynamic `[slug]` catch-all, matching the SPA's
|
|
145
|
+
// route resolution (Website.getPage checks exact static routes before the
|
|
146
|
+
// `:param` loop). Without this guard, a record whose param value collides
|
|
147
|
+
// with a static sibling's segment (e.g. slug:'about' + a static /blog/about)
|
|
148
|
+
// would emit a duplicate concrete route; the write loops are keyed on
|
|
149
|
+
// page.route and last-writer-wins, silently clobbering the static page's
|
|
150
|
+
// HTML. Collect the static routes up front so we can skip + warn on collision.
|
|
151
|
+
const staticRoutes = new Set(
|
|
152
|
+
pages.filter((p) => !p.isDynamic).map((p) => p.route)
|
|
153
|
+
)
|
|
154
|
+
|
|
144
155
|
for (const page of pages) {
|
|
145
156
|
if (!page.isDynamic) {
|
|
146
157
|
// Regular page - include as-is
|
|
@@ -187,6 +198,13 @@ function expandDynamicPages(pages, pageFetchedData, onProgress) {
|
|
|
187
198
|
// Create concrete route: /blog/:slug → /blog/my-post
|
|
188
199
|
const concreteRoute = page.route.replace(`:${paramName}`, paramValue)
|
|
189
200
|
|
|
201
|
+
// Static sibling wins: skip a record whose concrete route collides with
|
|
202
|
+
// an existing static page rather than overwriting its HTML at write time.
|
|
203
|
+
if (staticRoutes.has(concreteRoute)) {
|
|
204
|
+
onProgress(` Skipping ${concreteRoute} — a static page already claims this route (${paramName}:'${paramValue}')`)
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
|
|
190
208
|
// Deep clone the page with modifications
|
|
191
209
|
const concretePage = JSON.parse(JSON.stringify(page))
|
|
192
210
|
concretePage.route = concreteRoute
|
|
@@ -95,7 +95,9 @@ export async function buildSiteData({
|
|
|
95
95
|
|
|
96
96
|
// 1. Collect content (pages, sections, theme, config, assets manifest).
|
|
97
97
|
// No vite needed — collectSiteContent is a plain async function.
|
|
98
|
-
|
|
98
|
+
// dropUnpublished: link mode is always a published deploy — prune hidden
|
|
99
|
+
// pages + their subtree so drafts never reach the served site.
|
|
100
|
+
let siteContent = await collectSiteContent(resolvedSiteRoot, { foundationPath, dropUnpublished: true })
|
|
99
101
|
|
|
100
102
|
// 2. Compile content collections (file-based markdown/yaml/json).
|
|
101
103
|
// `writeCollectionFiles` lands them under `<siteRoot>/public/data/`;
|
|
@@ -29,7 +29,7 @@ import { existsSync, statSync, realpathSync } from 'node:fs'
|
|
|
29
29
|
import yaml from 'js-yaml'
|
|
30
30
|
import { collectSectionAssets, mergeAssetCollections, collectConfigAssets } from './assets.js'
|
|
31
31
|
import { collectSectionIcons, mergeIconCollections, buildIconManifest } from './icons.js'
|
|
32
|
-
import { normalizeHideIn } from './nav-visibility.js'
|
|
32
|
+
import { normalizeHideIn, dropUnpublishedPages } from './nav-visibility.js'
|
|
33
33
|
import { parseFetchConfig, singularize } from './data-fetcher.js'
|
|
34
34
|
import { buildTheme, extractFoundationVars } from '../theme/index.js'
|
|
35
35
|
|
|
@@ -1660,13 +1660,16 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1660
1660
|
}
|
|
1661
1661
|
}
|
|
1662
1662
|
|
|
1663
|
-
// When pages: is strict (no '...'),
|
|
1663
|
+
// When pages: is strict (no '...'), suppress unlisted direct children from
|
|
1664
|
+
// every nav area — but keep them ROUTED (strict is a nav filter, not a draft
|
|
1665
|
+
// toggle). This is the nav axis (`hideIn: ['*']`), not the reachability axis
|
|
1666
|
+
// (`hidden`), which would drop the page from the published output entirely.
|
|
1664
1667
|
if (strictPageNamesFM) {
|
|
1665
1668
|
for (const page of pages) {
|
|
1666
1669
|
const childName = getDirectChildName(page.route, parentRoute)
|
|
1667
1670
|
|| (page.sourcePath ? getDirectChildName(page.sourcePath, parentRoute) : null)
|
|
1668
|
-
if (childName && !strictPageNamesFM.has(childName) && !page.
|
|
1669
|
-
page.
|
|
1671
|
+
if (childName && !strictPageNamesFM.has(childName) && !page.hideIn?.includes('*')) {
|
|
1672
|
+
page.hideIn = [...(page.hideIn || []), '*']
|
|
1670
1673
|
}
|
|
1671
1674
|
}
|
|
1672
1675
|
}
|
|
@@ -1797,13 +1800,15 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1797
1800
|
}
|
|
1798
1801
|
}
|
|
1799
1802
|
|
|
1800
|
-
// When pages: is strict (no '...'),
|
|
1803
|
+
// When pages: is strict (no '...'), suppress unlisted direct children from every
|
|
1804
|
+
// nav area but keep them ROUTED — the nav axis (`hideIn: ['*']`), not the
|
|
1805
|
+
// reachability axis (`hidden`), which would drop them from the published output.
|
|
1801
1806
|
if (strictPageNames) {
|
|
1802
1807
|
for (const page of pages) {
|
|
1803
1808
|
const childName = getDirectChildName(page.route, parentRoute)
|
|
1804
1809
|
|| (page.sourcePath ? getDirectChildName(page.sourcePath, parentRoute) : null)
|
|
1805
|
-
if (childName && !strictPageNames.has(childName) && !page.
|
|
1806
|
-
page.
|
|
1810
|
+
if (childName && !strictPageNames.has(childName) && !page.hideIn?.includes('*')) {
|
|
1811
|
+
page.hideIn = [...(page.hideIn || []), '*']
|
|
1807
1812
|
}
|
|
1808
1813
|
}
|
|
1809
1814
|
}
|
|
@@ -2008,7 +2013,7 @@ async function collectLayouts(layoutDir, siteRoot, layoutNames = new Set()) {
|
|
|
2008
2013
|
* @returns {Promise<Object>} Site content object with assets manifest
|
|
2009
2014
|
*/
|
|
2010
2015
|
export async function collectSiteContent(sitePath, options = {}) {
|
|
2011
|
-
const { foundationPath, configFile = 'site.yml', profile: profileName } = options
|
|
2016
|
+
const { foundationPath, configFile = 'site.yml', profile: profileName, dropUnpublished = false } = options
|
|
2012
2017
|
|
|
2013
2018
|
// Read site config and raw theme config
|
|
2014
2019
|
const siteConfig = await readYamlFile(join(sitePath, configFile))
|
|
@@ -2205,7 +2210,9 @@ export async function collectSiteContent(sitePath, options = {}) {
|
|
|
2205
2210
|
...processedTheme,
|
|
2206
2211
|
css: themeCSS
|
|
2207
2212
|
},
|
|
2208
|
-
pages
|
|
2213
|
+
// Reachability axis: on the published build paths, drop `hidden` pages and
|
|
2214
|
+
// their whole subtree (cascade). Dev keeps them so drafts stay previewable.
|
|
2215
|
+
pages: dropUnpublished ? dropUnpublishedPages(pages) : pages,
|
|
2209
2216
|
// Layout area sets: { default: { header: page, footer: page, ... }, marketing: { ... } }
|
|
2210
2217
|
layouts,
|
|
2211
2218
|
notFound,
|
package/src/site/data-fetcher.js
CHANGED
|
@@ -300,6 +300,9 @@ export function parseFetchConfig(fetch) {
|
|
|
300
300
|
where: fetch.where,
|
|
301
301
|
limit: fetch.limit,
|
|
302
302
|
sort: fetch.sort,
|
|
303
|
+
// Canonical detail page for a list card's href (page:<stable_id> ref;
|
|
304
|
+
// resolved to a route template + interpolated per record at runtime).
|
|
305
|
+
detailPage: fetch.detailPage,
|
|
303
306
|
// Legacy post-processing (deprecated, see warning above)
|
|
304
307
|
filter: fetch.filter,
|
|
305
308
|
}
|
|
@@ -313,6 +316,7 @@ export function parseFetchConfig(fetch) {
|
|
|
313
316
|
merge = false,
|
|
314
317
|
transform,
|
|
315
318
|
detail,
|
|
319
|
+
detailPage,
|
|
316
320
|
// Query operators
|
|
317
321
|
where,
|
|
318
322
|
limit,
|
|
@@ -334,6 +338,8 @@ export function parseFetchConfig(fetch) {
|
|
|
334
338
|
merge,
|
|
335
339
|
transform,
|
|
336
340
|
detail,
|
|
341
|
+
// Canonical detail page for a list card's href (page:<stable_id>).
|
|
342
|
+
detailPage,
|
|
337
343
|
// Query operators
|
|
338
344
|
where,
|
|
339
345
|
limit,
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
// Page-visibility helpers — the two orthogonal axes a page carries:
|
|
2
|
+
// • Reachability — `hidden: bool`. `true` = excluded from the PUBLISHED output
|
|
3
|
+
// entirely (see `dropUnpublishedPages`). Still previewable in `uniweb dev`.
|
|
4
|
+
// • Nav placement — `hideIn: string[]`. Which nav areas suppress the page while
|
|
5
|
+
// it IS routed (see `normalizeHideIn`). `['*']` = suppressed from every area.
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* Normalize a page/folder config's navigation visibility into a `hideIn` array —
|
|
3
9
|
* the list of named nav areas a page is suppressed from (layout-area names like
|
|
4
10
|
* 'header', 'footer', or any foundation-declared area). The canonical form behind
|
|
5
11
|
* the runtime `hideIn`, the sync `hide_in` field, and `getPageHierarchy({ for })`.
|
|
12
|
+
* The sentinel `'*'` means "suppressed from every nav area" (still routed) — it is
|
|
13
|
+
* a normal array element, interpreted by the runtime, not special-cased here.
|
|
6
14
|
*
|
|
7
15
|
* Reads the canonical `hideIn` (an array, or a single string for convenience) and
|
|
8
16
|
* folds in the legacy `hideInHeader` / `hideInFooter` booleans for back-compat.
|
|
9
|
-
* Deduped; declaration order preserved. `hidden` (
|
|
17
|
+
* Deduped; declaration order preserved. `hidden` (reachability) is a separate axis.
|
|
10
18
|
*
|
|
11
19
|
* @param {object} config - a parsed page.yml / folder.yml config (or page data)
|
|
12
20
|
* @returns {string[]}
|
|
@@ -27,3 +35,35 @@ export function normalizeHideIn(config = {}) {
|
|
|
27
35
|
if (config.hideInFooter) add('footer')
|
|
28
36
|
return out
|
|
29
37
|
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Drop unpublished pages — the reachability axis. A page with `hidden: true` is
|
|
41
|
+
* excluded from the PUBLISHED output entirely, and the exclusion CASCADES to its
|
|
42
|
+
* whole subtree: drafting a container drafts the branch. Applied only on the
|
|
43
|
+
* published build paths (`uniweb build` / link-mode deploy); `uniweb dev` keeps
|
|
44
|
+
* hidden pages so in-progress work stays previewable by direct URL.
|
|
45
|
+
*
|
|
46
|
+
* Cascade is resolved via the parent-route chain (the same `parent` route strings
|
|
47
|
+
* the hierarchy is built from), so no surviving page is ever left pointing at a
|
|
48
|
+
* pruned parent — this is what avoids orphaned routes / dangling parent references
|
|
49
|
+
* that a per-node drop would create.
|
|
50
|
+
*
|
|
51
|
+
* @param {Array<object>} pages - collected page data (each with `route`, `parent`)
|
|
52
|
+
* @returns {Array<object>} pages with hidden pages and their descendants removed
|
|
53
|
+
*/
|
|
54
|
+
export function dropUnpublishedPages(pages) {
|
|
55
|
+
if (!Array.isArray(pages) || pages.length === 0) return pages
|
|
56
|
+
const byRoute = new Map(pages.map((p) => [p.route, p]))
|
|
57
|
+
const cache = new Map()
|
|
58
|
+
const isUnpublished = (page, seen = new Set()) => {
|
|
59
|
+
if (!page) return false
|
|
60
|
+
if (cache.has(page.route)) return cache.get(page.route)
|
|
61
|
+
if (seen.has(page.route)) return false // defensive cycle guard (trees don't cycle)
|
|
62
|
+
seen.add(page.route)
|
|
63
|
+
const parent = page.parent ? byRoute.get(page.parent) : null
|
|
64
|
+
const result = page.hidden ? true : (parent ? isUnpublished(parent, seen) : false)
|
|
65
|
+
cache.set(page.route, result)
|
|
66
|
+
return result
|
|
67
|
+
}
|
|
68
|
+
return pages.filter((p) => !isUnpublished(p))
|
|
69
|
+
}
|
package/src/site/plugin.js
CHANGED
|
@@ -582,7 +582,9 @@ export function siteContentPlugin(options = {}) {
|
|
|
582
582
|
async buildStart() {
|
|
583
583
|
// Collect content at build start
|
|
584
584
|
try {
|
|
585
|
-
|
|
585
|
+
// dropUnpublished only on a production build — in dev (serve) hidden
|
|
586
|
+
// pages stay in the graph so in-progress drafts remain previewable.
|
|
587
|
+
siteContent = await collectSiteContent(resolvedSitePath, { foundationPath, dropUnpublished: isProduction })
|
|
586
588
|
headHtml = await loadHeadHtml()
|
|
587
589
|
console.log(`[site-content] Collected ${siteContent.pages?.length || 0} pages`)
|
|
588
590
|
|