@uniweb/build 0.1.27 → 0.1.29
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 +4 -4
- package/src/prerender.js +347 -7
- package/src/runtime-schema.js +34 -1
- package/src/site/collection-processor.js +382 -0
- package/src/site/config.js +46 -1
- package/src/site/content-collector.js +60 -6
- package/src/site/data-fetcher.js +496 -0
- package/src/site/index.js +14 -0
- package/src/site/plugin.js +74 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"sharp": "^0.33.2"
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
53
|
-
"@uniweb/
|
|
54
|
-
"@uniweb/
|
|
53
|
+
"@uniweb/runtime": "0.2.16",
|
|
54
|
+
"@uniweb/content-reader": "1.0.4"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@tailwindcss/vite": "^4.0.0",
|
|
61
61
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
62
62
|
"vite-plugin-svgr": "^4.0.0",
|
|
63
|
-
"@uniweb/core": "0.1.
|
|
63
|
+
"@uniweb/core": "0.1.13"
|
|
64
64
|
},
|
|
65
65
|
"peerDependenciesMeta": {
|
|
66
66
|
"vite": {
|
package/src/prerender.js
CHANGED
|
@@ -13,9 +13,212 @@ import { existsSync } from 'node:fs'
|
|
|
13
13
|
import { join, dirname, resolve } from 'node:path'
|
|
14
14
|
import { pathToFileURL } from 'node:url'
|
|
15
15
|
import { createRequire } from 'node:module'
|
|
16
|
+
import { executeFetch, mergeDataIntoContent, singularize } from './site/data-fetcher.js'
|
|
16
17
|
|
|
17
18
|
// Lazily loaded dependencies
|
|
18
|
-
let React, renderToString, createUniweb
|
|
19
|
+
let React, renderToString, createUniweb
|
|
20
|
+
let preparePropsSSR, getComponentMetaSSR, guaranteeContentStructureSSR
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Execute all data fetches for prerender
|
|
24
|
+
* Processes site, page, and section level fetches, merging data appropriately
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} siteContent - The site content from site-content.json
|
|
27
|
+
* @param {string} siteDir - Path to the site directory
|
|
28
|
+
* @param {function} onProgress - Progress callback
|
|
29
|
+
* @returns {Object} { siteCascadedData, pageFetchedData } - Fetched data for dynamic route expansion
|
|
30
|
+
*/
|
|
31
|
+
async function executeAllFetches(siteContent, siteDir, onProgress) {
|
|
32
|
+
const fetchOptions = { siteRoot: siteDir, publicDir: 'public' }
|
|
33
|
+
|
|
34
|
+
// 1. Site-level fetch (cascades to all pages)
|
|
35
|
+
let siteCascadedData = {}
|
|
36
|
+
const siteFetch = siteContent.config?.fetch
|
|
37
|
+
if (siteFetch && siteFetch.prerender !== false) {
|
|
38
|
+
onProgress(` Fetching site data: ${siteFetch.path || siteFetch.url}`)
|
|
39
|
+
const result = await executeFetch(siteFetch, fetchOptions)
|
|
40
|
+
if (result.data && !result.error) {
|
|
41
|
+
siteCascadedData[siteFetch.schema] = result.data
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 2. Process each page and track fetched data by route
|
|
46
|
+
const pageFetchedData = new Map()
|
|
47
|
+
|
|
48
|
+
for (const page of siteContent.pages || []) {
|
|
49
|
+
// Page-level fetch (cascades to sections in this page)
|
|
50
|
+
let pageCascadedData = { ...siteCascadedData }
|
|
51
|
+
const pageFetch = page.fetch
|
|
52
|
+
if (pageFetch && pageFetch.prerender !== false) {
|
|
53
|
+
onProgress(` Fetching page data for ${page.route}: ${pageFetch.path || pageFetch.url}`)
|
|
54
|
+
const result = await executeFetch(pageFetch, fetchOptions)
|
|
55
|
+
if (result.data && !result.error) {
|
|
56
|
+
pageCascadedData[pageFetch.schema] = result.data
|
|
57
|
+
// Store for dynamic route expansion
|
|
58
|
+
pageFetchedData.set(page.route, {
|
|
59
|
+
schema: pageFetch.schema,
|
|
60
|
+
data: result.data,
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Process sections recursively (handles subsections too)
|
|
66
|
+
await processSectionFetches(page.sections, pageCascadedData, fetchOptions, onProgress)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { siteCascadedData, pageFetchedData }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Expand dynamic pages into concrete pages based on fetched data
|
|
74
|
+
* A dynamic page like /blog/:slug with parent data [{ slug: 'post-1' }, { slug: 'post-2' }]
|
|
75
|
+
* becomes /blog/post-1 and /blog/post-2
|
|
76
|
+
*
|
|
77
|
+
* @param {Array} pages - Original pages array
|
|
78
|
+
* @param {Map} pageFetchedData - Map of route -> { schema, data }
|
|
79
|
+
* @param {function} onProgress - Progress callback
|
|
80
|
+
* @returns {Array} Expanded pages array with dynamic pages replaced by concrete instances
|
|
81
|
+
*/
|
|
82
|
+
function expandDynamicPages(pages, pageFetchedData, onProgress) {
|
|
83
|
+
const expandedPages = []
|
|
84
|
+
|
|
85
|
+
for (const page of pages) {
|
|
86
|
+
if (!page.isDynamic) {
|
|
87
|
+
// Regular page - include as-is
|
|
88
|
+
expandedPages.push(page)
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Dynamic page - expand based on parent's data
|
|
93
|
+
const { paramName, parentSchema } = page
|
|
94
|
+
|
|
95
|
+
if (!parentSchema) {
|
|
96
|
+
onProgress(` Warning: Dynamic page ${page.route} has no parentSchema, skipping`)
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Find the parent's data
|
|
101
|
+
// The parent route is the route without the :param suffix
|
|
102
|
+
const parentRoute = page.route.replace(/\/:[\w]+$/, '') || '/'
|
|
103
|
+
const parentData = pageFetchedData.get(parentRoute)
|
|
104
|
+
|
|
105
|
+
if (!parentData || !Array.isArray(parentData.data)) {
|
|
106
|
+
onProgress(` Warning: No data found for dynamic page ${page.route} (parent: ${parentRoute})`)
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const items = parentData.data
|
|
111
|
+
const schema = parentData.schema
|
|
112
|
+
const singularSchema = singularize(schema)
|
|
113
|
+
|
|
114
|
+
onProgress(` Expanding ${page.route} → ${items.length} pages from ${schema}`)
|
|
115
|
+
|
|
116
|
+
// Create a concrete page for each item
|
|
117
|
+
for (const item of items) {
|
|
118
|
+
// Get the param value from the item (e.g., item.slug for :slug)
|
|
119
|
+
const paramValue = item[paramName]
|
|
120
|
+
if (!paramValue) {
|
|
121
|
+
onProgress(` Skipping item without ${paramName}`)
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Create concrete route: /blog/:slug → /blog/my-post
|
|
126
|
+
const concreteRoute = page.route.replace(`:${paramName}`, paramValue)
|
|
127
|
+
|
|
128
|
+
// Deep clone the page with modifications
|
|
129
|
+
const concretePage = JSON.parse(JSON.stringify(page))
|
|
130
|
+
concretePage.route = concreteRoute
|
|
131
|
+
concretePage.isDynamic = false // No longer dynamic
|
|
132
|
+
concretePage.paramName = undefined
|
|
133
|
+
concretePage.parentSchema = undefined
|
|
134
|
+
|
|
135
|
+
// Store the dynamic route context for runtime data resolution
|
|
136
|
+
concretePage.dynamicContext = {
|
|
137
|
+
paramName,
|
|
138
|
+
paramValue,
|
|
139
|
+
schema, // Plural: 'articles'
|
|
140
|
+
singularSchema, // Singular: 'article'
|
|
141
|
+
currentItem: item, // The item for this specific route
|
|
142
|
+
allItems: items, // All items from parent
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Also inject into sections' cascadedData for components with inheritData
|
|
146
|
+
injectDynamicData(concretePage.sections, {
|
|
147
|
+
[singularSchema]: item, // Current item as singular
|
|
148
|
+
[schema]: items, // All items as plural
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// Use item data for page metadata if available
|
|
152
|
+
if (item.title) concretePage.title = item.title
|
|
153
|
+
if (item.description || item.excerpt) concretePage.description = item.description || item.excerpt
|
|
154
|
+
|
|
155
|
+
expandedPages.push(concretePage)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return expandedPages
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Inject dynamic route data into section cascadedData
|
|
164
|
+
* This ensures components with inheritData receive the current item
|
|
165
|
+
*
|
|
166
|
+
* @param {Array} sections - Sections to update
|
|
167
|
+
* @param {Object} data - Data to inject { article: {...}, articles: [...] }
|
|
168
|
+
*/
|
|
169
|
+
function injectDynamicData(sections, data) {
|
|
170
|
+
if (!sections || !Array.isArray(sections)) return
|
|
171
|
+
|
|
172
|
+
for (const section of sections) {
|
|
173
|
+
section.cascadedData = {
|
|
174
|
+
...(section.cascadedData || {}),
|
|
175
|
+
...data,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Recurse into subsections
|
|
179
|
+
if (section.subsections && section.subsections.length > 0) {
|
|
180
|
+
injectDynamicData(section.subsections, data)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Process fetch configs for sections (and subsections recursively)
|
|
187
|
+
*
|
|
188
|
+
* @param {Array} sections - Array of section objects
|
|
189
|
+
* @param {Object} cascadedData - Data cascaded from site/page level
|
|
190
|
+
* @param {Object} fetchOptions - Options for executeFetch
|
|
191
|
+
* @param {function} onProgress - Progress callback
|
|
192
|
+
*/
|
|
193
|
+
async function processSectionFetches(sections, cascadedData, fetchOptions, onProgress) {
|
|
194
|
+
if (!sections || !Array.isArray(sections)) return
|
|
195
|
+
|
|
196
|
+
for (const section of sections) {
|
|
197
|
+
// Execute section-level fetch
|
|
198
|
+
const sectionFetch = section.fetch
|
|
199
|
+
if (sectionFetch && sectionFetch.prerender !== false) {
|
|
200
|
+
onProgress(` Fetching section data: ${sectionFetch.path || sectionFetch.url}`)
|
|
201
|
+
const result = await executeFetch(sectionFetch, fetchOptions)
|
|
202
|
+
if (result.data && !result.error) {
|
|
203
|
+
// Merge fetched data into section's parsedContent
|
|
204
|
+
section.parsedContent = mergeDataIntoContent(
|
|
205
|
+
section.parsedContent || {},
|
|
206
|
+
result.data,
|
|
207
|
+
sectionFetch.schema,
|
|
208
|
+
sectionFetch.merge
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Attach cascaded data for components with inheritData
|
|
214
|
+
section.cascadedData = cascadedData
|
|
215
|
+
|
|
216
|
+
// Process subsections recursively
|
|
217
|
+
if (section.subsections && section.subsections.length > 0) {
|
|
218
|
+
await processSectionFetches(section.subsections, cascadedData, fetchOptions, onProgress)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
19
222
|
|
|
20
223
|
/**
|
|
21
224
|
* Load dependencies dynamically from the site's context
|
|
@@ -53,9 +256,127 @@ async function loadDependencies(siteDir) {
|
|
|
53
256
|
const coreMod = await import('@uniweb/core')
|
|
54
257
|
createUniweb = coreMod.createUniweb
|
|
55
258
|
|
|
56
|
-
// Load
|
|
57
|
-
const
|
|
58
|
-
|
|
259
|
+
// Load runtime utilities (prepare-props doesn't use React)
|
|
260
|
+
const runtimeMod = await import('@uniweb/runtime/ssr')
|
|
261
|
+
preparePropsSSR = runtimeMod.prepareProps
|
|
262
|
+
getComponentMetaSSR = runtimeMod.getComponentMeta
|
|
263
|
+
guaranteeContentStructureSSR = runtimeMod.guaranteeContentStructure
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Inline BlockRenderer for SSR
|
|
268
|
+
* Uses React from prerender's scope to avoid module resolution issues
|
|
269
|
+
*/
|
|
270
|
+
function renderBlock(block) {
|
|
271
|
+
const Component = block.initComponent()
|
|
272
|
+
|
|
273
|
+
if (!Component) {
|
|
274
|
+
return React.createElement('div', {
|
|
275
|
+
className: 'block-error',
|
|
276
|
+
style: { padding: '1rem', background: '#fef2f2', color: '#dc2626' }
|
|
277
|
+
}, `Component not found: ${block.type}`)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Build content and params with runtime guarantees
|
|
281
|
+
let content, params
|
|
282
|
+
|
|
283
|
+
if (block.parsedContent?._isPoc) {
|
|
284
|
+
// Simple PoC format - content was passed directly
|
|
285
|
+
content = block.parsedContent._pocContent
|
|
286
|
+
params = block.properties
|
|
287
|
+
} else {
|
|
288
|
+
// Get runtime metadata for this component
|
|
289
|
+
const meta = getComponentMetaSSR(block.type)
|
|
290
|
+
|
|
291
|
+
// Prepare props with runtime guarantees
|
|
292
|
+
const prepared = preparePropsSSR(block, meta)
|
|
293
|
+
params = prepared.params
|
|
294
|
+
content = {
|
|
295
|
+
...prepared.content,
|
|
296
|
+
...block.properties,
|
|
297
|
+
_prosemirror: block.parsedContent
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const componentProps = {
|
|
302
|
+
content,
|
|
303
|
+
params,
|
|
304
|
+
block,
|
|
305
|
+
input: block.input
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Wrapper props
|
|
309
|
+
const theme = block.themeName
|
|
310
|
+
const wrapperProps = {
|
|
311
|
+
id: `Section${block.id}`,
|
|
312
|
+
className: theme || ''
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return React.createElement('div', wrapperProps,
|
|
316
|
+
React.createElement(Component, componentProps)
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Inline Blocks renderer for SSR
|
|
322
|
+
*/
|
|
323
|
+
function renderBlocks(blocks) {
|
|
324
|
+
if (!blocks || blocks.length === 0) return null
|
|
325
|
+
return blocks.map((block, index) =>
|
|
326
|
+
React.createElement(React.Fragment, { key: block.id || index },
|
|
327
|
+
renderBlock(block)
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Inline Layout renderer for SSR
|
|
334
|
+
*/
|
|
335
|
+
function renderLayout(page, website) {
|
|
336
|
+
const RemoteLayout = website.getRemoteLayout()
|
|
337
|
+
|
|
338
|
+
const headerBlocks = page.getHeaderBlocks()
|
|
339
|
+
const bodyBlocks = page.getBodyBlocks()
|
|
340
|
+
const footerBlocks = page.getFooterBlocks()
|
|
341
|
+
const leftBlocks = page.getLeftBlocks()
|
|
342
|
+
const rightBlocks = page.getRightBlocks()
|
|
343
|
+
|
|
344
|
+
const headerElement = headerBlocks ? renderBlocks(headerBlocks) : null
|
|
345
|
+
const bodyElement = bodyBlocks ? renderBlocks(bodyBlocks) : null
|
|
346
|
+
const footerElement = footerBlocks ? renderBlocks(footerBlocks) : null
|
|
347
|
+
const leftElement = leftBlocks ? renderBlocks(leftBlocks) : null
|
|
348
|
+
const rightElement = rightBlocks ? renderBlocks(rightBlocks) : null
|
|
349
|
+
|
|
350
|
+
if (RemoteLayout) {
|
|
351
|
+
return React.createElement(RemoteLayout, {
|
|
352
|
+
page,
|
|
353
|
+
website,
|
|
354
|
+
header: headerElement,
|
|
355
|
+
body: bodyElement,
|
|
356
|
+
footer: footerElement,
|
|
357
|
+
left: leftElement,
|
|
358
|
+
right: rightElement,
|
|
359
|
+
leftPanel: leftElement,
|
|
360
|
+
rightPanel: rightElement
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Default layout
|
|
365
|
+
return React.createElement(React.Fragment, null,
|
|
366
|
+
headerElement,
|
|
367
|
+
bodyElement,
|
|
368
|
+
footerElement
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Inline PageElement for SSR
|
|
374
|
+
* Uses React from prerender's scope
|
|
375
|
+
*/
|
|
376
|
+
function createPageElement(page, website) {
|
|
377
|
+
return React.createElement('main', null,
|
|
378
|
+
renderLayout(page, website)
|
|
379
|
+
)
|
|
59
380
|
}
|
|
60
381
|
|
|
61
382
|
/**
|
|
@@ -92,6 +413,16 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
92
413
|
}
|
|
93
414
|
const siteContent = JSON.parse(await readFile(contentPath, 'utf8'))
|
|
94
415
|
|
|
416
|
+
// Execute data fetches (site, page, section levels)
|
|
417
|
+
onProgress('Executing data fetches...')
|
|
418
|
+
const { siteCascadedData, pageFetchedData } = await executeAllFetches(siteContent, siteDir, onProgress)
|
|
419
|
+
|
|
420
|
+
// Expand dynamic pages (e.g., /blog/:slug → /blog/post-1, /blog/post-2)
|
|
421
|
+
if (siteContent.pages?.some(p => p.isDynamic)) {
|
|
422
|
+
onProgress('Expanding dynamic routes...')
|
|
423
|
+
siteContent.pages = expandDynamicPages(siteContent.pages, pageFetchedData, onProgress)
|
|
424
|
+
}
|
|
425
|
+
|
|
95
426
|
// Load the HTML shell
|
|
96
427
|
onProgress('Loading HTML shell...')
|
|
97
428
|
const shellPath = join(distDir, 'index.html')
|
|
@@ -141,8 +472,9 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
141
472
|
// Set this as the active page
|
|
142
473
|
uniweb.activeWebsite.setActivePage(page.route)
|
|
143
474
|
|
|
144
|
-
// Create the page element using
|
|
145
|
-
|
|
475
|
+
// Create the page element using inline SSR rendering
|
|
476
|
+
// (uses React from prerender's scope to avoid module resolution issues)
|
|
477
|
+
const element = createPageElement(page, website)
|
|
146
478
|
|
|
147
479
|
// Render to HTML string
|
|
148
480
|
let renderedContent
|
|
@@ -215,8 +547,16 @@ function injectContent(shell, renderedContent, page, siteContent) {
|
|
|
215
547
|
}
|
|
216
548
|
|
|
217
549
|
// Inject site content as JSON for hydration
|
|
550
|
+
// Replace existing content if present, otherwise add it
|
|
218
551
|
const contentScript = `<script id="__SITE_CONTENT__" type="application/json">${JSON.stringify(siteContent)}</script>`
|
|
219
|
-
if (
|
|
552
|
+
if (html.includes('__SITE_CONTENT__')) {
|
|
553
|
+
// Replace existing site content with updated version (includes expanded dynamic routes)
|
|
554
|
+
// Match script tag with attributes in any order
|
|
555
|
+
html = html.replace(
|
|
556
|
+
/<script[^>]*id="__SITE_CONTENT__"[^>]*>[\s\S]*?<\/script>/,
|
|
557
|
+
contentScript
|
|
558
|
+
)
|
|
559
|
+
} else {
|
|
220
560
|
html = html.replace(
|
|
221
561
|
'</head>',
|
|
222
562
|
` ${contentScript}\n </head>`
|
package/src/runtime-schema.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - defaults: param default values
|
|
11
11
|
* - context: static capabilities for cross-block coordination
|
|
12
12
|
* - initialState: initial values for mutable block state
|
|
13
|
+
* - inheritData: boolean or array for cascaded data from page/site fetches
|
|
13
14
|
*
|
|
14
15
|
* Full metadata (titles, descriptions, hints, etc.) stays in schema.json
|
|
15
16
|
* for the visual editor.
|
|
@@ -105,10 +106,30 @@ function extractSchemaFields(schemaFields) {
|
|
|
105
106
|
return lean
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Check if a schema value is in the full @uniweb/schemas format
|
|
111
|
+
* Full format has: { name, version?, description?, fields: {...} }
|
|
112
|
+
*
|
|
113
|
+
* @param {Object} schema - Schema value to check
|
|
114
|
+
* @returns {boolean}
|
|
115
|
+
*/
|
|
116
|
+
function isFullSchemaFormat(schema) {
|
|
117
|
+
return (
|
|
118
|
+
schema &&
|
|
119
|
+
typeof schema === 'object' &&
|
|
120
|
+
typeof schema.fields === 'object' &&
|
|
121
|
+
schema.fields !== null
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
108
125
|
/**
|
|
109
126
|
* Extract lean schemas from meta.js schemas object
|
|
110
127
|
* Strips editor-only fields while preserving structure
|
|
111
128
|
*
|
|
129
|
+
* Supports two formats:
|
|
130
|
+
* 1. Full @uniweb/schemas format: { name, version, fields: {...} }
|
|
131
|
+
* 2. Inline fields format: { fieldName: fieldDef, ... }
|
|
132
|
+
*
|
|
112
133
|
* @param {Object} schemas - The schemas object from meta.js
|
|
113
134
|
* @returns {Object|null} - Lean schemas or null if empty
|
|
114
135
|
*/
|
|
@@ -118,7 +139,13 @@ function extractSchemas(schemas) {
|
|
|
118
139
|
}
|
|
119
140
|
|
|
120
141
|
const lean = {}
|
|
121
|
-
for (const [schemaName,
|
|
142
|
+
for (const [schemaName, schemaValue] of Object.entries(schemas)) {
|
|
143
|
+
// Handle full schema format (from @uniweb/schemas or npm packages)
|
|
144
|
+
// Extract just the fields, discard name/version/description metadata
|
|
145
|
+
const schemaFields = isFullSchemaFormat(schemaValue)
|
|
146
|
+
? schemaValue.fields
|
|
147
|
+
: schemaValue
|
|
148
|
+
|
|
122
149
|
const leanSchema = extractSchemaFields(schemaFields)
|
|
123
150
|
if (Object.keys(leanSchema).length > 0) {
|
|
124
151
|
lean[schemaName] = leanSchema
|
|
@@ -204,6 +231,12 @@ export function extractRuntimeSchema(fullMeta) {
|
|
|
204
231
|
}
|
|
205
232
|
}
|
|
206
233
|
|
|
234
|
+
// Data inheritance - component receives cascaded data from page/site level fetches
|
|
235
|
+
// Can be: true (inherit all), false (inherit none), or ['schema1', 'schema2'] (selective)
|
|
236
|
+
if (fullMeta.inheritData !== undefined) {
|
|
237
|
+
runtime.inheritData = fullMeta.inheritData
|
|
238
|
+
}
|
|
239
|
+
|
|
207
240
|
return Object.keys(runtime).length > 0 ? runtime : null
|
|
208
241
|
}
|
|
209
242
|
|