@uniweb/build 0.52.3 → 0.52.5
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/docs.js +37 -15
- package/src/i18n/extract.js +2 -1
- package/src/uwx/site-diff.js +6 -6
- package/src/uwx/site-project.js +45 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.5",
|
|
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/content-reader": "^1.2.5",
|
|
61
|
-
"@uniweb/schemas": "^0.2.15",
|
|
62
|
-
"@uniweb/semantic-parser": "^1.4.0",
|
|
63
60
|
"@uniweb/projections": "^0.7.3",
|
|
64
|
-
"@uniweb/
|
|
65
|
-
"@uniweb/
|
|
61
|
+
"@uniweb/semantic-parser": "^1.4.1",
|
|
62
|
+
"@uniweb/schemas": "^0.3.1",
|
|
63
|
+
"@uniweb/content-reader": "^1.2.5",
|
|
64
|
+
"@uniweb/content-writer": "^0.3.4",
|
|
65
|
+
"@uniweb/theming": "^0.1.15"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
|
68
68
|
"@uniweb/runtime": "^0.26.4"
|
package/src/docs.js
CHANGED
|
@@ -31,6 +31,13 @@ function generateComponentDocs(name, meta) {
|
|
|
31
31
|
lines.push('')
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// The standard section type this component claims. `family` replaced the
|
|
35
|
+
// retired `category:` / `purpose:` pair, which this generator never emitted.
|
|
36
|
+
if (meta.family) {
|
|
37
|
+
lines.push(`*Family:* \`${meta.family}\``)
|
|
38
|
+
lines.push('')
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
// Parameters (shown first - most important for content authors)
|
|
35
42
|
if (meta.params && Object.keys(meta.params).length > 0) {
|
|
36
43
|
lines.push('### Parameters')
|
|
@@ -66,33 +73,48 @@ function generateComponentDocs(name, meta) {
|
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
// Presets
|
|
70
|
-
|
|
76
|
+
// Presets — named param combinations. `meta.presets` is a KEYED OBJECT
|
|
77
|
+
// ({ name: { label, params } }), not an array: this read `meta.presets.length`
|
|
78
|
+
// until 2026-09-16, which is `undefined > 0` on an object, so every preset in
|
|
79
|
+
// every foundation was dropped from COMPONENTS.md with nothing reporting it.
|
|
80
|
+
const presets = Object.entries(meta.presets || {})
|
|
81
|
+
if (presets.length > 0) {
|
|
71
82
|
lines.push('### Presets')
|
|
72
83
|
lines.push('')
|
|
73
84
|
|
|
74
|
-
for (const preset of
|
|
75
|
-
const settings = preset
|
|
76
|
-
? Object.entries(preset.
|
|
85
|
+
for (const [name, preset] of presets) {
|
|
86
|
+
const settings = preset?.params
|
|
87
|
+
? Object.entries(preset.params)
|
|
77
88
|
.map(([k, v]) => `${k}: ${v}`)
|
|
78
89
|
.join(', ')
|
|
79
90
|
: ''
|
|
80
|
-
|
|
91
|
+
const label = preset?.label || name
|
|
92
|
+
lines.push(`- **${name}**${label !== name ? ` — ${label}` : ''}${settings ? ` (${settings})` : ''}`)
|
|
81
93
|
}
|
|
82
94
|
lines.push('')
|
|
83
95
|
}
|
|
84
96
|
|
|
85
|
-
// Content
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
// Content expectations — what an author writes in the markdown. The key is
|
|
98
|
+
// `content`; this read `meta.content` as `meta.elements` until 2026-09-16 —
|
|
99
|
+
// the pre-rename name, which nothing has produced since. A dead branch reads
|
|
100
|
+
// exactly like a component that declares nothing, so the whole section was
|
|
101
|
+
// missing from every generated catalog.
|
|
102
|
+
//
|
|
103
|
+
// A value is either a label string ('Description [1-2]') or { label, hint }.
|
|
104
|
+
// The LABEL is the point: `paragraphs` alone tells an author nothing, and the
|
|
105
|
+
// count hint is the part that says how much to write.
|
|
106
|
+
const elements = Object.entries(meta.content || {})
|
|
107
|
+
if (elements.length > 0) {
|
|
92
108
|
lines.push('### Content')
|
|
93
109
|
lines.push('')
|
|
94
|
-
|
|
95
|
-
|
|
110
|
+
|
|
111
|
+
for (const [key, el] of elements) {
|
|
112
|
+
const label = typeof el === 'string' ? el : el?.label || ''
|
|
113
|
+
lines.push(`**${key}**${label ? ` — ${label}` : ''}`)
|
|
114
|
+
const hint = typeof el === 'object' && el?.hint ? el.hint : ''
|
|
115
|
+
if (hint) lines.push(` ${hint}`)
|
|
116
|
+
lines.push('')
|
|
117
|
+
}
|
|
96
118
|
}
|
|
97
119
|
|
|
98
120
|
return lines.join('\n')
|
package/src/i18n/extract.js
CHANGED
|
@@ -327,7 +327,8 @@ function visitTranslatableBlocks(nodes, visit) {
|
|
|
327
327
|
* Determine field name for heading based on level and index
|
|
328
328
|
*/
|
|
329
329
|
function getHeadingField(level, index) {
|
|
330
|
-
// First H1 is title, first H2 is subtitle
|
|
330
|
+
// First H1 is title, first H2 is subtitle. A pretitle is a `#>` label line
|
|
331
|
+
// (or, in older content, a smaller heading above the title).
|
|
331
332
|
// This is simplified - semantic-parser does this more intelligently
|
|
332
333
|
if (level === 1) return index.h1 === 0 ? 'title' : `heading.${index.h1}`
|
|
333
334
|
if (level === 2) return index.h2 === 0 ? 'subtitle' : `heading.h2.${index.h2}`
|
package/src/uwx/site-diff.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// Units are DISJOINT: a page's hash excludes its sections, a section's excludes its
|
|
24
24
|
// children, so a change lands in exactly one unit and is never double-reported.
|
|
25
25
|
// Naming comes from the projector's own helpers (`pageDirName`,
|
|
26
|
-
// `
|
|
26
|
+
// `sectionFileBase`) rather than a local copy, so a label always names a file
|
|
27
27
|
// that really exists.
|
|
28
28
|
//
|
|
29
29
|
// It needs TWO bases, and that is the non-obvious part.
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
// reported as unattributed rather than guessed at.
|
|
48
48
|
|
|
49
49
|
import { entityContentHash } from './records.js'
|
|
50
|
-
import {
|
|
50
|
+
import { sectionFileBase, pageDirName, layoutSectionPaths } from './site-project.js'
|
|
51
51
|
import { LOCALIZED_FIELD_ASSUMPTION } from './localize.js'
|
|
52
52
|
|
|
53
53
|
// A unit's own content, with the nested collections that are their own units
|
|
@@ -95,10 +95,10 @@ export function collectSiteUnits(doc, sourceLocale = LOCALIZED_FIELD_ASSUMPTION.
|
|
|
95
95
|
export function walkSiteUnits(doc, cb, sourceLocale = LOCALIZED_FIELD_ASSUMPTION.defaultSourceLocale) {
|
|
96
96
|
const walkSections = (sections, dir) => {
|
|
97
97
|
for (const record of sections || []) {
|
|
98
|
-
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
if (
|
|
98
|
+
// The projector's own file rule, so a unit's path here is the file it is
|
|
99
|
+
// written to — including an app-created section named from its `$uuid`.
|
|
100
|
+
const base = sectionFileBase(record)
|
|
101
|
+
if (base) cb(`${dir}/${base}.md`, record, 'section')
|
|
102
102
|
walkSections(record.$children, dir)
|
|
103
103
|
}
|
|
104
104
|
}
|
package/src/uwx/site-project.js
CHANGED
|
@@ -464,6 +464,42 @@ export function safeStableIdFilename(stableId) {
|
|
|
464
464
|
return base ? `${base}-${hash}` : `s-${hash}`
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
+
/**
|
|
468
|
+
* The `.md` file base a page section is written to — the ONE rule the projector,
|
|
469
|
+
* the prune keep-set and the push/pull unit walk (`site-diff.js::walkSiteUnits`)
|
|
470
|
+
* all use, so the path a pull harvests a `$uuid` under is the path the next push
|
|
471
|
+
* stamps it back onto.
|
|
472
|
+
*
|
|
473
|
+
* `stable_id || $id` when the record has one (unchanged). ⛔ A section with
|
|
474
|
+
* neither — what the app's editor creates: a `$uuid` and no stable id — used to be
|
|
475
|
+
* SKIPPED, silently, by all three. A pull then left its file out, and a push after
|
|
476
|
+
* any local edit sent the page without it, which the backend deletes (a
|
|
477
|
+
* package's absence of a stored `page_sections` item under a page it sends is a
|
|
478
|
+
* delete). So such a section is named from its identity instead:
|
|
479
|
+
* `<type-kebab>-<last 8 hex of $uuid>`, e.g. `hero-95993360`.
|
|
480
|
+
*
|
|
481
|
+
* The derived base is already filesystem-safe and starts with a letter, so the
|
|
482
|
+
* build reads it back verbatim as the section's stable id (no numeric prefix to
|
|
483
|
+
* strip) and `safeStableIdFilename` returns it unchanged — the push path equals
|
|
484
|
+
* the pull path. The first push after such a pull therefore gives the item that
|
|
485
|
+
* stable id; the `$uuid` is re-stamped by path, so the item keeps its identity.
|
|
486
|
+
*
|
|
487
|
+
* @returns {string|null} null only for a record with no stable id and no `$uuid`
|
|
488
|
+
*/
|
|
489
|
+
export function sectionFileBase(record) {
|
|
490
|
+
const id = recordStableId(record)
|
|
491
|
+
if (id) return safeStableIdFilename(id)
|
|
492
|
+
const hex = typeof record?.$uuid === 'string' ? record.$uuid.replace(/-/g, '').toLowerCase() : ''
|
|
493
|
+
if (!/^[0-9a-f]{8,}$/.test(hex)) return null
|
|
494
|
+
const kebab = String(record?.type || '')
|
|
495
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
496
|
+
.toLowerCase()
|
|
497
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
498
|
+
.replace(/^-+|-+$/g, '')
|
|
499
|
+
const stem = /^[a-z]/.test(kebab) ? kebab : 'section'
|
|
500
|
+
return `${stem}-${hex.slice(-8)}`
|
|
501
|
+
}
|
|
502
|
+
|
|
467
503
|
/**
|
|
468
504
|
* Write a page's `page_sections` tree to clean `<stableId>.md` files in `pageDir`
|
|
469
505
|
* and return the `page.yml::sections:` array that captures order + nesting — the
|
|
@@ -483,12 +519,13 @@ export function pageSectionsToFiles({ pageDir, pageSections, ctx, pageContext })
|
|
|
483
519
|
const buildEntries = (records) => {
|
|
484
520
|
const entries = []
|
|
485
521
|
for (const record of records || []) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
|
|
491
|
-
|
|
522
|
+
// Filesystem-safe filename (= stableId when already safe; derived from `$uuid`
|
|
523
|
+
// for an app-created section with no stable id); the true stableId rides in
|
|
524
|
+
// frontmatter `id:`. The sections: leaf uses the same base so the producer's
|
|
525
|
+
// filename-based resolution matches.
|
|
526
|
+
const fileBase = sectionFileBase(record)
|
|
527
|
+
if (!fileBase) continue // no stable id AND no `$uuid` → nothing to name it by
|
|
528
|
+
const stableId = recordStableId(record) || fileBase
|
|
492
529
|
const filePath = join(pageDir, `${fileBase}.md`)
|
|
493
530
|
// If this uuid's section moved (an app-side stableId rename), relocate its
|
|
494
531
|
// `.md` in place before writing; then record its current path in the index.
|
|
@@ -672,8 +709,8 @@ function collectSectionFileBases(pageSections) {
|
|
|
672
709
|
const bases = new Set()
|
|
673
710
|
const walk = (records) => {
|
|
674
711
|
for (const record of records || []) {
|
|
675
|
-
const
|
|
676
|
-
if (
|
|
712
|
+
const base = sectionFileBase(record)
|
|
713
|
+
if (base) bases.add(base)
|
|
677
714
|
if (Array.isArray(record.$children)) walk(record.$children)
|
|
678
715
|
}
|
|
679
716
|
}
|