@uniweb/build 0.4.10 → 0.6.0
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/README.md +3 -3
- package/package.json +4 -3
- package/src/docs.js +3 -3
- package/src/foundation/config.js +3 -3
- package/src/generate-entry.js +29 -10
- package/src/i18n/collections.js +479 -126
- package/src/i18n/index.js +2 -0
- package/src/prerender.js +59 -73
- package/src/runtime-schema.js +36 -7
- package/src/schema.js +196 -19
- package/src/site/collection-processor.js +73 -9
- package/src/site/data-fetcher.js +3 -1
- package/src/site/plugin.js +93 -15
- package/src/theme/css-generator.js +21 -1
- package/src/theme/processor.js +8 -0
- package/src/utils/infer-title.js +16 -0
- package/src/vite-foundation-plugin.js +17 -5
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Collection Processor
|
|
3
3
|
*
|
|
4
|
-
* Processes content collections from markdown files into JSON data.
|
|
4
|
+
* Processes content collections from markdown and YAML files into JSON data.
|
|
5
5
|
* Collections are defined in site.yml and processed at build time.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
8
|
-
* - Discovers markdown files in collection folders
|
|
9
|
-
* - Parses frontmatter for metadata
|
|
8
|
+
* - Discovers markdown (.md), data (.yml/.yaml), and JSON (.json) files in collection folders
|
|
9
|
+
* - Parses frontmatter for metadata (markdown), full YAML (data items), or JSON (data items)
|
|
10
10
|
* - Converts markdown body to ProseMirror JSON
|
|
11
11
|
* - Supports filtering, sorting, and limiting
|
|
12
|
-
* - Auto-generates excerpts and extracts first images
|
|
12
|
+
* - Auto-generates excerpts and extracts first images (markdown items only)
|
|
13
13
|
*
|
|
14
14
|
* @module @uniweb/build/site/collection-processor
|
|
15
15
|
*
|
|
@@ -315,6 +315,53 @@ async function processCollectionAssets(content, itemPath, siteRoot, collectionNa
|
|
|
315
315
|
|
|
316
316
|
// Filter and sort utilities are imported from data-fetcher.js
|
|
317
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Process a single data item from a YAML file
|
|
320
|
+
*
|
|
321
|
+
* YAML items are pure data — no ProseMirror conversion, no body, no excerpt,
|
|
322
|
+
* no image extraction, no lastModified. The output is slug + YAML fields.
|
|
323
|
+
*
|
|
324
|
+
* @param {string} dir - Collection directory path
|
|
325
|
+
* @param {string} filename - YAML filename (.yml or .yaml)
|
|
326
|
+
* @returns {Promise<Object|null>} Processed item or null if unpublished
|
|
327
|
+
*/
|
|
328
|
+
async function processDataItem(dir, filename) {
|
|
329
|
+
const filepath = join(dir, filename)
|
|
330
|
+
const raw = await readFile(filepath, 'utf-8')
|
|
331
|
+
const slug = basename(filename, extname(filename))
|
|
332
|
+
const data = yaml.load(raw) || {}
|
|
333
|
+
|
|
334
|
+
// Skip unpublished items
|
|
335
|
+
if (data.published === false) return null
|
|
336
|
+
|
|
337
|
+
return { slug, ...data }
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Process a single data item from a JSON file
|
|
342
|
+
*
|
|
343
|
+
* JSON items are pure data — like YAML items, no ProseMirror conversion.
|
|
344
|
+
* A JSON file containing an array returns all items (single-file collection).
|
|
345
|
+
* A JSON file containing an object returns a single item with slug from filename.
|
|
346
|
+
*
|
|
347
|
+
* @param {string} dir - Collection directory path
|
|
348
|
+
* @param {string} filename - JSON filename
|
|
349
|
+
* @returns {Promise<Object|Array|null>} Processed item(s) or null if unpublished
|
|
350
|
+
*/
|
|
351
|
+
async function processJsonItem(dir, filename) {
|
|
352
|
+
const filepath = join(dir, filename)
|
|
353
|
+
const raw = await readFile(filepath, 'utf-8')
|
|
354
|
+
const slug = basename(filename, '.json')
|
|
355
|
+
const data = JSON.parse(raw)
|
|
356
|
+
|
|
357
|
+
// Array → multiple items (single-file collection)
|
|
358
|
+
if (Array.isArray(data)) return data
|
|
359
|
+
|
|
360
|
+
// Object → single item
|
|
361
|
+
if (data.published === false) return null
|
|
362
|
+
return { slug, ...data }
|
|
363
|
+
}
|
|
364
|
+
|
|
318
365
|
/**
|
|
319
366
|
* Process a single content item from a markdown file
|
|
320
367
|
*
|
|
@@ -384,13 +431,27 @@ async function collectItems(siteDir, config) {
|
|
|
384
431
|
}
|
|
385
432
|
|
|
386
433
|
const files = await readdir(collectionDir)
|
|
387
|
-
const
|
|
434
|
+
const itemFiles = files.filter(f =>
|
|
435
|
+
!f.startsWith('_') &&
|
|
436
|
+
(f.endsWith('.md') || f.endsWith('.yml') || f.endsWith('.yaml') || f.endsWith('.json'))
|
|
437
|
+
)
|
|
388
438
|
|
|
389
|
-
// Process all markdown
|
|
439
|
+
// Process all collection files (markdown → content items, YAML/JSON → data items)
|
|
390
440
|
let items = await Promise.all(
|
|
391
|
-
|
|
441
|
+
itemFiles.map(file => {
|
|
442
|
+
if (file.endsWith('.json')) {
|
|
443
|
+
return processJsonItem(collectionDir, file)
|
|
444
|
+
}
|
|
445
|
+
if (file.endsWith('.yml') || file.endsWith('.yaml')) {
|
|
446
|
+
return processDataItem(collectionDir, file)
|
|
447
|
+
}
|
|
448
|
+
return processContentItem(collectionDir, file, config, siteDir)
|
|
449
|
+
})
|
|
392
450
|
)
|
|
393
451
|
|
|
452
|
+
// Flatten arrays from JSON files that contain multiple items
|
|
453
|
+
items = items.flat()
|
|
454
|
+
|
|
394
455
|
// Filter out nulls (unpublished items)
|
|
395
456
|
items = items.filter(Boolean)
|
|
396
457
|
|
|
@@ -496,11 +557,14 @@ export async function getCollectionLastModified(siteDir, config) {
|
|
|
496
557
|
}
|
|
497
558
|
|
|
498
559
|
const files = await readdir(collectionDir)
|
|
499
|
-
const
|
|
560
|
+
const itemFiles = files.filter(f =>
|
|
561
|
+
!f.startsWith('_') &&
|
|
562
|
+
(f.endsWith('.md') || f.endsWith('.yml') || f.endsWith('.yaml') || f.endsWith('.json'))
|
|
563
|
+
)
|
|
500
564
|
|
|
501
565
|
let lastModified = null
|
|
502
566
|
|
|
503
|
-
for (const file of
|
|
567
|
+
for (const file of itemFiles) {
|
|
504
568
|
const fileStat = await stat(join(collectionDir, file))
|
|
505
569
|
if (!lastModified || fileStat.mtime > lastModified) {
|
|
506
570
|
lastModified = fileStat.mtime
|
package/src/site/data-fetcher.js
CHANGED
|
@@ -245,9 +245,10 @@ export function parseFetchConfig(fetch) {
|
|
|
245
245
|
path,
|
|
246
246
|
url,
|
|
247
247
|
schema,
|
|
248
|
-
prerender = true,
|
|
248
|
+
prerender = url ? false : true,
|
|
249
249
|
merge = false,
|
|
250
250
|
transform,
|
|
251
|
+
detail,
|
|
251
252
|
// Post-processing options (also supported for path/url fetches)
|
|
252
253
|
limit,
|
|
253
254
|
sort,
|
|
@@ -264,6 +265,7 @@ export function parseFetchConfig(fetch) {
|
|
|
264
265
|
prerender,
|
|
265
266
|
merge,
|
|
266
267
|
transform,
|
|
268
|
+
detail,
|
|
267
269
|
// Post-processing options
|
|
268
270
|
limit,
|
|
269
271
|
sort,
|
package/src/site/plugin.js
CHANGED
|
@@ -41,50 +41,51 @@ import { executeFetch, mergeDataIntoContent } from './data-fetcher.js'
|
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Execute all fetches for site content (used in dev mode)
|
|
44
|
-
*
|
|
44
|
+
* Collects fetchedData for DataStore pre-population at runtime
|
|
45
45
|
*
|
|
46
46
|
* @param {Object} siteContent - The collected site content
|
|
47
47
|
* @param {string} siteDir - Path to site directory
|
|
48
48
|
*/
|
|
49
49
|
async function executeDevFetches(siteContent, siteDir) {
|
|
50
50
|
const fetchOptions = { siteRoot: siteDir, publicDir: 'public' }
|
|
51
|
+
const fetchedData = []
|
|
51
52
|
|
|
52
53
|
// Site-level fetch
|
|
53
|
-
let siteCascadedData = {}
|
|
54
54
|
const siteFetch = siteContent.config?.fetch
|
|
55
55
|
if (siteFetch) {
|
|
56
56
|
const result = await executeFetch(siteFetch, fetchOptions)
|
|
57
57
|
if (result.data && !result.error) {
|
|
58
|
-
|
|
58
|
+
fetchedData.push({ config: siteFetch, data: result.data })
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// Process each page
|
|
63
63
|
for (const page of siteContent.pages || []) {
|
|
64
|
-
let pageCascadedData = { ...siteCascadedData }
|
|
65
|
-
|
|
66
64
|
// Page-level fetch
|
|
67
65
|
const pageFetch = page.fetch
|
|
68
66
|
if (pageFetch) {
|
|
69
67
|
const result = await executeFetch(pageFetch, fetchOptions)
|
|
70
68
|
if (result.data && !result.error) {
|
|
71
|
-
|
|
69
|
+
fetchedData.push({ config: pageFetch, data: result.data })
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
// Process
|
|
76
|
-
await processDevSectionFetches(page.sections,
|
|
73
|
+
// Process section-level fetches (own fetch → parsedContent.data)
|
|
74
|
+
await processDevSectionFetches(page.sections, fetchOptions)
|
|
77
75
|
}
|
|
76
|
+
|
|
77
|
+
// Store on siteContent for runtime DataStore pre-population
|
|
78
|
+
siteContent.fetchedData = fetchedData
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
/**
|
|
81
82
|
* Process fetches for sections recursively
|
|
83
|
+
* Section-level fetches merge data into parsedContent.data (not cascaded).
|
|
82
84
|
*
|
|
83
85
|
* @param {Array} sections - Sections to process
|
|
84
|
-
* @param {Object} cascadedData - Data from parent levels
|
|
85
86
|
* @param {Object} fetchOptions - Options for executeFetch
|
|
86
87
|
*/
|
|
87
|
-
async function processDevSectionFetches(sections,
|
|
88
|
+
async function processDevSectionFetches(sections, fetchOptions) {
|
|
88
89
|
if (!sections || !Array.isArray(sections)) return
|
|
89
90
|
|
|
90
91
|
for (const section of sections) {
|
|
@@ -104,13 +105,9 @@ async function processDevSectionFetches(sections, cascadedData, fetchOptions) {
|
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
// Attach cascaded data for components with inheritData
|
|
108
|
-
// Note: cascadedData is from page/site level only, not section's own fetch
|
|
109
|
-
section.cascadedData = cascadedData
|
|
110
|
-
|
|
111
108
|
// Process subsections recursively
|
|
112
109
|
if (section.subsections && section.subsections.length > 0) {
|
|
113
|
-
await processDevSectionFetches(section.subsections,
|
|
110
|
+
await processDevSectionFetches(section.subsections, fetchOptions)
|
|
114
111
|
}
|
|
115
112
|
}
|
|
116
113
|
}
|
|
@@ -376,6 +373,7 @@ export function siteContentPlugin(options = {}) {
|
|
|
376
373
|
let watcher = null
|
|
377
374
|
let server = null
|
|
378
375
|
let localeTranslations = {} // Cache: { locale: translations }
|
|
376
|
+
let collectionTranslations = {} // Cache: { locale: collection translations }
|
|
379
377
|
let localesDir = 'locales' // Default, updated from site config
|
|
380
378
|
let collectionsConfig = null // Cached for watcher setup
|
|
381
379
|
|
|
@@ -402,6 +400,29 @@ export function siteContentPlugin(options = {}) {
|
|
|
402
400
|
}
|
|
403
401
|
}
|
|
404
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Load collection translations for a specific locale
|
|
405
|
+
*/
|
|
406
|
+
async function loadCollectionTranslations(locale) {
|
|
407
|
+
if (collectionTranslations[locale]) {
|
|
408
|
+
return collectionTranslations[locale]
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const localePath = join(resolvedSitePath, localesDir, 'collections', `${locale}.json`)
|
|
412
|
+
if (!existsSync(localePath)) {
|
|
413
|
+
return null
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
const content = await readFile(localePath, 'utf-8')
|
|
418
|
+
const translations = JSON.parse(content)
|
|
419
|
+
collectionTranslations[locale] = translations
|
|
420
|
+
return translations
|
|
421
|
+
} catch {
|
|
422
|
+
return null
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
405
426
|
/**
|
|
406
427
|
* Get available locales from locales directory
|
|
407
428
|
*/
|
|
@@ -504,6 +525,7 @@ export function siteContentPlugin(options = {}) {
|
|
|
504
525
|
|
|
505
526
|
// Clear translation cache on rebuild
|
|
506
527
|
localeTranslations = {}
|
|
528
|
+
collectionTranslations = {}
|
|
507
529
|
} catch (err) {
|
|
508
530
|
console.error('[site-content] Failed to collect content:', err.message)
|
|
509
531
|
siteContent = { config: {}, theme: {}, pages: [] }
|
|
@@ -621,6 +643,7 @@ export function siteContentPlugin(options = {}) {
|
|
|
621
643
|
const localeWatcher = watch(localesPath, { recursive: false }, () => {
|
|
622
644
|
console.log('[site-content] Translation files changed, clearing cache...')
|
|
623
645
|
localeTranslations = {}
|
|
646
|
+
collectionTranslations = {}
|
|
624
647
|
server.ws.send({ type: 'full-reload' })
|
|
625
648
|
})
|
|
626
649
|
additionalWatchers.push(localeWatcher)
|
|
@@ -643,6 +666,22 @@ export function siteContentPlugin(options = {}) {
|
|
|
643
666
|
// freeform dir may not exist, that's ok
|
|
644
667
|
}
|
|
645
668
|
}
|
|
669
|
+
|
|
670
|
+
// Watch collection translations directory
|
|
671
|
+
const collectionsLocalesPath = resolve(localesPath, 'collections')
|
|
672
|
+
if (existsSync(collectionsLocalesPath)) {
|
|
673
|
+
try {
|
|
674
|
+
const collWatcher = watch(collectionsLocalesPath, { recursive: false }, () => {
|
|
675
|
+
console.log('[site-content] Collection translations changed, clearing cache...')
|
|
676
|
+
collectionTranslations = {}
|
|
677
|
+
server.ws.send({ type: 'full-reload' })
|
|
678
|
+
})
|
|
679
|
+
additionalWatchers.push(collWatcher)
|
|
680
|
+
console.log(`[site-content] Watching ${collectionsLocalesPath} for collection translation changes`)
|
|
681
|
+
} catch (err) {
|
|
682
|
+
// collections locales dir may not exist, that's ok
|
|
683
|
+
}
|
|
684
|
+
}
|
|
646
685
|
}
|
|
647
686
|
|
|
648
687
|
// Add additional watchers to cleanup
|
|
@@ -721,6 +760,45 @@ export function siteContentPlugin(options = {}) {
|
|
|
721
760
|
}
|
|
722
761
|
}
|
|
723
762
|
|
|
763
|
+
// Handle localized collection data (e.g., /fr/data/articles.json)
|
|
764
|
+
const localeDataMatch = req.url.match(/^\/([a-z]{2})\/data\/(.+\.json)$/)
|
|
765
|
+
if (localeDataMatch) {
|
|
766
|
+
const locale = localeDataMatch[1]
|
|
767
|
+
const filename = localeDataMatch[2]
|
|
768
|
+
const collectionName = filename.replace('.json', '')
|
|
769
|
+
const sourcePath = join(resolvedSitePath, 'public', 'data', filename)
|
|
770
|
+
|
|
771
|
+
if (existsSync(sourcePath)) {
|
|
772
|
+
try {
|
|
773
|
+
const raw = await readFile(sourcePath, 'utf-8')
|
|
774
|
+
const items = JSON.parse(raw)
|
|
775
|
+
|
|
776
|
+
// Load collection translations for this locale
|
|
777
|
+
const translations = await loadCollectionTranslations(locale) || {}
|
|
778
|
+
|
|
779
|
+
// Check for free-form translations
|
|
780
|
+
const freeformDir = join(resolvedSitePath, localesDir, 'freeform', locale)
|
|
781
|
+
const hasFreeform = existsSync(freeformDir)
|
|
782
|
+
|
|
783
|
+
// Translate using the collections module
|
|
784
|
+
const { translateCollectionData } = await import('../i18n/collections.js')
|
|
785
|
+
const translated = await translateCollectionData(items, collectionName, resolvedSitePath, {
|
|
786
|
+
locale,
|
|
787
|
+
localesDir: join(resolvedSitePath, localesDir),
|
|
788
|
+
translations,
|
|
789
|
+
freeformEnabled: hasFreeform
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
res.setHeader('Content-Type', 'application/json')
|
|
793
|
+
res.end(JSON.stringify(translated, null, 2))
|
|
794
|
+
return
|
|
795
|
+
} catch (err) {
|
|
796
|
+
console.warn(`[site-content] Failed to serve localized collection ${filename}: ${err.message}`)
|
|
797
|
+
// Fall through to Vite's static server
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
724
802
|
next()
|
|
725
803
|
})
|
|
726
804
|
},
|
|
@@ -134,7 +134,7 @@ function generateContextCSS(context, tokens = {}) {
|
|
|
134
134
|
|
|
135
135
|
const vars = generateVarDeclarations(mergedTokens)
|
|
136
136
|
|
|
137
|
-
return `.context-${context} {\n${vars}\n}`
|
|
137
|
+
return `.context-${context} {\n${vars}\n background-color: var(--bg);\n}`
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/**
|
|
@@ -305,6 +305,26 @@ export function generateThemeCSS(config = {}) {
|
|
|
305
305
|
sections.push(generateDarkSchemeCSS(appearance))
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
// 7. Site background (if specified in theme.yml)
|
|
309
|
+
if (config.background) {
|
|
310
|
+
sections.push(`/* Site Background */\nbody {\n background: ${config.background};\n}`)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 8. Inline text styles (if specified in theme.yml)
|
|
314
|
+
if (config.inline && typeof config.inline === 'object') {
|
|
315
|
+
const rules = Object.entries(config.inline)
|
|
316
|
+
.filter(([, styles]) => styles && typeof styles === 'object')
|
|
317
|
+
.map(([name, styles]) => {
|
|
318
|
+
const declarations = Object.entries(styles)
|
|
319
|
+
.map(([prop, value]) => ` ${prop}: ${value};`)
|
|
320
|
+
.join('\n')
|
|
321
|
+
return `span[${name}] {\n${declarations}\n}`
|
|
322
|
+
})
|
|
323
|
+
if (rules.length > 0) {
|
|
324
|
+
sections.push('/* Inline Text Styles */\n' + rules.join('\n\n'))
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
308
328
|
return sections.join('\n\n')
|
|
309
329
|
}
|
|
310
330
|
|
package/src/theme/processor.js
CHANGED
|
@@ -422,6 +422,12 @@ export function processTheme(rawConfig = {}, options = {}) {
|
|
|
422
422
|
...(rawConfig.code || {}),
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
+
// Site background (pass through as CSS value)
|
|
426
|
+
const background = rawConfig.background || null
|
|
427
|
+
|
|
428
|
+
// Inline text styles (semantic names → CSS declarations)
|
|
429
|
+
const inline = rawConfig.inline || null
|
|
430
|
+
|
|
425
431
|
const config = {
|
|
426
432
|
colors, // Raw colors for CSS generator
|
|
427
433
|
palettes, // Generated palettes for Theme class
|
|
@@ -430,6 +436,8 @@ export function processTheme(rawConfig = {}, options = {}) {
|
|
|
430
436
|
appearance,
|
|
431
437
|
foundationVars: mergedFoundationVars,
|
|
432
438
|
code, // Code block theme for runtime injection
|
|
439
|
+
background, // Site-level background CSS value
|
|
440
|
+
inline, // Inline text style definitions
|
|
433
441
|
}
|
|
434
442
|
|
|
435
443
|
return { config, errors, warnings }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infer display title from PascalCase component name.
|
|
3
|
+
*
|
|
4
|
+
* TeamRoster → "Team Roster"
|
|
5
|
+
* CTA → "CTA"
|
|
6
|
+
* FAQSection → "FAQ Section"
|
|
7
|
+
* Hero → "Hero"
|
|
8
|
+
*
|
|
9
|
+
* @param {string} name - PascalCase component name
|
|
10
|
+
* @returns {string} Human-readable title
|
|
11
|
+
*/
|
|
12
|
+
export function inferTitle(name) {
|
|
13
|
+
return name
|
|
14
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
15
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
16
|
+
}
|
|
@@ -120,21 +120,33 @@ export function foundationDevPlugin(options = {}) {
|
|
|
120
120
|
},
|
|
121
121
|
|
|
122
122
|
async handleHotUpdate({ file, server }) {
|
|
123
|
+
const entryPath = join(resolvedSrcDir, entryFileName)
|
|
124
|
+
|
|
123
125
|
// Regenerate entry when meta.js files change
|
|
124
|
-
// Check if file is a meta.js in the src directory
|
|
125
126
|
if (file.startsWith(resolvedSrcDir) && file.endsWith('/meta.js')) {
|
|
126
127
|
console.log('Component meta.js changed, regenerating entry...')
|
|
127
|
-
const entryPath = join(resolvedSrcDir, entryFileName)
|
|
128
128
|
await generateEntryPoint(resolvedSrcDir, entryPath, { componentPaths })
|
|
129
|
-
|
|
130
|
-
// Trigger full reload since entry changed
|
|
131
129
|
server.ws.send({ type: 'full-reload' })
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Regenerate when component files are added/removed in sections/ root
|
|
134
|
+
// (bare file discovery means any .jsx/.tsx/.js/.ts at sections root is a section type)
|
|
135
|
+
const sectionsDir = join(resolvedSrcDir, 'sections')
|
|
136
|
+
if (file.startsWith(sectionsDir)) {
|
|
137
|
+
const relative = file.slice(sectionsDir.length + 1)
|
|
138
|
+
// Direct child of sections/ (no further slashes) — could be a new/removed bare file
|
|
139
|
+
if (!relative.includes('/') && /\.(jsx|tsx|js|ts)$/.test(relative)) {
|
|
140
|
+
console.log('Section file changed, regenerating entry...')
|
|
141
|
+
await generateEntryPoint(resolvedSrcDir, entryPath, { componentPaths })
|
|
142
|
+
server.ws.send({ type: 'full-reload' })
|
|
143
|
+
return
|
|
144
|
+
}
|
|
132
145
|
}
|
|
133
146
|
|
|
134
147
|
// Also regenerate if exports.js changes
|
|
135
148
|
if (file.endsWith('/exports.js') || file.endsWith('/exports.jsx')) {
|
|
136
149
|
console.log('Foundation exports changed, regenerating entry...')
|
|
137
|
-
const entryPath = join(resolvedSrcDir, entryFileName)
|
|
138
150
|
await generateEntryPoint(resolvedSrcDir, entryPath, { componentPaths })
|
|
139
151
|
server.ws.send({ type: 'full-reload' })
|
|
140
152
|
}
|