@uniweb/build 0.4.10 → 0.5.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 +1 -1
- package/src/foundation/config.js +3 -3
- package/src/generate-entry.js +29 -10
- package/src/schema.js +196 -19
- package/src/utils/infer-title.js +16 -0
- package/src/vite-foundation-plugin.js +17 -5
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ npm install @uniweb/build --save-dev
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
17
|
**For Foundations:**
|
|
18
|
-
- **Component Discovery** -
|
|
18
|
+
- **Component Discovery** - Discovers section types from `src/sections/` (implicit at root) and `src/components/` (requires `meta.js`)
|
|
19
19
|
- **Entry Generation** - Generates the foundation entry point with all exports
|
|
20
20
|
- **Schema Building** - Creates `schema.json` with full component metadata for editors
|
|
21
21
|
- **Image Processing** - Converts preview images to WebP format
|
|
@@ -263,7 +263,7 @@ src/
|
|
|
263
263
|
### Component Meta File
|
|
264
264
|
|
|
265
265
|
```js
|
|
266
|
-
// src/
|
|
266
|
+
// src/sections/Hero/meta.js
|
|
267
267
|
export default {
|
|
268
268
|
title: 'Hero Banner',
|
|
269
269
|
description: 'A prominent header section',
|
|
@@ -367,7 +367,7 @@ Identity fields (`name`, `version`, `description`) come from the foundation's `p
|
|
|
367
367
|
|
|
368
368
|
| Function | Description |
|
|
369
369
|
|----------|-------------|
|
|
370
|
-
| `discoverComponents(srcDir)` | Discover all
|
|
370
|
+
| `discoverComponents(srcDir)` | Discover all section types (folders with meta.js) |
|
|
371
371
|
| `loadComponentMeta(componentDir)` | Load meta file for a component |
|
|
372
372
|
| `loadPackageJson(srcDir)` | Load identity from package.json |
|
|
373
373
|
| `loadFoundationConfig(srcDir)` | Load foundation.js configuration |
|
package/package.json
CHANGED
package/src/foundation/config.js
CHANGED
|
@@ -48,9 +48,9 @@ const DEFAULT_EXTERNALS = [
|
|
|
48
48
|
* @param {Object} [options={}] - Configuration options
|
|
49
49
|
* @param {string} [options.entry] - Entry point path (default: 'src/_entry.generated.js')
|
|
50
50
|
* @param {string} [options.fileName] - Output file name (default: 'foundation')
|
|
51
|
-
* @param {string[]} [options.components] - Paths to
|
|
52
|
-
* Default: ['components']
|
|
53
|
-
* Example: ['
|
|
51
|
+
* @param {string[]} [options.components] - Paths to scan for content interfaces (relative to src/).
|
|
52
|
+
* Default: ['sections', 'components']
|
|
53
|
+
* Example: ['sections', 'sections/marketing']
|
|
54
54
|
* @param {string[]} [options.externals] - Additional packages to externalize
|
|
55
55
|
* @param {boolean} [options.includeDefaultExternals] - Include default externals (default: true)
|
|
56
56
|
* @param {Array} [options.plugins] - Additional Vite plugins
|
package/src/generate-entry.js
CHANGED
|
@@ -112,10 +112,10 @@ function generateEntrySource(components, options = {}) {
|
|
|
112
112
|
lines.push(`import capabilities from '${foundationExports.path}'`)
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// Component imports
|
|
115
|
+
// Component imports
|
|
116
116
|
for (const name of componentNames) {
|
|
117
|
-
const { path,
|
|
118
|
-
lines.push(`import ${name} from './${path}
|
|
117
|
+
const { path, entryFile = `index.js` } = components[name]
|
|
118
|
+
lines.push(`import ${name} from './${path}/${entryFile}'`)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
lines.push('')
|
|
@@ -153,19 +153,33 @@ function generateEntrySource(components, options = {}) {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
|
-
* Detect the
|
|
156
|
+
* Detect the entry file for a component
|
|
157
|
+
*
|
|
158
|
+
* Supports two conventions:
|
|
159
|
+
* - index.jsx (default)
|
|
160
|
+
* - ComponentName.jsx (named file matching the directory name)
|
|
161
|
+
*
|
|
162
|
+
* Named files are checked first so that Hero/Hero.jsx takes precedence
|
|
163
|
+
* over Hero/index.jsx when both exist (the named file is more intentional).
|
|
157
164
|
*
|
|
158
165
|
* @param {string} srcDir - Source directory
|
|
159
166
|
* @param {string} componentPath - Relative path to component (e.g., 'components/Hero')
|
|
167
|
+
* @param {string} componentName - Component name (e.g., 'Hero')
|
|
168
|
+
* @returns {{ file: string, ext: string }} Entry file name and extension
|
|
160
169
|
*/
|
|
161
|
-
function
|
|
170
|
+
function detectComponentEntry(srcDir, componentPath, componentName) {
|
|
162
171
|
const basePath = join(srcDir, componentPath)
|
|
163
172
|
for (const ext of ['jsx', 'tsx', 'js', 'ts']) {
|
|
173
|
+
// Check named file first: Hero/Hero.jsx
|
|
174
|
+
if (existsSync(join(basePath, `${componentName}.${ext}`))) {
|
|
175
|
+
return { file: `${componentName}.${ext}`, ext }
|
|
176
|
+
}
|
|
177
|
+
// Then index file: Hero/index.jsx
|
|
164
178
|
if (existsSync(join(basePath, `index.${ext}`))) {
|
|
165
|
-
return ext
|
|
179
|
+
return { file: `index.${ext}`, ext }
|
|
166
180
|
}
|
|
167
181
|
}
|
|
168
|
-
return 'js' // default
|
|
182
|
+
return { file: 'index.js', ext: 'js' } // default
|
|
169
183
|
}
|
|
170
184
|
|
|
171
185
|
/**
|
|
@@ -184,13 +198,18 @@ export async function generateEntryPoint(srcDir, outputPath = null, options = {}
|
|
|
184
198
|
const componentNames = Object.keys(components).sort()
|
|
185
199
|
|
|
186
200
|
if (componentNames.length === 0) {
|
|
187
|
-
console.warn('Warning: No
|
|
201
|
+
console.warn('Warning: No section types found')
|
|
188
202
|
}
|
|
189
203
|
|
|
190
|
-
// Detect
|
|
204
|
+
// Detect entry files for each component
|
|
205
|
+
// Bare files discovered in sections/ already have entryFile set — skip detection for those
|
|
191
206
|
for (const name of componentNames) {
|
|
192
207
|
const component = components[name]
|
|
193
|
-
|
|
208
|
+
if (!component.entryFile) {
|
|
209
|
+
const entry = detectComponentEntry(srcDir, component.path, component.name)
|
|
210
|
+
component.ext = entry.ext
|
|
211
|
+
component.entryFile = entry.file
|
|
212
|
+
}
|
|
194
213
|
}
|
|
195
214
|
|
|
196
215
|
// Check for CSS file
|
package/src/schema.js
CHANGED
|
@@ -3,12 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Discovers component meta files and loads them for schema.json generation.
|
|
5
5
|
* Schema data is for editor-time only, not runtime.
|
|
6
|
+
*
|
|
7
|
+
* Discovery rules:
|
|
8
|
+
* - sections/ root: bare files and folders are addressable by default (implicit empty meta)
|
|
9
|
+
* - sections/ nested: meta.js required for addressability
|
|
10
|
+
* - components/ (and other paths): meta.js required (backward compatibility)
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
import { readdir, readFile } from 'node:fs/promises'
|
|
9
14
|
import { existsSync } from 'node:fs'
|
|
10
|
-
import { join, dirname } from 'node:path'
|
|
15
|
+
import { join, dirname, extname, basename } from 'node:path'
|
|
11
16
|
import { pathToFileURL } from 'node:url'
|
|
17
|
+
import { inferTitle } from './utils/infer-title.js'
|
|
12
18
|
|
|
13
19
|
// Component meta file name
|
|
14
20
|
const META_FILE_NAME = 'meta.js'
|
|
@@ -16,8 +22,15 @@ const META_FILE_NAME = 'meta.js'
|
|
|
16
22
|
// Foundation config file name
|
|
17
23
|
const FOUNDATION_FILE_NAME = 'foundation.js'
|
|
18
24
|
|
|
19
|
-
// Default
|
|
20
|
-
|
|
25
|
+
// Default paths to scan for content interfaces (relative to srcDir)
|
|
26
|
+
// sections/ is the primary convention; components/ supported for backward compatibility
|
|
27
|
+
const DEFAULT_COMPONENT_PATHS = ['sections', 'components']
|
|
28
|
+
|
|
29
|
+
// Extensions recognized as component entry files
|
|
30
|
+
const COMPONENT_EXTENSIONS = new Set(['.jsx', '.tsx', '.js', '.ts'])
|
|
31
|
+
|
|
32
|
+
// The primary sections path where relaxed discovery applies
|
|
33
|
+
const SECTIONS_PATH = 'sections'
|
|
21
34
|
|
|
22
35
|
/**
|
|
23
36
|
* Load a meta.js file via dynamic import
|
|
@@ -113,12 +126,173 @@ export async function loadFoundationMeta(srcDir) {
|
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
/**
|
|
116
|
-
*
|
|
129
|
+
* Check if a filename looks like a PascalCase component (starts with uppercase)
|
|
130
|
+
*/
|
|
131
|
+
function isComponentFileName(name) {
|
|
132
|
+
return /^[A-Z]/.test(name)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if a directory has a valid entry file (Name.ext or index.ext)
|
|
137
|
+
*/
|
|
138
|
+
function hasEntryFile(dirPath, dirName) {
|
|
139
|
+
for (const ext of ['.jsx', '.tsx', '.js', '.ts']) {
|
|
140
|
+
if (existsSync(join(dirPath, `${dirName}${ext}`))) return true
|
|
141
|
+
if (existsSync(join(dirPath, `index${ext}`))) return true
|
|
142
|
+
}
|
|
143
|
+
return false
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Create an implicit empty meta for a section type discovered without meta.js
|
|
148
|
+
*/
|
|
149
|
+
function createImplicitMeta(name) {
|
|
150
|
+
return { title: inferTitle(name) }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Build a component entry with title inference applied
|
|
155
|
+
*/
|
|
156
|
+
function buildComponentEntry(name, relativePath, meta) {
|
|
157
|
+
const entry = {
|
|
158
|
+
name,
|
|
159
|
+
path: relativePath,
|
|
160
|
+
...meta,
|
|
161
|
+
}
|
|
162
|
+
// Apply title inference if meta has no explicit title
|
|
163
|
+
if (!entry.title) {
|
|
164
|
+
entry.title = inferTitle(name)
|
|
165
|
+
}
|
|
166
|
+
return entry
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Discover section types in sections/ with relaxed rules
|
|
171
|
+
*
|
|
172
|
+
* Root level: bare files and folders are addressable by default.
|
|
173
|
+
* Nested levels: meta.js required for addressability.
|
|
174
|
+
*
|
|
117
175
|
* @param {string} srcDir - Source directory (e.g., 'src')
|
|
118
|
-
* @param {string}
|
|
176
|
+
* @param {string} sectionsRelPath - Relative path to sections dir (e.g., 'sections')
|
|
177
|
+
*/
|
|
178
|
+
async function discoverSectionsInPath(srcDir, sectionsRelPath) {
|
|
179
|
+
const fullPath = join(srcDir, sectionsRelPath)
|
|
180
|
+
|
|
181
|
+
if (!existsSync(fullPath)) {
|
|
182
|
+
return {}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const entries = await readdir(fullPath, { withFileTypes: true })
|
|
186
|
+
const components = {}
|
|
187
|
+
|
|
188
|
+
// Collect names from both files and directories to detect collisions
|
|
189
|
+
const fileNames = new Set()
|
|
190
|
+
const dirNames = new Set()
|
|
191
|
+
|
|
192
|
+
for (const entry of entries) {
|
|
193
|
+
const ext = extname(entry.name)
|
|
194
|
+
if (entry.isFile() && COMPONENT_EXTENSIONS.has(ext)) {
|
|
195
|
+
const name = basename(entry.name, ext)
|
|
196
|
+
if (isComponentFileName(name)) {
|
|
197
|
+
fileNames.add(name)
|
|
198
|
+
}
|
|
199
|
+
} else if (entry.isDirectory()) {
|
|
200
|
+
dirNames.add(entry.name)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check for name collisions (e.g., Hero.jsx AND Hero/)
|
|
205
|
+
for (const name of fileNames) {
|
|
206
|
+
if (dirNames.has(name)) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`Name collision in ${sectionsRelPath}/: both "${name}.jsx" (or similar) and "${name}/" exist. ` +
|
|
209
|
+
`Use one or the other, not both.`
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Discover bare files at root
|
|
215
|
+
for (const entry of entries) {
|
|
216
|
+
if (!entry.isFile()) continue
|
|
217
|
+
const ext = extname(entry.name)
|
|
218
|
+
if (!COMPONENT_EXTENSIONS.has(ext)) continue
|
|
219
|
+
const name = basename(entry.name, ext)
|
|
220
|
+
if (!isComponentFileName(name)) continue
|
|
221
|
+
|
|
222
|
+
const meta = createImplicitMeta(name)
|
|
223
|
+
components[name] = {
|
|
224
|
+
...buildComponentEntry(name, sectionsRelPath, meta),
|
|
225
|
+
// Bare file: the entry file IS the file itself (not inside a subdirectory)
|
|
226
|
+
entryFile: entry.name,
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Discover directories at root
|
|
231
|
+
for (const entry of entries) {
|
|
232
|
+
if (!entry.isDirectory()) continue
|
|
233
|
+
if (!isComponentFileName(entry.name)) continue
|
|
234
|
+
|
|
235
|
+
const dirPath = join(fullPath, entry.name)
|
|
236
|
+
const relativePath = join(sectionsRelPath, entry.name)
|
|
237
|
+
const result = await loadComponentMeta(dirPath)
|
|
238
|
+
|
|
239
|
+
if (result && result.meta) {
|
|
240
|
+
// Has meta.js — use explicit meta
|
|
241
|
+
if (result.meta.exposed === false) continue
|
|
242
|
+
components[entry.name] = buildComponentEntry(entry.name, relativePath, result.meta)
|
|
243
|
+
} else if (hasEntryFile(dirPath, entry.name)) {
|
|
244
|
+
// No meta.js but has entry file — implicit section type at root
|
|
245
|
+
components[entry.name] = buildComponentEntry(entry.name, relativePath, createImplicitMeta(entry.name))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Recurse into subdirectories for nested section types (meta.js required)
|
|
249
|
+
await discoverNestedSections(srcDir, dirPath, relativePath, components)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return components
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Recursively discover nested section types that have meta.js
|
|
257
|
+
*
|
|
258
|
+
* @param {string} srcDir - Source directory
|
|
259
|
+
* @param {string} parentFullPath - Absolute path to parent directory
|
|
260
|
+
* @param {string} parentRelPath - Relative path from srcDir to parent
|
|
261
|
+
* @param {Object} components - Accumulator for discovered components
|
|
262
|
+
*/
|
|
263
|
+
async function discoverNestedSections(srcDir, parentFullPath, parentRelPath, components) {
|
|
264
|
+
let entries
|
|
265
|
+
try {
|
|
266
|
+
entries = await readdir(parentFullPath, { withFileTypes: true })
|
|
267
|
+
} catch {
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
for (const entry of entries) {
|
|
272
|
+
if (!entry.isDirectory()) continue
|
|
273
|
+
|
|
274
|
+
const dirPath = join(parentFullPath, entry.name)
|
|
275
|
+
const relativePath = join(parentRelPath, entry.name)
|
|
276
|
+
const result = await loadComponentMeta(dirPath)
|
|
277
|
+
|
|
278
|
+
if (result && result.meta) {
|
|
279
|
+
if (result.meta.exposed === false) continue
|
|
280
|
+
components[entry.name] = buildComponentEntry(entry.name, relativePath, result.meta)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Continue recursing regardless — deeper levels may have meta.js
|
|
284
|
+
await discoverNestedSections(srcDir, dirPath, relativePath, components)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Discover components in a non-sections path (meta.js required)
|
|
290
|
+
*
|
|
291
|
+
* @param {string} srcDir - Source directory (e.g., 'src')
|
|
292
|
+
* @param {string} relativePath - Path relative to srcDir (e.g., 'components')
|
|
119
293
|
* @returns {Object} Map of componentName -> { name, path, ...meta }
|
|
120
294
|
*/
|
|
121
|
-
async function
|
|
295
|
+
async function discoverExplicitComponentsInPath(srcDir, relativePath) {
|
|
122
296
|
const fullPath = join(srcDir, relativePath)
|
|
123
297
|
|
|
124
298
|
if (!existsSync(fullPath)) {
|
|
@@ -135,16 +309,12 @@ async function discoverComponentsInPath(srcDir, relativePath) {
|
|
|
135
309
|
const result = await loadComponentMeta(componentDir)
|
|
136
310
|
|
|
137
311
|
if (result && result.meta) {
|
|
138
|
-
// Check if explicitly
|
|
312
|
+
// Check if explicitly hidden from discovery
|
|
139
313
|
if (result.meta.exposed === false) {
|
|
140
314
|
continue
|
|
141
315
|
}
|
|
142
316
|
|
|
143
|
-
components[entry.name] =
|
|
144
|
-
name: entry.name,
|
|
145
|
-
path: join(relativePath, entry.name), // e.g., 'components/Hero' or 'components/sections/Hero'
|
|
146
|
-
...result.meta,
|
|
147
|
-
}
|
|
317
|
+
components[entry.name] = buildComponentEntry(entry.name, join(relativePath, entry.name), result.meta)
|
|
148
318
|
}
|
|
149
319
|
}
|
|
150
320
|
|
|
@@ -152,18 +322,25 @@ async function discoverComponentsInPath(srcDir, relativePath) {
|
|
|
152
322
|
}
|
|
153
323
|
|
|
154
324
|
/**
|
|
155
|
-
* Discover all
|
|
325
|
+
* Discover all section types in a foundation
|
|
326
|
+
*
|
|
327
|
+
* For the 'sections' path: relaxed discovery (bare files and folders at root,
|
|
328
|
+
* meta.js required for nested levels).
|
|
329
|
+
* For other paths: strict discovery (meta.js required).
|
|
156
330
|
*
|
|
157
331
|
* @param {string} srcDir - Source directory (e.g., 'src')
|
|
158
|
-
* @param {string[]} [componentPaths] - Paths to
|
|
159
|
-
* Default: ['components']
|
|
160
|
-
* @returns {Object} Map of
|
|
332
|
+
* @param {string[]} [componentPaths] - Paths to scan for section types (relative to srcDir).
|
|
333
|
+
* Default: ['sections', 'components']
|
|
334
|
+
* @returns {Object} Map of sectionTypeName -> { name, path, ...meta }
|
|
161
335
|
*/
|
|
162
336
|
export async function discoverComponents(srcDir, componentPaths = DEFAULT_COMPONENT_PATHS) {
|
|
163
337
|
const components = {}
|
|
164
338
|
|
|
165
339
|
for (const relativePath of componentPaths) {
|
|
166
|
-
|
|
340
|
+
// Use relaxed discovery for the primary sections path
|
|
341
|
+
const found = relativePath === SECTIONS_PATH
|
|
342
|
+
? await discoverSectionsInPath(srcDir, relativePath)
|
|
343
|
+
: await discoverExplicitComponentsInPath(srcDir, relativePath)
|
|
167
344
|
|
|
168
345
|
for (const [name, meta] of Object.entries(found)) {
|
|
169
346
|
if (components[name]) {
|
|
@@ -210,10 +387,10 @@ export async function buildSchema(srcDir, componentPaths) {
|
|
|
210
387
|
}
|
|
211
388
|
|
|
212
389
|
/**
|
|
213
|
-
* Get list of
|
|
390
|
+
* Get list of section type names
|
|
214
391
|
*
|
|
215
392
|
* @param {string} srcDir - Source directory
|
|
216
|
-
* @param {string[]} [componentPaths] - Paths to
|
|
393
|
+
* @param {string[]} [componentPaths] - Paths to scan for section types
|
|
217
394
|
*/
|
|
218
395
|
export async function getExposedComponents(srcDir, componentPaths) {
|
|
219
396
|
const components = await discoverComponents(srcDir, componentPaths)
|
|
@@ -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
|
}
|