@uniweb/build 0.8.12 → 0.8.13
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 +3 -3
- package/src/site/assets.js +1 -1
- package/src/site/collection-processor.js +77 -26
- package/src/site/plugin.js +5 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.13",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
53
53
|
"@uniweb/content-reader": "1.1.4",
|
|
54
|
-
"@uniweb/runtime": "0.6.
|
|
54
|
+
"@uniweb/runtime": "0.6.9",
|
|
55
55
|
"@uniweb/schemas": "0.2.1"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@tailwindcss/vite": "^4.0.0",
|
|
62
62
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
63
63
|
"vite-plugin-svgr": "^4.0.0",
|
|
64
|
-
"@uniweb/core": "0.5.
|
|
64
|
+
"@uniweb/core": "0.5.10"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"vite": {
|
package/src/site/assets.js
CHANGED
|
@@ -56,7 +56,7 @@ function isPdfPath(src) {
|
|
|
56
56
|
* @param {string} value - String to check
|
|
57
57
|
* @returns {boolean} True if it looks like a local asset path
|
|
58
58
|
*/
|
|
59
|
-
function isLocalAssetPath(value) {
|
|
59
|
+
export function isLocalAssetPath(value) {
|
|
60
60
|
if (typeof value !== 'string' || !value) return false
|
|
61
61
|
|
|
62
62
|
// Skip external URLs
|
|
@@ -30,7 +30,7 @@ import { join, basename, extname, dirname, relative, resolve } from 'node:path'
|
|
|
30
30
|
import { existsSync } from 'node:fs'
|
|
31
31
|
import yaml from 'js-yaml'
|
|
32
32
|
import { applyFilter, applySort } from './data-fetcher.js'
|
|
33
|
-
import { resolveAssetPath, walkContentAssets } from './assets.js'
|
|
33
|
+
import { resolveAssetPath, walkContentAssets, isLocalAssetPath } from './assets.js'
|
|
34
34
|
|
|
35
35
|
// Try to import content-reader for markdown parsing
|
|
36
36
|
let markdownToProseMirror
|
|
@@ -222,7 +222,7 @@ function isExternalUrl(src) {
|
|
|
222
222
|
* @param {string} collectionName - Name of the collection (e.g., 'articles')
|
|
223
223
|
* @returns {Promise<Object>} Asset manifest for this item
|
|
224
224
|
*/
|
|
225
|
-
async function processCollectionAssets(content, itemPath, siteRoot, collectionName) {
|
|
225
|
+
async function processCollectionAssets(content, itemPath, siteRoot, collectionName, basePath) {
|
|
226
226
|
const assets = {}
|
|
227
227
|
const itemDir = dirname(itemPath)
|
|
228
228
|
const publicDir = join(siteRoot, 'public')
|
|
@@ -259,7 +259,7 @@ async function processCollectionAssets(content, itemPath, siteRoot, collectionNa
|
|
|
259
259
|
await copyFile(result.resolved, targetPath)
|
|
260
260
|
|
|
261
261
|
// Update path to site-root-relative
|
|
262
|
-
finalPath =
|
|
262
|
+
finalPath = `${basePath}collections/${collectionName}/${assetFilename}`
|
|
263
263
|
|
|
264
264
|
assets[src] = {
|
|
265
265
|
original: src,
|
|
@@ -294,7 +294,7 @@ async function processCollectionAssets(content, itemPath, siteRoot, collectionNa
|
|
|
294
294
|
const posterTarget = join(targetDir, posterFilename)
|
|
295
295
|
await mkdir(targetDir, { recursive: true })
|
|
296
296
|
await copyFile(posterResult.resolved, posterTarget)
|
|
297
|
-
node.attrs.poster =
|
|
297
|
+
node.attrs.poster = `${basePath}collections/${collectionName}/${posterFilename}`
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
300
|
|
|
@@ -305,7 +305,7 @@ async function processCollectionAssets(content, itemPath, siteRoot, collectionNa
|
|
|
305
305
|
const previewTarget = join(targetDir, previewFilename)
|
|
306
306
|
await mkdir(targetDir, { recursive: true })
|
|
307
307
|
await copyFile(previewResult.resolved, previewTarget)
|
|
308
|
-
node.attrs.preview =
|
|
308
|
+
node.attrs.preview = `${basePath}collections/${collectionName}/${previewFilename}`
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
}
|
|
@@ -313,6 +313,53 @@ async function processCollectionAssets(content, itemPath, siteRoot, collectionNa
|
|
|
313
313
|
return assets
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Process assets in a data item (YAML/JSON)
|
|
318
|
+
* - Recursively walks the data object looking for local asset paths
|
|
319
|
+
* - Copies co-located assets to public/collections/<collection>/
|
|
320
|
+
* - Rewrites paths to absolute URLs (with base path)
|
|
321
|
+
*
|
|
322
|
+
* @param {Object} data - Parsed data object (mutated in place)
|
|
323
|
+
* @param {string} itemPath - Path to the data file
|
|
324
|
+
* @param {string} siteRoot - Site root directory
|
|
325
|
+
* @param {string} collectionName - Name of the collection
|
|
326
|
+
* @param {string} basePath - Site base path (e.g., '/' or '/docs/')
|
|
327
|
+
*/
|
|
328
|
+
async function processDataItemAssets(data, itemPath, siteRoot, collectionName, basePath) {
|
|
329
|
+
const targetDir = join(siteRoot, 'public', 'collections', collectionName)
|
|
330
|
+
|
|
331
|
+
async function walk(parent, key) {
|
|
332
|
+
const val = parent[key]
|
|
333
|
+
if (typeof val === 'string' && isLocalAssetPath(val)) {
|
|
334
|
+
if (val.startsWith('./') || val.startsWith('../')) {
|
|
335
|
+
const resolved = resolve(dirname(itemPath), val)
|
|
336
|
+
if (existsSync(resolved)) {
|
|
337
|
+
const filename = basename(resolved)
|
|
338
|
+
await mkdir(targetDir, { recursive: true })
|
|
339
|
+
await copyFile(resolved, join(targetDir, filename))
|
|
340
|
+
parent[key] = `${basePath}collections/${collectionName}/${filename}`
|
|
341
|
+
}
|
|
342
|
+
} else if (val.startsWith('/')) {
|
|
343
|
+
// Absolute site path — just prepend base
|
|
344
|
+
parent[key] = `${basePath}${val.slice(1)}`
|
|
345
|
+
}
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
if (Array.isArray(val)) {
|
|
349
|
+
for (let i = 0; i < val.length; i++) await walk(val, i)
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
if (val && typeof val === 'object') {
|
|
353
|
+
for (const k of Object.keys(val)) await walk(val, k)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
for (const key of Object.keys(data)) {
|
|
358
|
+
if (key === 'slug') continue
|
|
359
|
+
await walk(data, key)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
316
363
|
// Filter and sort utilities are imported from data-fetcher.js
|
|
317
364
|
|
|
318
365
|
/**
|
|
@@ -325,7 +372,7 @@ async function processCollectionAssets(content, itemPath, siteRoot, collectionNa
|
|
|
325
372
|
* @param {string} filename - YAML filename (.yml or .yaml)
|
|
326
373
|
* @returns {Promise<Object|null>} Processed item or null if unpublished
|
|
327
374
|
*/
|
|
328
|
-
async function processDataItem(dir, filename) {
|
|
375
|
+
async function processDataItem(dir, filename, siteRoot, collectionName, basePath) {
|
|
329
376
|
const filepath = join(dir, filename)
|
|
330
377
|
const raw = await readFile(filepath, 'utf-8')
|
|
331
378
|
const slug = basename(filename, extname(filename))
|
|
@@ -334,7 +381,9 @@ async function processDataItem(dir, filename) {
|
|
|
334
381
|
// Skip unpublished items
|
|
335
382
|
if (data.published === false) return null
|
|
336
383
|
|
|
337
|
-
|
|
384
|
+
const item = { slug, ...data }
|
|
385
|
+
await processDataItemAssets(item, filepath, siteRoot, collectionName, basePath)
|
|
386
|
+
return item
|
|
338
387
|
}
|
|
339
388
|
|
|
340
389
|
/**
|
|
@@ -348,18 +397,27 @@ async function processDataItem(dir, filename) {
|
|
|
348
397
|
* @param {string} filename - JSON filename
|
|
349
398
|
* @returns {Promise<Object|Array|null>} Processed item(s) or null if unpublished
|
|
350
399
|
*/
|
|
351
|
-
async function processJsonItem(dir, filename) {
|
|
400
|
+
async function processJsonItem(dir, filename, siteRoot, collectionName, basePath) {
|
|
352
401
|
const filepath = join(dir, filename)
|
|
353
402
|
const raw = await readFile(filepath, 'utf-8')
|
|
354
403
|
const slug = basename(filename, '.json')
|
|
355
404
|
const data = JSON.parse(raw)
|
|
356
405
|
|
|
357
406
|
// Array → multiple items (single-file collection)
|
|
358
|
-
if (Array.isArray(data))
|
|
407
|
+
if (Array.isArray(data)) {
|
|
408
|
+
for (const item of data) {
|
|
409
|
+
if (item && typeof item === 'object') {
|
|
410
|
+
await processDataItemAssets(item, filepath, siteRoot, collectionName, basePath)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return data
|
|
414
|
+
}
|
|
359
415
|
|
|
360
416
|
// Object → single item
|
|
361
417
|
if (data.published === false) return null
|
|
362
|
-
|
|
418
|
+
const item = { slug, ...data }
|
|
419
|
+
await processDataItemAssets(item, filepath, siteRoot, collectionName, basePath)
|
|
420
|
+
return item
|
|
363
421
|
}
|
|
364
422
|
|
|
365
423
|
/**
|
|
@@ -371,7 +429,7 @@ async function processJsonItem(dir, filename) {
|
|
|
371
429
|
* @param {string} siteRoot - Site root directory for asset resolution
|
|
372
430
|
* @returns {Promise<Object|null>} Processed item or null if unpublished
|
|
373
431
|
*/
|
|
374
|
-
async function processContentItem(dir, filename, config, siteRoot) {
|
|
432
|
+
async function processContentItem(dir, filename, config, siteRoot, basePath) {
|
|
375
433
|
const filepath = join(dir, filename)
|
|
376
434
|
const raw = await readFile(filepath, 'utf-8')
|
|
377
435
|
const slug = basename(filename, extname(filename))
|
|
@@ -389,7 +447,7 @@ async function processContentItem(dir, filename, config, siteRoot) {
|
|
|
389
447
|
|
|
390
448
|
// Process assets (resolve paths, copy co-located files)
|
|
391
449
|
// This modifies content in place, updating paths to site-root-relative
|
|
392
|
-
await processCollectionAssets(content, filepath, siteRoot, config.name)
|
|
450
|
+
await processCollectionAssets(content, filepath, siteRoot, config.name, basePath)
|
|
393
451
|
|
|
394
452
|
// Extract excerpt
|
|
395
453
|
const excerpt = extractExcerpt(frontmatter, content, config.excerpt)
|
|
@@ -398,19 +456,12 @@ async function processContentItem(dir, filename, config, siteRoot) {
|
|
|
398
456
|
// Note: paths in content have already been updated by processCollectionAssets
|
|
399
457
|
const image = frontmatter.image || extractFirstImage(content)
|
|
400
458
|
|
|
401
|
-
// Get file stats for lastModified
|
|
402
|
-
const fileStat = await stat(filepath)
|
|
403
|
-
|
|
404
459
|
return {
|
|
405
460
|
slug,
|
|
406
461
|
...frontmatter,
|
|
407
462
|
excerpt,
|
|
408
463
|
image,
|
|
409
|
-
|
|
410
|
-
// and ProseMirror content (for rich rendering)
|
|
411
|
-
body: body.trim(),
|
|
412
|
-
content,
|
|
413
|
-
lastModified: fileStat.mtime.toISOString()
|
|
464
|
+
content
|
|
414
465
|
}
|
|
415
466
|
}
|
|
416
467
|
|
|
@@ -421,7 +472,7 @@ async function processContentItem(dir, filename, config, siteRoot) {
|
|
|
421
472
|
* @param {Object} config - Parsed collection config
|
|
422
473
|
* @returns {Promise<Array>} Array of processed items
|
|
423
474
|
*/
|
|
424
|
-
async function collectItems(siteDir, config, collectionsBase) {
|
|
475
|
+
async function collectItems(siteDir, config, collectionsBase, basePath) {
|
|
425
476
|
const base = collectionsBase || siteDir
|
|
426
477
|
const collectionDir = resolve(base, config.path)
|
|
427
478
|
|
|
@@ -441,12 +492,12 @@ async function collectItems(siteDir, config, collectionsBase) {
|
|
|
441
492
|
let items = await Promise.all(
|
|
442
493
|
itemFiles.map(file => {
|
|
443
494
|
if (file.endsWith('.json')) {
|
|
444
|
-
return processJsonItem(collectionDir, file)
|
|
495
|
+
return processJsonItem(collectionDir, file, siteDir, config.name, basePath)
|
|
445
496
|
}
|
|
446
497
|
if (file.endsWith('.yml') || file.endsWith('.yaml')) {
|
|
447
|
-
return processDataItem(collectionDir, file)
|
|
498
|
+
return processDataItem(collectionDir, file, siteDir, config.name, basePath)
|
|
448
499
|
}
|
|
449
|
-
return processContentItem(collectionDir, file, config, siteDir)
|
|
500
|
+
return processContentItem(collectionDir, file, config, siteDir, basePath)
|
|
450
501
|
})
|
|
451
502
|
)
|
|
452
503
|
|
|
@@ -497,7 +548,7 @@ async function collectItems(siteDir, config, collectionsBase) {
|
|
|
497
548
|
* })
|
|
498
549
|
* // { articles: [...], products: [...] }
|
|
499
550
|
*/
|
|
500
|
-
export async function processCollections(siteDir, collectionsConfig, collectionsBase) {
|
|
551
|
+
export async function processCollections(siteDir, collectionsConfig, collectionsBase, basePath = '/') {
|
|
501
552
|
if (!collectionsConfig || typeof collectionsConfig !== 'object') {
|
|
502
553
|
return {}
|
|
503
554
|
}
|
|
@@ -506,7 +557,7 @@ export async function processCollections(siteDir, collectionsConfig, collections
|
|
|
506
557
|
|
|
507
558
|
for (const [name, config] of Object.entries(collectionsConfig)) {
|
|
508
559
|
const parsed = parseCollectionConfig(name, config)
|
|
509
|
-
const items = await collectItems(siteDir, parsed, collectionsBase)
|
|
560
|
+
const items = await collectItems(siteDir, parsed, collectionsBase, basePath)
|
|
510
561
|
results[name] = items
|
|
511
562
|
console.log(`[collection-processor] Processed ${name}: ${items.length} items`)
|
|
512
563
|
}
|
package/src/site/plugin.js
CHANGED
|
@@ -381,6 +381,7 @@ export function siteContentPlugin(options = {}) {
|
|
|
381
381
|
let resolvedLayoutPath = null // Resolved from site.yml layoutDir or default
|
|
382
382
|
let resolvedCollectionsBase = null // Resolved from site.yml collectionsDir
|
|
383
383
|
let headHtml = '' // Contents of site/head.html for injection
|
|
384
|
+
let basePath = '/' // Vite's config.base, always has trailing slash
|
|
384
385
|
|
|
385
386
|
/**
|
|
386
387
|
* Load translations for a specific locale
|
|
@@ -494,6 +495,7 @@ export function siteContentPlugin(options = {}) {
|
|
|
494
495
|
resolvedSitePath = resolve(config.root, sitePath)
|
|
495
496
|
resolvedOutDir = resolve(config.root, config.build.outDir)
|
|
496
497
|
isProduction = config.command === 'build'
|
|
498
|
+
basePath = config.base || '/'
|
|
497
499
|
|
|
498
500
|
// In dev mode, process collections early so JSON files exist before server starts
|
|
499
501
|
// This runs before configureServer, ensuring data is available immediately
|
|
@@ -517,7 +519,7 @@ export function siteContentPlugin(options = {}) {
|
|
|
517
519
|
|
|
518
520
|
if (collectionsConfig) {
|
|
519
521
|
console.log('[site-content] Processing content collections...')
|
|
520
|
-
const collections = await processCollections(resolvedSitePath, collectionsConfig, resolvedCollectionsBase)
|
|
522
|
+
const collections = await processCollections(resolvedSitePath, collectionsConfig, resolvedCollectionsBase, basePath)
|
|
521
523
|
await writeCollectionFiles(resolvedSitePath, collections)
|
|
522
524
|
}
|
|
523
525
|
} catch (err) {
|
|
@@ -554,7 +556,7 @@ export function siteContentPlugin(options = {}) {
|
|
|
554
556
|
// In production, do it here
|
|
555
557
|
if (isProduction && siteContent.config?.collections) {
|
|
556
558
|
console.log('[site-content] Processing content collections...')
|
|
557
|
-
const collections = await processCollections(resolvedSitePath, siteContent.config.collections, resolvedCollectionsBase)
|
|
559
|
+
const collections = await processCollections(resolvedSitePath, siteContent.config.collections, resolvedCollectionsBase, basePath)
|
|
558
560
|
await writeCollectionFiles(resolvedSitePath, collections)
|
|
559
561
|
}
|
|
560
562
|
|
|
@@ -617,7 +619,7 @@ export function siteContentPlugin(options = {}) {
|
|
|
617
619
|
// Use collectionsConfig (cached from configResolved) or siteContent
|
|
618
620
|
const collections = collectionsConfig || siteContent?.config?.collections
|
|
619
621
|
if (collections) {
|
|
620
|
-
const processed = await processCollections(resolvedSitePath, collections, resolvedCollectionsBase)
|
|
622
|
+
const processed = await processCollections(resolvedSitePath, collections, resolvedCollectionsBase, basePath)
|
|
621
623
|
await writeCollectionFiles(resolvedSitePath, processed)
|
|
622
624
|
}
|
|
623
625
|
// Send full reload to client
|