@uniweb/build 0.15.0 → 0.15.2
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/site/build-site-data.js +7 -4
- package/src/site/config.js +20 -2
- package/src/site/content-collector.js +122 -47
- package/src/site/plugin.js +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"@uniweb/theming": "0.1.12"
|
|
64
64
|
},
|
|
65
65
|
"optionalDependencies": {
|
|
66
|
-
"@uniweb/content-reader": "1.1.
|
|
66
|
+
"@uniweb/content-reader": "1.1.15",
|
|
67
67
|
"@uniweb/schemas": "0.2.4",
|
|
68
|
-
"@uniweb/runtime": "0.8.
|
|
68
|
+
"@uniweb/runtime": "0.8.33"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@tailwindcss/vite": "^4.0.0",
|
|
75
75
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
76
76
|
"vite-plugin-svgr": "^4.0.0",
|
|
77
|
-
"@uniweb/core": "0.7.
|
|
77
|
+
"@uniweb/core": "0.7.27"
|
|
78
78
|
},
|
|
79
79
|
"peerDependenciesMeta": {
|
|
80
80
|
"vite": {
|
|
@@ -51,9 +51,12 @@ import { generateCollectionIndex } from '../search/collections.js'
|
|
|
51
51
|
* - `_pages/<route>.json` — only meaningful for static-host bundles
|
|
52
52
|
* where they're served as static assets. Worker derives them from
|
|
53
53
|
* `payload.locales[lang].pages[].sections` server-side.
|
|
54
|
-
* - `sitemap.xml`, `robots.txt
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
* - `sitemap.xml`, `robots.txt` — static-host bundle territory. Note these
|
|
55
|
+
* are simply absent from Uniweb-hosted sites today: nothing in platform
|
|
56
|
+
* generates them, at publish time or at request time.
|
|
57
|
+
* - `search-index.json` — the single-file bundle-mode form. Link mode emits
|
|
58
|
+
* the split `_search/{locale}/*.json` files instead (step 5 below), gated
|
|
59
|
+
* by `features: [search]`; those are what the hosting worker serves.
|
|
57
60
|
*
|
|
58
61
|
* @param {Object} params
|
|
59
62
|
* @param {string} params.siteRoot - Absolute path to the site directory.
|
|
@@ -98,7 +101,7 @@ export async function buildSiteData({
|
|
|
98
101
|
// No vite needed — collectSiteContent is a plain async function.
|
|
99
102
|
// dropUnpublished: link mode is always a published deploy — prune hidden
|
|
100
103
|
// pages + their subtree so drafts never reach the served site.
|
|
101
|
-
let siteContent = await collectSiteContent(resolvedSiteRoot, { foundationPath, dropUnpublished: true, base: basePath })
|
|
104
|
+
let siteContent = await collectSiteContent(resolvedSiteRoot, { foundationPath, dropUnpublished: true, base: basePath, strict: true })
|
|
102
105
|
|
|
103
106
|
// 2. Compile content collections (file-based markdown/yaml/json).
|
|
104
107
|
// `writeCollectionFiles` lands them under `<siteRoot>/public/data/`;
|
package/src/site/config.js
CHANGED
|
@@ -263,12 +263,18 @@ export function readIntelligenceConfig(siteRoot) {
|
|
|
263
263
|
* @param {string} [options.base] - Base public path for deployment (e.g., '/demos/mysite/')
|
|
264
264
|
* @returns {Promise<Object>} Vite configuration
|
|
265
265
|
*/
|
|
266
|
+
/** De-duplicate a list, keeping first-seen order. */
|
|
267
|
+
function unique(list) {
|
|
268
|
+
return [...new Set(list)]
|
|
269
|
+
}
|
|
270
|
+
|
|
266
271
|
export async function defineSiteConfig(options = {}) {
|
|
267
272
|
const {
|
|
268
273
|
plugins: extraPlugins = [],
|
|
269
274
|
server: serverOverrides = {},
|
|
270
275
|
build: buildOverrides = {},
|
|
271
276
|
resolve: resolveOverrides = {},
|
|
277
|
+
optimizeDeps: optimizeDepsOverrides = {},
|
|
272
278
|
seo = {},
|
|
273
279
|
assets = {},
|
|
274
280
|
search = {},
|
|
@@ -575,9 +581,21 @@ export async function defineSiteConfig(options = {}) {
|
|
|
575
581
|
...buildOverrides
|
|
576
582
|
},
|
|
577
583
|
|
|
584
|
+
// `include` and `exclude` are unioned with anything the site adds rather
|
|
585
|
+
// than replaced by it. Every other key here is a value a caller can
|
|
586
|
+
// reasonably want to override outright; these two are lists of things that
|
|
587
|
+
// must be true, and the framework's entries are load-bearing — React has to
|
|
588
|
+
// be prebundled or the site gets two copies of it. A site adding one
|
|
589
|
+
// package to `exclude` is asking for one more exclusion, not volunteering
|
|
590
|
+
// to restate the framework's list, and getting that wrong is silent: the
|
|
591
|
+
// dev server starts, and the failure surfaces somewhere unrelated.
|
|
578
592
|
optimizeDeps: {
|
|
579
|
-
|
|
580
|
-
|
|
593
|
+
...optimizeDepsOverrides,
|
|
594
|
+
include: unique([
|
|
595
|
+
'react', 'react-dom', 'react-dom/client', 'react-dom/server', 'react-router-dom',
|
|
596
|
+
...(optimizeDepsOverrides.include || [])
|
|
597
|
+
]),
|
|
598
|
+
exclude: unique(['#foundation', ...(optimizeDepsOverrides.exclude || [])])
|
|
581
599
|
},
|
|
582
600
|
|
|
583
601
|
...restOptions
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { readFile, readdir, stat } from 'node:fs/promises'
|
|
27
27
|
import { join, parse, resolve, sep } from 'node:path'
|
|
28
|
-
import { existsSync, statSync, realpathSync } from 'node:fs'
|
|
28
|
+
import { existsSync, statSync, realpathSync, readdirSync } from 'node:fs'
|
|
29
29
|
import yaml from 'js-yaml'
|
|
30
30
|
import { collectSectionAssets, mergeAssetCollections, collectConfigAssets } from './assets.js'
|
|
31
31
|
import { collectSectionIcons, mergeIconCollections, buildIconManifest } from './icons.js'
|
|
@@ -267,10 +267,18 @@ function stripAtPrefix(filename) {
|
|
|
267
267
|
|
|
268
268
|
/**
|
|
269
269
|
* Check if a folder should be ignored.
|
|
270
|
-
*
|
|
270
|
+
*
|
|
271
|
+
* Excludes folders starting with `_` (drafts/private) and with `.` (hidden).
|
|
272
|
+
*
|
|
273
|
+
* Hidden folders matter more than they look. A site's own `pages/` never holds
|
|
274
|
+
* one, but a mount target routinely does: point `paths:` at a directory that is
|
|
275
|
+
* also a git working tree — a sibling clone, which the docs suggest — and `.git`
|
|
276
|
+
* is a directory sitting right next to the content. Walked as content it
|
|
277
|
+
* contributed hundreds of routes. (A submodule hides this: there, `.git` is a
|
|
278
|
+
* file, so only the plain-clone case ever showed it.)
|
|
271
279
|
*/
|
|
272
280
|
function isIgnoredFolder(name) {
|
|
273
|
-
return name.startsWith('_')
|
|
281
|
+
return name.startsWith('_') || name.startsWith('.')
|
|
274
282
|
}
|
|
275
283
|
|
|
276
284
|
/**
|
|
@@ -339,6 +347,35 @@ async function readFolderConfig(dirPath, inheritedMode) {
|
|
|
339
347
|
return { config: {}, mode: inheritedMode, source: 'inherited' }
|
|
340
348
|
}
|
|
341
349
|
|
|
350
|
+
/**
|
|
351
|
+
* Whether a mount target holds anything this collector would read.
|
|
352
|
+
*
|
|
353
|
+
* A mount declares "pages come from here." An existing, readable directory is
|
|
354
|
+
* not the same claim — an uninitialized git submodule is exactly that, and it
|
|
355
|
+
* is the state a plain `git clone` leaves one in. So look for content the walk
|
|
356
|
+
* would actually pick up: a folder to recurse into, a markdown file, or a
|
|
357
|
+
* folder/page config. README.md deliberately does not count; isMarkdownFile
|
|
358
|
+
* excludes it as repo documentation.
|
|
359
|
+
*
|
|
360
|
+
* @param {string} dirPath - Absolute path to the mount target
|
|
361
|
+
* @returns {boolean}
|
|
362
|
+
*/
|
|
363
|
+
function mountHasContent(dirPath) {
|
|
364
|
+
let entries
|
|
365
|
+
try {
|
|
366
|
+
entries = readdirSync(dirPath, { withFileTypes: true })
|
|
367
|
+
} catch {
|
|
368
|
+
return false
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return entries.some(entry => {
|
|
372
|
+
const { name } = entry
|
|
373
|
+
if (name.startsWith('.') || isIgnoredFolder(name)) return false
|
|
374
|
+
if (entry.isDirectory()) return true
|
|
375
|
+
return isMarkdownFile(name) || name === 'folder.yml' || name === 'page.yml'
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
|
|
342
379
|
/**
|
|
343
380
|
* Extract page mounts from site.yml paths: config.
|
|
344
381
|
*
|
|
@@ -348,9 +385,14 @@ async function readFolderConfig(dirPath, inheritedMode) {
|
|
|
348
385
|
* @param {Object} pathsConfig - The paths: object from site.yml
|
|
349
386
|
* @param {string} sitePath - Absolute path to the site directory
|
|
350
387
|
* @param {string} pagesPath - Resolved absolute path to the pages directory
|
|
388
|
+
* @param {Object} [options]
|
|
389
|
+
* @param {boolean} [options.strict=false] - Treat a mount that contributes no
|
|
390
|
+
* pages as an error rather than a warning. Dev is forgiving — an empty folder
|
|
391
|
+
* is normal the moment an author creates one. A build is not: shipping a route
|
|
392
|
+
* with nothing under it is never what was meant.
|
|
351
393
|
* @returns {Map<string, string>|null} Route segment → canonical absolute path, or null
|
|
352
394
|
*/
|
|
353
|
-
function resolveMounts(pathsConfig, sitePath, pagesPath) {
|
|
395
|
+
function resolveMounts(pathsConfig, sitePath, pagesPath, { strict = false } = {}) {
|
|
354
396
|
if (!pathsConfig || typeof pathsConfig !== 'object') return null
|
|
355
397
|
|
|
356
398
|
// Extract entries with "pages/" prefix (e.g., "pages/docs": "../../../docs")
|
|
@@ -390,6 +432,22 @@ function resolveMounts(pathsConfig, sitePath, pagesPath) {
|
|
|
390
432
|
)
|
|
391
433
|
}
|
|
392
434
|
|
|
435
|
+
// Existing, readable, and empty — which is what a mount looks like when its
|
|
436
|
+
// git submodule was never fetched. Every check above passes and the route
|
|
437
|
+
// builds with nothing under it, so say so rather than shipping the silence.
|
|
438
|
+
if (!mountHasContent(absolutePath)) {
|
|
439
|
+
const detail =
|
|
440
|
+
`External pages path is empty: ${absolutePath}\n` +
|
|
441
|
+
` Declared in site.yml: pages/${routeSegment}: ${relativePath}\n` +
|
|
442
|
+
` The "${routeSegment}" route will have no pages under it.\n` +
|
|
443
|
+
` If this is a git submodule: git submodule update --init --recursive`
|
|
444
|
+
|
|
445
|
+
if (strict) {
|
|
446
|
+
throw new Error(`[content-collector] ${detail}`)
|
|
447
|
+
}
|
|
448
|
+
console.warn(`[content-collector] ${detail}`)
|
|
449
|
+
}
|
|
450
|
+
|
|
393
451
|
const canonical = realpathSync(absolutePath)
|
|
394
452
|
|
|
395
453
|
// Reject node_modules
|
|
@@ -1373,28 +1431,61 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1373
1431
|
let notFound = null
|
|
1374
1432
|
const versionedScopes = new Map() // scope route → versionMeta
|
|
1375
1433
|
|
|
1376
|
-
// First pass: discover all page folders and read their config
|
|
1377
|
-
|
|
1434
|
+
// First pass: discover all page folders and read their config.
|
|
1435
|
+
//
|
|
1436
|
+
// A route can be backed by a local directory, by a mount, or by both. When
|
|
1437
|
+
// both, the two configs LAYER rather than one replacing the other — the same
|
|
1438
|
+
// rule as params over meta defaults, or page over folder config. The division
|
|
1439
|
+
// it encodes: the mounted repo declares what its content is (ordering, title,
|
|
1440
|
+
// how it nests), the local folder declares how this site presents it (layout,
|
|
1441
|
+
// SEO, menu visibility). A local folder used to win outright, and since a
|
|
1442
|
+
// local folder is the only place `layout:` can be declared for a mounted
|
|
1443
|
+
// route, an author had to choose between a layout and the mounted repo's own
|
|
1444
|
+
// config. There was no way to see which one had been dropped.
|
|
1445
|
+
const localDirs = new Set()
|
|
1378
1446
|
for (const entry of entries) {
|
|
1379
1447
|
if (isIgnoredFolder(entry)) continue // Skip _prefixed folders
|
|
1380
|
-
const
|
|
1381
|
-
|
|
1382
|
-
|
|
1448
|
+
const stats = await stat(join(dirPath, entry))
|
|
1449
|
+
if (stats.isDirectory()) localDirs.add(entry)
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
const routeNames = new Set([...localDirs, ...(mounts?.keys() ?? [])])
|
|
1383
1453
|
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
const
|
|
1454
|
+
const pageFolders = []
|
|
1455
|
+
for (const name of routeNames) {
|
|
1456
|
+
const mountPath = mounts?.get(name) ?? null
|
|
1457
|
+
const localPath = localDirs.has(name) ? join(dirPath, name) : null
|
|
1458
|
+
|
|
1459
|
+
// The content's declaration about itself, then the site's about presenting it.
|
|
1460
|
+
const mounted = mountPath ? await readFolderConfig(mountPath, 'pages') : null
|
|
1461
|
+
const local = localPath ? await readFolderConfig(localPath, contentMode) : null
|
|
1462
|
+
|
|
1463
|
+
const dirConfig = mounted
|
|
1464
|
+
? { title: mounted.config.title || name, ...mounted.config, ...local?.config }
|
|
1465
|
+
: local.config
|
|
1466
|
+
|
|
1467
|
+
// Mode is not a config key — it comes from which config file exists, so it
|
|
1468
|
+
// cannot be spread. An explicit local declaration wins (a `page.yml` stub
|
|
1469
|
+
// with its own markdown is a real landing page above mounted children);
|
|
1470
|
+
// otherwise the mount decides, because it owns the content being read.
|
|
1471
|
+
const dirMode = local && local.source !== 'inherited'
|
|
1472
|
+
? local.mode
|
|
1473
|
+
: mounted?.mode ?? local?.mode ?? contentMode
|
|
1387
1474
|
|
|
1388
|
-
// Extract layout name from folder config (folder.yml layout: or page.yml layout:)
|
|
1389
1475
|
const folderLayout = typeof dirConfig.layout === 'string' ? dirConfig.layout
|
|
1390
1476
|
: dirConfig.layout?.name || null
|
|
1391
1477
|
|
|
1392
1478
|
pageFolders.push({
|
|
1393
|
-
name
|
|
1394
|
-
path:
|
|
1395
|
-
order:
|
|
1479
|
+
name,
|
|
1480
|
+
path: localPath ?? mountPath,
|
|
1481
|
+
order: typeof dirConfig.order === 'number' ? dirConfig.order : undefined,
|
|
1396
1482
|
dirConfig,
|
|
1397
1483
|
dirMode,
|
|
1484
|
+
// The mode the mounted subtree is walked in belongs to the mount: it is
|
|
1485
|
+
// the mount's tree. Without this a `page.yml` stub silently imposed page
|
|
1486
|
+
// mode on a mounted folder of pages, and only every mounted subfolder
|
|
1487
|
+
// carrying its own folder.yml kept that from collapsing them into one page.
|
|
1488
|
+
mountedContentMode: mounted?.mode ?? null,
|
|
1398
1489
|
childOrderConfig: {
|
|
1399
1490
|
pages: dirConfig.pages,
|
|
1400
1491
|
index: dirConfig.index
|
|
@@ -1403,29 +1494,6 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1403
1494
|
})
|
|
1404
1495
|
}
|
|
1405
1496
|
|
|
1406
|
-
// Inject virtual entries for mounts without physical directories
|
|
1407
|
-
if (mounts) {
|
|
1408
|
-
for (const [routeSegment, mountPath] of mounts) {
|
|
1409
|
-
if (!pageFolders.some(f => f.name === routeSegment)) {
|
|
1410
|
-
const { config: mountConfig } = await readFolderConfig(mountPath, 'pages')
|
|
1411
|
-
const mountLayout = typeof mountConfig.layout === 'string' ? mountConfig.layout
|
|
1412
|
-
: mountConfig.layout?.name || null
|
|
1413
|
-
pageFolders.push({
|
|
1414
|
-
name: routeSegment,
|
|
1415
|
-
path: mountPath,
|
|
1416
|
-
order: typeof mountConfig.order === 'number' ? mountConfig.order : undefined,
|
|
1417
|
-
dirConfig: { title: mountConfig.title || routeSegment, ...mountConfig },
|
|
1418
|
-
dirMode: 'pages',
|
|
1419
|
-
childOrderConfig: {
|
|
1420
|
-
pages: mountConfig.pages,
|
|
1421
|
-
index: mountConfig.index
|
|
1422
|
-
},
|
|
1423
|
-
childLayoutName: mountLayout
|
|
1424
|
-
})
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
1497
|
// Sort page folders by order (ascending), then alphabetically
|
|
1430
1498
|
// Pages without explicit order come after ordered pages (order ?? Infinity)
|
|
1431
1499
|
pageFolders.sort((a, b) => {
|
|
@@ -1565,7 +1633,7 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1565
1633
|
|
|
1566
1634
|
// Process subdirectories
|
|
1567
1635
|
for (const folder of orderedFolders) {
|
|
1568
|
-
const { name: entry, path: entryPath, dirConfig, dirMode, childOrderConfig, childLayoutName } = folder
|
|
1636
|
+
const { name: entry, path: entryPath, dirConfig, dirMode, mountedContentMode, childOrderConfig, childLayoutName } = folder
|
|
1569
1637
|
const isIndex = entry === indexName
|
|
1570
1638
|
const effectiveLayout = childLayoutName || parentLayoutName
|
|
1571
1639
|
|
|
@@ -1590,11 +1658,14 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1590
1658
|
|
|
1591
1659
|
pages.push(page)
|
|
1592
1660
|
|
|
1593
|
-
// Recurse into subdirectories (page mode)
|
|
1661
|
+
// Recurse into subdirectories (page mode). When the children come from
|
|
1662
|
+
// a mount, they are walked in the mount's own mode — it is the mount's
|
|
1663
|
+
// tree, and its root config is what declared how its content is shaped.
|
|
1594
1664
|
const childDirPath = mounts?.get(entry) || entryPath
|
|
1595
1665
|
const childParentRoute = isIndex ? parentRoute : page.route
|
|
1596
1666
|
const childFetch = page.fetch || parentFetch
|
|
1597
|
-
const
|
|
1667
|
+
const childContentMode = mountedContentMode ?? 'sections'
|
|
1668
|
+
const subResult = await collectPagesRecursive(childDirPath, childParentRoute, siteRoot, childOrderConfig, childFetch, versionContext, childContentMode, null, effectiveLayout)
|
|
1598
1669
|
pages.push(...subResult.pages)
|
|
1599
1670
|
assetCollection = mergeAssetCollections(assetCollection, subResult.assetCollection)
|
|
1600
1671
|
iconCollection = mergeIconCollections(iconCollection, subResult.iconCollection)
|
|
@@ -1693,7 +1764,7 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1693
1764
|
|
|
1694
1765
|
// Second pass: process each page folder
|
|
1695
1766
|
for (const folder of orderedFolders) {
|
|
1696
|
-
const { name: entry, path: entryPath, dirConfig, dirMode, childOrderConfig, childLayoutName } = folder
|
|
1767
|
+
const { name: entry, path: entryPath, dirConfig, dirMode, mountedContentMode, childOrderConfig, childLayoutName } = folder
|
|
1697
1768
|
const isIndex = entry === indexPageName
|
|
1698
1769
|
const effectiveLayout = childLayoutName || parentLayoutName
|
|
1699
1770
|
|
|
@@ -1782,14 +1853,17 @@ async function collectPagesRecursive(dirPath, parentRoute, siteRoot, orderConfig
|
|
|
1782
1853
|
pages.push(page)
|
|
1783
1854
|
}
|
|
1784
1855
|
|
|
1785
|
-
// Recursively process subdirectories
|
|
1856
|
+
// Recursively process subdirectories. Children coming from a mount are
|
|
1857
|
+
// walked in the mount's own mode — its root config declared how its
|
|
1858
|
+
// content is shaped, and this local stub holds none of that content.
|
|
1786
1859
|
{
|
|
1787
1860
|
const childDirPath = mounts?.get(entry) || entryPath
|
|
1788
1861
|
const childParentRoute = isIndex
|
|
1789
1862
|
? (hasExplicitOrder ? parentRoute : (page.sourcePath || page.route))
|
|
1790
1863
|
: page.route
|
|
1791
1864
|
const childFetch = page.fetch || parentFetch
|
|
1792
|
-
const
|
|
1865
|
+
const childContentMode = mountedContentMode ?? dirMode
|
|
1866
|
+
const subResult = await collectPagesRecursive(childDirPath, childParentRoute, siteRoot, childOrderConfig, childFetch, versionContext, childContentMode, null, effectiveLayout)
|
|
1793
1867
|
pages.push(...subResult.pages)
|
|
1794
1868
|
assetCollection = mergeAssetCollections(assetCollection, subResult.assetCollection)
|
|
1795
1869
|
iconCollection = mergeIconCollections(iconCollection, subResult.iconCollection)
|
|
@@ -2011,10 +2085,11 @@ async function collectLayouts(layoutDir, siteRoot, layoutNames = new Set()) {
|
|
|
2011
2085
|
* @param {string} options.foundationPath - Path to foundation directory (for theme vars)
|
|
2012
2086
|
* @param {string} [options.configFile='site.yml'] - Name of the top-level config file inside sitePath. Defaults to 'site.yml'. Document tools (unipress) pass 'document.yml'.
|
|
2013
2087
|
* @param {'document'|'site'} [options.profile] - Explicit content profile, authoritative over the filename. Lets a tool that knows its context (unipress → 'document') read an arbitrarily named config with the right directory/mode/ordering. Falls back to the configFile-derived profile when omitted.
|
|
2088
|
+
* @param {boolean} [options.strict=false] - Fail on content problems that would ship broken output rather than warning about them. A production build passes true; dev leaves it false, because a folder an author just created is empty for a moment and that is not an error.
|
|
2014
2089
|
* @returns {Promise<Object>} Site content object with assets manifest
|
|
2015
2090
|
*/
|
|
2016
2091
|
export async function collectSiteContent(sitePath, options = {}) {
|
|
2017
|
-
const { foundationPath, configFile = 'site.yml', profile: profileName, dropUnpublished = false, base = '/' } = options
|
|
2092
|
+
const { foundationPath, configFile = 'site.yml', profile: profileName, dropUnpublished = false, base = '/', strict = false } = options
|
|
2018
2093
|
|
|
2019
2094
|
// Read site config and raw theme config
|
|
2020
2095
|
const siteConfig = await readYamlFile(join(sitePath, configFile))
|
|
@@ -2054,7 +2129,7 @@ export async function collectSiteContent(sitePath, options = {}) {
|
|
|
2054
2129
|
: profileDefault
|
|
2055
2130
|
}
|
|
2056
2131
|
|
|
2057
|
-
const mounts = resolveMounts(siteConfig.paths, sitePath, pagesPath)
|
|
2132
|
+
const mounts = resolveMounts(siteConfig.paths, sitePath, pagesPath, { strict })
|
|
2058
2133
|
|
|
2059
2134
|
const layoutPath = siteConfig.paths?.layout
|
|
2060
2135
|
? resolve(sitePath, siteConfig.paths.layout)
|
package/src/site/plugin.js
CHANGED
|
@@ -639,7 +639,9 @@ export function siteContentPlugin(options = {}) {
|
|
|
639
639
|
try {
|
|
640
640
|
// dropUnpublished only on a production build — in dev (serve) hidden
|
|
641
641
|
// pages stay in the graph so in-progress drafts remain previewable.
|
|
642
|
-
|
|
642
|
+
// strict on a production build: a mount that contributes no pages is a
|
|
643
|
+
// warning while you author and a broken deploy once you ship.
|
|
644
|
+
siteContent = await collectSiteContent(resolvedSitePath, { foundationPath, dropUnpublished: isProduction, base: basePath, strict: isProduction })
|
|
643
645
|
headHtml = await loadHeadHtml()
|
|
644
646
|
console.log(`[site-content] Collected ${siteContent.pages?.length || 0} pages`)
|
|
645
647
|
|