@uniweb/build 0.67.1 → 0.69.0
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 +6 -6
- package/src/schema.js +11 -6
- package/src/site/content-collector.js +49 -10
- package/src/site/nav-visibility.js +22 -1
- package/src/uwx/project-writer.js +2 -1
- package/src/uwx/site-project.js +14 -5
- package/src/uwx/site.js +12 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.69.0",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
"js-yaml": "^4.1.0",
|
|
58
58
|
"sharp": "^0.35.3",
|
|
59
59
|
"yaml": "^2.5.0",
|
|
60
|
-
"@uniweb/
|
|
60
|
+
"@uniweb/schemas": "^0.8.3",
|
|
61
61
|
"@uniweb/content-writer": "^0.3.4",
|
|
62
|
-
"@uniweb/
|
|
63
|
-
"@uniweb/
|
|
64
|
-
"@uniweb/projections": "^0.7.
|
|
65
|
-
"@uniweb/
|
|
62
|
+
"@uniweb/theming": "^0.1.15",
|
|
63
|
+
"@uniweb/content-reader": "^1.2.7",
|
|
64
|
+
"@uniweb/projections": "^0.7.8",
|
|
65
|
+
"@uniweb/semantic-parser": "^1.4.1"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
|
68
68
|
"@uniweb/runtime": "^0.27.10"
|
package/src/schema.js
CHANGED
|
@@ -17,6 +17,7 @@ import { isFontVar } from '@uniweb/theming'
|
|
|
17
17
|
import { join, dirname, extname, basename } from 'node:path'
|
|
18
18
|
import { pathToFileURL } from 'node:url'
|
|
19
19
|
import { describeChildren, describeVisuals } from '@uniweb/schemas/component'
|
|
20
|
+
import { SECTION_PARAMS } from '@uniweb/schemas/section'
|
|
20
21
|
import { collectSchemaRefs, buildDataSchemaMap, ownSchemaRefs } from './resolve-data-schema.js'
|
|
21
22
|
import {
|
|
22
23
|
composeSupports,
|
|
@@ -957,13 +958,17 @@ export function reportPlacementDeclarations(components) {
|
|
|
957
958
|
`provides renders "Component not found".`
|
|
958
959
|
)
|
|
959
960
|
}
|
|
960
|
-
//
|
|
961
|
-
//
|
|
962
|
-
|
|
961
|
+
// A name framework reserves in a section's params whose value the component never
|
|
962
|
+
// receives — `component: false` in `@uniweb/schemas/section`, as `grid` is.
|
|
963
|
+
const params = entry.params && typeof entry.params === 'object' ? entry.params : {}
|
|
964
|
+
for (const [param, reserved] of Object.entries(SECTION_PARAMS)) {
|
|
965
|
+
if (reserved.component || !Object.hasOwn(params, param)) continue
|
|
963
966
|
console.warn(
|
|
964
|
-
`Warning: ${name} (meta.js): a param named "
|
|
965
|
-
`
|
|
966
|
-
|
|
967
|
+
`Warning: ${name} (meta.js): a param named "${param}" never receives the author's ${param}: — framework ` +
|
|
968
|
+
`reserves it in a section's params (@uniweb/schemas/section). Rename the param.` +
|
|
969
|
+
(param === 'grid'
|
|
970
|
+
? ` To offer layouts for the child sections, declare children: { grid: [...] } and lay them out with kit's ChildGrid.`
|
|
971
|
+
: '')
|
|
967
972
|
)
|
|
968
973
|
}
|
|
969
974
|
}
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Section frontmatter reserved properties:
|
|
12
12
|
* - type: Component type (e.g., "Hero", "Features")
|
|
13
|
-
* -
|
|
14
|
-
* - input: Input field mapping
|
|
13
|
+
* - hidden: `true` makes the section a draft — left out of a published build, kept in dev
|
|
15
14
|
* - props: Additional component props (merged with other params)
|
|
16
15
|
* - fetch: The section's binding — names a query (query, as, where, sort, limit, current,
|
|
17
16
|
* prerender, detailPage); `query:` is its shorthand
|
|
@@ -33,7 +32,7 @@ import yaml from 'js-yaml'
|
|
|
33
32
|
import { YAML_OPTIONS } from '../utils/yaml-schema.js'
|
|
34
33
|
import { collectSectionAssets, mergeAssetCollections, collectConfigAssets } from './assets.js'
|
|
35
34
|
import { collectSectionIcons, mergeIconCollections, buildIconManifest } from './icons.js'
|
|
36
|
-
import { normalizeHideIn, dropUnpublishedPages } from './nav-visibility.js'
|
|
35
|
+
import { normalizeHideIn, dropUnpublishedPages, dropHiddenSections } from './nav-visibility.js'
|
|
37
36
|
import { parseFetchConfig, toFetchList } from './data-fetcher.js'
|
|
38
37
|
import { resolveExtensionUrls } from './extension-urls.js'
|
|
39
38
|
import { buildTheme, extractFoundationVars } from '../theme/index.js'
|
|
@@ -1011,7 +1010,28 @@ async function processMarkdownFile(filePath, id, siteRoot, defaultStableId = nul
|
|
|
1011
1010
|
|
|
1012
1011
|
// `query`, `fetch` and `data` are never params: `query:` / `fetch:` declare the
|
|
1013
1012
|
// section's own data, and a leftover `data:` is refused (`declaredFetch`).
|
|
1014
|
-
const { type, preset, input, props, fetch, query, data, id: frontmatterId, ...params } = frontMatter
|
|
1013
|
+
const { type, hidden, preset, input, props, fetch, query, data, id: frontmatterId, ...params } = frontMatter
|
|
1014
|
+
// ⭐ `hidden: true` — a draft section, the page's meaning one level down: left out of a
|
|
1015
|
+
// published build, kept by `uniweb dev`, and synced as the section's own `hidden`
|
|
1016
|
+
// [Diego, 2026-09-28]. ⛔ Until then it was an ordinary param: the component received it
|
|
1017
|
+
// and the section rendered everywhere.
|
|
1018
|
+
if (hidden !== undefined && typeof hidden !== 'boolean') {
|
|
1019
|
+
console.warn(
|
|
1020
|
+
`[content-collector] ${relative(siteRoot, filePath)}: hidden: ${JSON.stringify(hidden)} is not true or false — ` +
|
|
1021
|
+
`the section stays visible.`
|
|
1022
|
+
)
|
|
1023
|
+
}
|
|
1024
|
+
// ⛔ `preset:` and `input:` do nothing, so they are dropped with a warning rather than
|
|
1025
|
+
// handed to the component as params. Nothing ever read either [2026-09-27]: a preset is
|
|
1026
|
+
// a named set of params a section type's `meta.js` offers an editor, and a file writes
|
|
1027
|
+
// the params themselves (`uniweb add section <Type> --starter --preset <name>` writes them).
|
|
1028
|
+
for (const [key, value] of [['preset', preset], ['input', input]]) {
|
|
1029
|
+
if (value === undefined) continue
|
|
1030
|
+
console.warn(
|
|
1031
|
+
`[content-collector] ${relative(siteRoot, filePath)}: ${key}: ${JSON.stringify(value)} has no effect and is ignored` +
|
|
1032
|
+
(key === 'preset' ? ' — write the preset’s params in the frontmatter instead.' : '.')
|
|
1033
|
+
)
|
|
1034
|
+
}
|
|
1015
1035
|
// ⭐ `grid` — the layout of the section's child sections, `3` or `'40/60'` [Diego,
|
|
1016
1036
|
// 2026-09-27] — is a name framework reserves, and it stays in the params like
|
|
1017
1037
|
// `background`: that is where it is stored and synced, with no field of its own.
|
|
@@ -1049,8 +1069,7 @@ async function processMarkdownFile(filePath, id, siteRoot, defaultStableId = nul
|
|
|
1049
1069
|
id,
|
|
1050
1070
|
stableId,
|
|
1051
1071
|
type: type || null,
|
|
1052
|
-
|
|
1053
|
-
input,
|
|
1072
|
+
...(hidden === true ? { hidden: true } : {}),
|
|
1054
1073
|
params: { ...params, ...props },
|
|
1055
1074
|
content: proseMirrorContent,
|
|
1056
1075
|
fetch: parseFetchConfig(resolvedFetch, relative(siteRoot, filePath), { level: 'section' }),
|
|
@@ -2783,10 +2802,11 @@ export async function collectSiteContent(sitePath, options = {}) {
|
|
|
2783
2802
|
links: themeLinks
|
|
2784
2803
|
},
|
|
2785
2804
|
// Reachability axis: on the published build paths, drop `hidden` pages and
|
|
2786
|
-
// their whole subtree (cascade)
|
|
2787
|
-
|
|
2805
|
+
// their whole subtree (cascade), and every `hidden` section with its children.
|
|
2806
|
+
// Dev keeps them so drafts stay previewable.
|
|
2807
|
+
pages: dropUnpublished ? dropUnpublishedPages(pages).map(withoutHiddenSections) : pages,
|
|
2788
2808
|
// Layout area sets: { default: { header: page, footer: page, ... }, marketing: { ... } }
|
|
2789
|
-
layouts,
|
|
2809
|
+
layouts: dropUnpublished ? mapLayoutAreas(layouts, withoutHiddenSections) : layouts,
|
|
2790
2810
|
// ⭐ THE SAME REACHABILITY AXIS, for the 404 slot. `hidden: true` means DRAFT —
|
|
2791
2811
|
// "excluded from the published site" (`docs/reference/page-configuration.md`);
|
|
2792
2812
|
// `hideIn: ['*']` is the control for "routed but in no nav".
|
|
@@ -2799,7 +2819,7 @@ export async function collectSiteContent(sitePath, options = {}) {
|
|
|
2799
2819
|
// shell), and `uniweb dev` keeps the page previewable like any other draft.
|
|
2800
2820
|
// No cascade to resolve: this slot is root-level only, so its own flag is
|
|
2801
2821
|
// the only one that can apply.
|
|
2802
|
-
notFound: dropUnpublished
|
|
2822
|
+
notFound: dropUnpublished ? (notFound?.hidden ? null : withoutHiddenSections(notFound)) : notFound,
|
|
2803
2823
|
// Versioned scopes: route → { versions, latestId }
|
|
2804
2824
|
versionedScopes: versionedScopesObj,
|
|
2805
2825
|
assets: assetCollection.assets,
|
|
@@ -2810,6 +2830,25 @@ export async function collectSiteContent(sitePath, options = {}) {
|
|
|
2810
2830
|
}
|
|
2811
2831
|
}
|
|
2812
2832
|
|
|
2833
|
+
// A page (or a layout area) without its hidden sections — `dropHiddenSections`.
|
|
2834
|
+
function withoutHiddenSections(page) {
|
|
2835
|
+
if (!page || !Array.isArray(page.sections)) return page
|
|
2836
|
+
return { ...page, sections: dropHiddenSections(page.sections) }
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
// Apply `fn` to every area of every layout: `{ default: { header: page, … }, … }`.
|
|
2840
|
+
function mapLayoutAreas(layouts, fn) {
|
|
2841
|
+
if (!layouts || typeof layouts !== 'object') return layouts
|
|
2842
|
+
return Object.fromEntries(
|
|
2843
|
+
Object.entries(layouts).map(([name, areas]) => [
|
|
2844
|
+
name,
|
|
2845
|
+
areas && typeof areas === 'object'
|
|
2846
|
+
? Object.fromEntries(Object.entries(areas).map(([area, page]) => [area, fn(page)]))
|
|
2847
|
+
: areas,
|
|
2848
|
+
])
|
|
2849
|
+
)
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2813
2852
|
// Shared pure / IO helpers. Exported for testing AND reused by
|
|
2814
2853
|
// @uniweb/build/uwx, the .uwx exporter, which re-walks the source rather
|
|
2815
2854
|
// than the flattened collector output but reuses these primitives so
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Page-visibility helpers — the two orthogonal axes a page carries:
|
|
2
2
|
// • Reachability — `hidden: bool`. `true` = excluded from the PUBLISHED output
|
|
3
|
-
// entirely (see `dropUnpublishedPages
|
|
3
|
+
// entirely (see `dropUnpublishedPages`, and `dropHiddenSections` for a section).
|
|
4
|
+
// Still previewable in `uniweb dev`.
|
|
4
5
|
// • Nav placement — `hideIn: string[]`. Which nav areas suppress the page while
|
|
5
6
|
// it IS routed (see `normalizeHideIn`). `['*']` = suppressed from every area.
|
|
6
7
|
|
|
@@ -67,3 +68,23 @@ export function dropUnpublishedPages(pages) {
|
|
|
67
68
|
}
|
|
68
69
|
return pages.filter((p) => !isUnpublished(p))
|
|
69
70
|
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Drop hidden sections — the same reachability axis, one level down. A section with
|
|
74
|
+
* `hidden: true` in its frontmatter is a draft: left out of the PUBLISHED output together
|
|
75
|
+
* with its child sections, and kept by `uniweb dev` so it stays previewable. Applied where
|
|
76
|
+
* `dropUnpublishedPages` is, to a page's sections and a layout area's alike.
|
|
77
|
+
*
|
|
78
|
+
* @param {Array<object>} sections - collected sections (each may hold `subsections`)
|
|
79
|
+
* @returns {Array<object>} the sections without the hidden ones, at every depth
|
|
80
|
+
*/
|
|
81
|
+
export function dropHiddenSections(sections) {
|
|
82
|
+
if (!Array.isArray(sections)) return sections
|
|
83
|
+
return sections
|
|
84
|
+
.filter((section) => section?.hidden !== true)
|
|
85
|
+
.map((section) =>
|
|
86
|
+
Array.isArray(section.subsections) && section.subsections.length > 0
|
|
87
|
+
? { ...section, subsections: dropHiddenSections(section.subsections) }
|
|
88
|
+
: section
|
|
89
|
+
)
|
|
90
|
+
}
|
|
@@ -41,7 +41,8 @@ export const DEFAULT_RESERVED_FRONTMATTER = new Set([
|
|
|
41
41
|
'fetch',
|
|
42
42
|
'data',
|
|
43
43
|
'nest',
|
|
44
|
-
|
|
44
|
+
// ⛔ Not `hidden`, which was here from the start (2026-05-30) with no reason recorded: an
|
|
45
|
+
// editor toggles it, so a pull must carry the store's value into the file (2026-09-28).
|
|
45
46
|
])
|
|
46
47
|
|
|
47
48
|
// js-yaml dump options shared by every config write, so output is byte-stable.
|
package/src/uwx/site-project.js
CHANGED
|
@@ -425,7 +425,7 @@ function reinlineInsets(content, insets) {
|
|
|
425
425
|
* Project one section `$`-record (from `page_sections` / `layout_sections`) to a
|
|
426
426
|
* section `.md` file — the inverse of site.js `mapSectionData`. Frontmatter is
|
|
427
427
|
* `type` + the flat `params` + `background` / `theme` (`theme_override`) /
|
|
428
|
-
* `
|
|
428
|
+
* `fetch` / `id` (`stable_id`); the body is the section's
|
|
429
429
|
* content (insets re-inlined) serialized to markdown. Idempotent.
|
|
430
430
|
*
|
|
431
431
|
* Note: `$children` (a section's child sections) are NOT written here — the page
|
|
@@ -442,7 +442,9 @@ function reinlineInsets(content, insets) {
|
|
|
442
442
|
* @returns {'updated'|'unchanged'}
|
|
443
443
|
*/
|
|
444
444
|
export function sectionRecordToFile({ filePath, record, sourceLocale = LOCALIZED_FIELD_ASSUMPTION.defaultSourceLocale, collector, freeformRelPath, freeformCandidates = null, writeId = true, context = null }) {
|
|
445
|
-
|
|
445
|
+
// ⛔ A stored `preset` or `input` is not written back: neither has an effect, and the build
|
|
446
|
+
// would only warn about it (2026-09-27).
|
|
447
|
+
const { type, stable_id, hidden, params, content, insets, fetch, background, theme_override } = record || {}
|
|
446
448
|
|
|
447
449
|
// A localized `content` field unwraps to the source-locale doc for the body; its
|
|
448
450
|
// target-locale structural maps are captured into the locales/ collector, and any
|
|
@@ -450,13 +452,20 @@ export function sectionRecordToFile({ filePath, record, sourceLocale = LOCALIZED
|
|
|
450
452
|
// locales/freeform/. A bare doc (source-only / pre-localization) passes through.
|
|
451
453
|
const sourceContent = unwrapLocalizedContent(content, sourceLocale, collector, freeformRelPath, freeformCandidates, context)
|
|
452
454
|
|
|
455
|
+
const { hidden: paramHidden, ...paramsRest } = params && typeof params === 'object' ? params : {}
|
|
453
456
|
const frontmatter = {}
|
|
454
457
|
if (type !== undefined) frontmatter.type = type
|
|
455
|
-
|
|
458
|
+
// ⭐ `hidden:` says what the store says: written for a hidden section, and removed from the
|
|
459
|
+
// file of a visible one — an editor toggles it [2026-09-28]. ⛔ Until then a pull dropped it,
|
|
460
|
+
// and the next push un-hid the section. A push before that day sent a file's `hidden:` inside
|
|
461
|
+
// `params`, so one stored there counts when the field is absent.
|
|
462
|
+
frontmatter.hidden = (typeof hidden === 'boolean' ? hidden : paramHidden === true) ? true : undefined
|
|
463
|
+
Object.assign(frontmatter, paramsRest)
|
|
464
|
+
// ⚠️ A stored `background` / `theme_override` field wins over the same key in `params` while
|
|
465
|
+
// one is stored: an editor may still write the fields rather than `params` (2026-09-28;
|
|
466
|
+
// uwx-format.md § A page section's fields, Order).
|
|
456
467
|
if (background !== undefined) frontmatter.background = background
|
|
457
468
|
if (theme_override !== undefined) frontmatter.theme = theme_override
|
|
458
|
-
if (preset !== undefined) frontmatter.preset = preset
|
|
459
|
-
if (input !== undefined) frontmatter.input = input
|
|
460
469
|
// Invert the build's resolution rather than copy it — see fetch-shapes.js. A new
|
|
461
470
|
// file gets `query:` for a declaration of names alone; a section that declares
|
|
462
471
|
// its data locally keeps it (`writeSectionFile`, the declaration keys).
|
package/src/uwx/site.js
CHANGED
|
@@ -142,9 +142,9 @@ function stripCredentials(block, label) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// page_sections and layout_sections share this content shape.
|
|
145
|
-
// processMarkdownFile only
|
|
146
|
-
//
|
|
147
|
-
// inside section.params —
|
|
145
|
+
// processMarkdownFile takes only type/hidden/props/fetch/query/data/id out of the
|
|
146
|
+
// frontmatter (and drops preset/input), so `background:` and `theme:` stay
|
|
147
|
+
// inside section.params — and ride there on the wire too (`mapSectionData`).
|
|
148
148
|
// The `type` a push sends for a section whose file names none — the Model requires one. A pull does
|
|
149
149
|
// not write it back into a file that names none (`sectionRecordToFile`): absent, the foundation's
|
|
150
150
|
// `defaultSection` renders it, which is not always `Content`.
|
|
@@ -302,20 +302,20 @@ function localizePageMeta(page, route, sourceLocale, translations) {
|
|
|
302
302
|
|
|
303
303
|
function mapSectionData(section) {
|
|
304
304
|
const params = { ...section.params }
|
|
305
|
-
const background = params.background
|
|
306
|
-
const themeOverride = params.theme
|
|
307
|
-
delete params.background
|
|
308
|
-
delete params.theme
|
|
309
|
-
|
|
310
305
|
const data = { type: section.type || FILLED_SECTION_TYPE } // entity type requires `type`
|
|
311
306
|
setIf(data, 'stable_id', section.stableId ?? undefined)
|
|
312
|
-
|
|
313
|
-
|
|
307
|
+
// ⭐ A draft section — the store's own `hidden`, which an editor also writes [2026-09-28].
|
|
308
|
+
if (section.hidden === true) data.hidden = true
|
|
309
|
+
// No `preset` or `input`: nothing read either, and the collector drops them (2026-09-27).
|
|
310
|
+
// ⭐ `background` and `theme` stay IN `params` [Diego, 2026-09-28] — where the runtime reads
|
|
311
|
+
// them, and where a section stores them from now on. ⚠️ Also sent as the two fields they had
|
|
312
|
+
// until today, until every reader of a stored section reads `params`; a pull prefers a
|
|
313
|
+
// stored field meanwhile (`sectionRecordToFile`). uwx-format.md § A page section's fields.
|
|
314
314
|
if (Object.keys(params).length > 0) data.params = params
|
|
315
315
|
data.content = section.content // leaf insets in it as written — no `insets` field since 2026-09-27
|
|
316
316
|
setIf(data, 'fetch', section.fetch ?? undefined)
|
|
317
|
-
setIf(data, 'background', background)
|
|
318
|
-
setIf(data, 'theme_override',
|
|
317
|
+
setIf(data, 'background', params.background)
|
|
318
|
+
setIf(data, 'theme_override', params.theme)
|
|
319
319
|
return data
|
|
320
320
|
}
|
|
321
321
|
|