@uniweb/build 0.2.3 → 0.2.4
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/collection-processor.js +121 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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/content-reader": "1.0.
|
|
54
|
-
"@uniweb/runtime": "0.3.
|
|
53
|
+
"@uniweb/content-reader": "1.0.6",
|
|
54
|
+
"@uniweb/runtime": "0.3.1"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -25,11 +25,12 @@
|
|
|
25
25
|
* await writeCollectionFiles(siteDir, collections)
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import { readFile, readdir, stat, writeFile, mkdir } from 'node:fs/promises'
|
|
29
|
-
import { join, basename, extname } from 'node:path'
|
|
28
|
+
import { readFile, readdir, stat, writeFile, mkdir, copyFile } from 'node:fs/promises'
|
|
29
|
+
import { join, basename, extname, dirname, relative } 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
34
|
|
|
34
35
|
// Try to import content-reader for markdown parsing
|
|
35
36
|
let markdownToProseMirror
|
|
@@ -202,6 +203,116 @@ function extractFirstImage(node) {
|
|
|
202
203
|
return null
|
|
203
204
|
}
|
|
204
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Check if a path is external (http/https/data URL)
|
|
208
|
+
*/
|
|
209
|
+
function isExternalUrl(src) {
|
|
210
|
+
return /^(https?:)?\/\//.test(src) || src.startsWith('data:')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Process assets in collection content
|
|
215
|
+
* - Resolves relative paths to site-root-relative paths
|
|
216
|
+
* - Copies co-located assets to public/library/<collection>/
|
|
217
|
+
* - Updates paths in the content in place
|
|
218
|
+
*
|
|
219
|
+
* @param {Object} content - ProseMirror document
|
|
220
|
+
* @param {string} itemPath - Path to the markdown file
|
|
221
|
+
* @param {string} siteRoot - Site root directory
|
|
222
|
+
* @param {string} collectionName - Name of the collection (e.g., 'articles')
|
|
223
|
+
* @returns {Promise<Object>} Asset manifest for this item
|
|
224
|
+
*/
|
|
225
|
+
async function processCollectionAssets(content, itemPath, siteRoot, collectionName) {
|
|
226
|
+
const assets = {}
|
|
227
|
+
const itemDir = dirname(itemPath)
|
|
228
|
+
const publicDir = join(siteRoot, 'public')
|
|
229
|
+
const targetDir = join(publicDir, 'library', collectionName)
|
|
230
|
+
|
|
231
|
+
// Walk content and collect asset paths
|
|
232
|
+
const assetNodes = []
|
|
233
|
+
walkContentAssets(content, (node, path, attrName) => {
|
|
234
|
+
assetNodes.push({ node, attrName })
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
for (const { node, attrName } of assetNodes) {
|
|
238
|
+
const src = node.attrs.src
|
|
239
|
+
if (!src || isExternalUrl(src)) continue
|
|
240
|
+
|
|
241
|
+
// Resolve the path
|
|
242
|
+
const result = resolveAssetPath(src, itemPath, siteRoot)
|
|
243
|
+
if (result.external || !result.resolved) continue
|
|
244
|
+
|
|
245
|
+
let finalPath = src
|
|
246
|
+
|
|
247
|
+
// Handle relative paths (co-located assets)
|
|
248
|
+
if (src.startsWith('./') || src.startsWith('../')) {
|
|
249
|
+
// Check if file exists at resolved location
|
|
250
|
+
if (existsSync(result.resolved)) {
|
|
251
|
+
// Copy to public/library/<collection>/
|
|
252
|
+
const assetFilename = basename(result.resolved)
|
|
253
|
+
const targetPath = join(targetDir, assetFilename)
|
|
254
|
+
|
|
255
|
+
// Ensure target directory exists
|
|
256
|
+
await mkdir(targetDir, { recursive: true })
|
|
257
|
+
|
|
258
|
+
// Copy the asset
|
|
259
|
+
await copyFile(result.resolved, targetPath)
|
|
260
|
+
|
|
261
|
+
// Update path to site-root-relative
|
|
262
|
+
finalPath = `/library/${collectionName}/${assetFilename}`
|
|
263
|
+
|
|
264
|
+
assets[src] = {
|
|
265
|
+
original: src,
|
|
266
|
+
resolved: result.resolved,
|
|
267
|
+
copied: targetPath,
|
|
268
|
+
publicPath: finalPath
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Handle absolute site paths - just validate they exist
|
|
273
|
+
else if (src.startsWith('/')) {
|
|
274
|
+
const publicPath = join(publicDir, src)
|
|
275
|
+
if (existsSync(publicPath)) {
|
|
276
|
+
assets[src] = {
|
|
277
|
+
original: src,
|
|
278
|
+
resolved: publicPath,
|
|
279
|
+
publicPath: src
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Update the node's src attribute if path changed
|
|
285
|
+
if (finalPath !== src) {
|
|
286
|
+
node.attrs.src = finalPath
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Also handle poster/preview attributes
|
|
290
|
+
if (node.attrs.poster && !isExternalUrl(node.attrs.poster)) {
|
|
291
|
+
const posterResult = resolveAssetPath(node.attrs.poster, itemPath, siteRoot)
|
|
292
|
+
if (posterResult.resolved && existsSync(posterResult.resolved)) {
|
|
293
|
+
const posterFilename = basename(posterResult.resolved)
|
|
294
|
+
const posterTarget = join(targetDir, posterFilename)
|
|
295
|
+
await mkdir(targetDir, { recursive: true })
|
|
296
|
+
await copyFile(posterResult.resolved, posterTarget)
|
|
297
|
+
node.attrs.poster = `/library/${collectionName}/${posterFilename}`
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (node.attrs.preview && !isExternalUrl(node.attrs.preview)) {
|
|
302
|
+
const previewResult = resolveAssetPath(node.attrs.preview, itemPath, siteRoot)
|
|
303
|
+
if (previewResult.resolved && existsSync(previewResult.resolved)) {
|
|
304
|
+
const previewFilename = basename(previewResult.resolved)
|
|
305
|
+
const previewTarget = join(targetDir, previewFilename)
|
|
306
|
+
await mkdir(targetDir, { recursive: true })
|
|
307
|
+
await copyFile(previewResult.resolved, previewTarget)
|
|
308
|
+
node.attrs.preview = `/library/${collectionName}/${previewFilename}`
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return assets
|
|
314
|
+
}
|
|
315
|
+
|
|
205
316
|
// Filter and sort utilities are imported from data-fetcher.js
|
|
206
317
|
|
|
207
318
|
/**
|
|
@@ -210,9 +321,10 @@ function extractFirstImage(node) {
|
|
|
210
321
|
* @param {string} dir - Collection directory path
|
|
211
322
|
* @param {string} filename - Markdown filename
|
|
212
323
|
* @param {Object} config - Collection configuration
|
|
324
|
+
* @param {string} siteRoot - Site root directory for asset resolution
|
|
213
325
|
* @returns {Promise<Object|null>} Processed item or null if unpublished
|
|
214
326
|
*/
|
|
215
|
-
async function processContentItem(dir, filename, config) {
|
|
327
|
+
async function processContentItem(dir, filename, config, siteRoot) {
|
|
216
328
|
const filepath = join(dir, filename)
|
|
217
329
|
const raw = await readFile(filepath, 'utf-8')
|
|
218
330
|
const slug = basename(filename, extname(filename))
|
|
@@ -228,10 +340,15 @@ async function processContentItem(dir, filename, config) {
|
|
|
228
340
|
// Parse markdown body to ProseMirror
|
|
229
341
|
const content = markdownToProseMirror(body)
|
|
230
342
|
|
|
343
|
+
// Process assets (resolve paths, copy co-located files)
|
|
344
|
+
// This modifies content in place, updating paths to site-root-relative
|
|
345
|
+
await processCollectionAssets(content, filepath, siteRoot, config.name)
|
|
346
|
+
|
|
231
347
|
// Extract excerpt
|
|
232
348
|
const excerpt = extractExcerpt(frontmatter, content, config.excerpt)
|
|
233
349
|
|
|
234
350
|
// Extract first image (frontmatter takes precedence)
|
|
351
|
+
// Note: paths in content have already been updated by processCollectionAssets
|
|
235
352
|
const image = frontmatter.image || extractFirstImage(content)
|
|
236
353
|
|
|
237
354
|
// Get file stats for lastModified
|
|
@@ -271,7 +388,7 @@ async function collectItems(siteDir, config) {
|
|
|
271
388
|
|
|
272
389
|
// Process all markdown files
|
|
273
390
|
let items = await Promise.all(
|
|
274
|
-
mdFiles.map(file => processContentItem(collectionDir, file, config))
|
|
391
|
+
mdFiles.map(file => processContentItem(collectionDir, file, config, siteDir))
|
|
275
392
|
)
|
|
276
393
|
|
|
277
394
|
// Filter out nulls (unpublished items)
|