@uniweb/build 0.1.17 → 0.1.20
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 +47 -1
- package/package.json +2 -2
- package/src/generate-entry.js +103 -127
- package/src/index.js +0 -2
- package/src/prerender.js +5 -6
- package/src/schema.js +2 -42
- package/src/site/content-collector.js +20 -9
- package/src/vite-foundation-plugin.js +3 -3
package/README.md
CHANGED
|
@@ -343,7 +343,6 @@ dist/
|
|
|
343
343
|
| `loadComponentMeta(componentDir)` | Load meta file for a component |
|
|
344
344
|
| `loadFoundationMeta(srcDir)` | Load foundation-level meta |
|
|
345
345
|
| `buildSchema(srcDir)` | Build complete schema object |
|
|
346
|
-
| `buildRuntimeConfig(srcDir)` | Build minimal runtime config |
|
|
347
346
|
|
|
348
347
|
### Entry Generation
|
|
349
348
|
|
|
@@ -351,6 +350,53 @@ dist/
|
|
|
351
350
|
|----------|-------------|
|
|
352
351
|
| `generateEntryPoint(srcDir, outputPath)` | Generate foundation entry file |
|
|
353
352
|
|
|
353
|
+
#### Generated Entry Exports
|
|
354
|
+
|
|
355
|
+
The generated `_entry.generated.js` file exports:
|
|
356
|
+
|
|
357
|
+
| Export | Description |
|
|
358
|
+
|--------|-------------|
|
|
359
|
+
| `components` | Object map of component name → React component |
|
|
360
|
+
| Named exports | Each component exported by name (e.g., `Hero`, `Features`) |
|
|
361
|
+
| `capabilities` | Custom Layout and props from `src/exports.js` (or `null`) |
|
|
362
|
+
| `meta` | Runtime metadata extracted from component `meta.js` files |
|
|
363
|
+
|
|
364
|
+
#### Runtime Metadata (`meta` export)
|
|
365
|
+
|
|
366
|
+
Some properties in `meta.js` are needed at runtime, not just editor-time. These are extracted into the `meta` export to keep them available without loading the full `schema.json`.
|
|
367
|
+
|
|
368
|
+
Currently extracted properties:
|
|
369
|
+
- `input` - Form input schemas (for components that accept user input)
|
|
370
|
+
|
|
371
|
+
Example `meta.js` with form schema:
|
|
372
|
+
```javascript
|
|
373
|
+
export default {
|
|
374
|
+
title: 'Contact Form',
|
|
375
|
+
// ... editor-only properties ...
|
|
376
|
+
|
|
377
|
+
// This gets extracted to the runtime `meta` export
|
|
378
|
+
input: {
|
|
379
|
+
name: { type: 'text', label: 'Name', required: true },
|
|
380
|
+
email: { type: 'email', label: 'Email', required: true },
|
|
381
|
+
message: { type: 'textarea', label: 'Message' }
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Generated entry will include:
|
|
387
|
+
```javascript
|
|
388
|
+
export const meta = {
|
|
389
|
+
"ContactForm": {
|
|
390
|
+
"input": {
|
|
391
|
+
"name": { "type": "text", "label": "Name", "required": true },
|
|
392
|
+
// ...
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
To add more runtime properties, update `RUNTIME_META_KEYS` in `src/generate-entry.js`.
|
|
399
|
+
|
|
354
400
|
### Image Processing
|
|
355
401
|
|
|
356
402
|
| Function | Description |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@tailwindcss/vite": "^4.0.0",
|
|
60
60
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
61
61
|
"vite-plugin-svgr": "^4.0.0",
|
|
62
|
-
"@uniweb/core": "0.1.
|
|
62
|
+
"@uniweb/core": "0.1.8"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
65
|
"vite": {
|
package/src/generate-entry.js
CHANGED
|
@@ -2,28 +2,43 @@
|
|
|
2
2
|
* Foundation Entry Point Generator
|
|
3
3
|
*
|
|
4
4
|
* Auto-generates the foundation entry point based on discovered components.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Exports:
|
|
7
|
+
* - `components` - Object map of component name -> React component
|
|
8
|
+
* - `capabilities` - Custom Layout and props from src/exports.js (if present)
|
|
9
|
+
* - `meta` - Runtime metadata extracted from component meta.js files
|
|
10
|
+
*
|
|
11
|
+
* The `meta` export contains properties from meta.js that are needed at runtime,
|
|
12
|
+
* not just editor-time. Currently this includes:
|
|
13
|
+
* - `input` - Form input schemas (for components that accept user input)
|
|
14
|
+
*
|
|
15
|
+
* Full component metadata lives in schema.json (for the visual editor).
|
|
16
|
+
* Only runtime-essential properties are extracted here to keep bundles small.
|
|
6
17
|
*/
|
|
7
18
|
|
|
8
19
|
import { writeFile, mkdir } from 'node:fs/promises'
|
|
9
20
|
import { existsSync } from 'node:fs'
|
|
10
21
|
import { join, dirname } from 'node:path'
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
import { discoverComponents } from './schema.js'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Keys from meta.js that should be included in the runtime bundle.
|
|
26
|
+
* These are properties needed at runtime, not just editor-time.
|
|
27
|
+
*
|
|
28
|
+
* - input: Form schemas for components that accept user input
|
|
29
|
+
*/
|
|
30
|
+
const RUNTIME_META_KEYS = ['input']
|
|
16
31
|
|
|
17
32
|
/**
|
|
18
|
-
* Detect
|
|
19
|
-
* Looks for: src/
|
|
33
|
+
* Detect foundation exports file (for custom Layout, props, etc.)
|
|
34
|
+
* Looks for: src/exports.js, src/exports.jsx, src/exports/index.js, src/exports/index.jsx
|
|
20
35
|
*/
|
|
21
|
-
function
|
|
36
|
+
function detectFoundationExports(srcDir) {
|
|
22
37
|
const candidates = [
|
|
23
|
-
{ path: '
|
|
24
|
-
{ path: '
|
|
25
|
-
{ path: '
|
|
26
|
-
{ path: '
|
|
38
|
+
{ path: 'exports.js', ext: 'js' },
|
|
39
|
+
{ path: 'exports.jsx', ext: 'jsx' },
|
|
40
|
+
{ path: 'exports/index.js', ext: 'js' },
|
|
41
|
+
{ path: 'exports/index.jsx', ext: 'jsx' },
|
|
27
42
|
]
|
|
28
43
|
|
|
29
44
|
for (const { path, ext } of candidates) {
|
|
@@ -48,117 +63,90 @@ function detectCssFile(srcDir) {
|
|
|
48
63
|
return null
|
|
49
64
|
}
|
|
50
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Extract runtime-needed properties from component meta
|
|
68
|
+
*/
|
|
69
|
+
function extractRuntimeMeta(componentsMeta) {
|
|
70
|
+
const meta = {}
|
|
71
|
+
|
|
72
|
+
for (const [name, componentMeta] of Object.entries(componentsMeta)) {
|
|
73
|
+
const extracted = {}
|
|
74
|
+
for (const key of RUNTIME_META_KEYS) {
|
|
75
|
+
if (componentMeta[key] !== undefined) {
|
|
76
|
+
extracted[key] = componentMeta[key]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (Object.keys(extracted).length > 0) {
|
|
80
|
+
meta[name] = extracted
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return meta
|
|
85
|
+
}
|
|
86
|
+
|
|
51
87
|
/**
|
|
52
88
|
* Generate the entry point source code
|
|
53
89
|
*/
|
|
54
|
-
function generateEntrySource(componentNames,
|
|
55
|
-
const { cssPath = null, componentExtensions = {},
|
|
90
|
+
function generateEntrySource(componentNames, options = {}) {
|
|
91
|
+
const { cssPath = null, componentExtensions = {}, foundationExports = null, runtimeMeta = {} } = options
|
|
56
92
|
|
|
57
|
-
const
|
|
93
|
+
const lines = [
|
|
94
|
+
'// Auto-generated foundation entry point',
|
|
95
|
+
'// DO NOT EDIT - This file is regenerated during build',
|
|
96
|
+
''
|
|
97
|
+
]
|
|
58
98
|
|
|
59
99
|
// CSS import
|
|
60
100
|
if (cssPath) {
|
|
61
|
-
|
|
101
|
+
lines.push(`import '${cssPath}'`)
|
|
62
102
|
}
|
|
63
103
|
|
|
64
|
-
//
|
|
65
|
-
if (
|
|
66
|
-
|
|
104
|
+
// Foundation capabilities import (for custom Layout, props, etc.)
|
|
105
|
+
if (foundationExports) {
|
|
106
|
+
lines.push(`import capabilities from '${foundationExports.path}'`)
|
|
67
107
|
}
|
|
68
108
|
|
|
69
109
|
// Component imports (use detected extension or default to .js)
|
|
70
110
|
for (const name of componentNames) {
|
|
71
111
|
const ext = componentExtensions[name] || 'js'
|
|
72
|
-
|
|
112
|
+
lines.push(`import ${name} from './components/${name}/index.${ext}'`)
|
|
73
113
|
}
|
|
74
114
|
|
|
75
|
-
|
|
76
|
-
const componentsObj = `const components = {\n ${componentNames.join(',\n ')},\n}`
|
|
77
|
-
|
|
78
|
-
// Runtime config (serialized)
|
|
79
|
-
const configStr = JSON.stringify(runtimeConfig, null, 2)
|
|
80
|
-
.split('\n')
|
|
81
|
-
.map((line, i) => (i === 0 ? line : ' ' + line))
|
|
82
|
-
.join('\n')
|
|
83
|
-
|
|
84
|
-
const runtimeConfigBlock = `
|
|
85
|
-
// Runtime configuration (extracted from meta files)
|
|
86
|
-
// Only includes properties needed at render time
|
|
87
|
-
const runtimeConfig = ${configStr}`
|
|
88
|
-
|
|
89
|
-
// Export functions
|
|
90
|
-
const exportFunctions = `
|
|
91
|
-
/**
|
|
92
|
-
* Get a component by name
|
|
93
|
-
*/
|
|
94
|
-
export function getComponent(name) {
|
|
95
|
-
return components[name]
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* List all available component names
|
|
100
|
-
*/
|
|
101
|
-
export function listComponents() {
|
|
102
|
-
return Object.keys(components)
|
|
103
|
-
}
|
|
115
|
+
lines.push('')
|
|
104
116
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
export
|
|
110
|
-
|
|
111
|
-
}
|
|
117
|
+
// Export components object
|
|
118
|
+
if (componentNames.length > 0) {
|
|
119
|
+
lines.push(`export const components = { ${componentNames.join(', ')} }`)
|
|
120
|
+
lines.push('')
|
|
121
|
+
lines.push(`export { ${componentNames.join(', ')} }`)
|
|
122
|
+
} else {
|
|
123
|
+
lines.push('export const components = {}')
|
|
124
|
+
}
|
|
112
125
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
export
|
|
117
|
-
|
|
118
|
-
|
|
126
|
+
// Foundation capabilities (Layout, props, etc.)
|
|
127
|
+
lines.push('')
|
|
128
|
+
if (foundationExports) {
|
|
129
|
+
lines.push('export { capabilities }')
|
|
130
|
+
} else {
|
|
131
|
+
lines.push('export const capabilities = null')
|
|
132
|
+
}
|
|
119
133
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
// Runtime meta (form schemas, etc.) - only if non-empty
|
|
135
|
+
lines.push('')
|
|
136
|
+
if (Object.keys(runtimeMeta).length > 0) {
|
|
137
|
+
const metaJson = JSON.stringify(runtimeMeta, null, 2)
|
|
138
|
+
.split('\n')
|
|
139
|
+
.map((line, i) => (i === 0 ? line : line))
|
|
140
|
+
.join('\n')
|
|
141
|
+
lines.push(`// Runtime metadata (form schemas, etc.)`)
|
|
142
|
+
lines.push(`export const meta = ${metaJson}`)
|
|
143
|
+
} else {
|
|
144
|
+
lines.push('export const meta = {}')
|
|
130
145
|
}
|
|
131
|
-
return schemas
|
|
132
|
-
}
|
|
133
146
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
export function getSchema(name) {
|
|
138
|
-
return components[name]?.schema
|
|
139
|
-
}`
|
|
140
|
-
|
|
141
|
-
// Named exports for direct imports
|
|
142
|
-
const namedExports = componentNames.length > 0
|
|
143
|
-
? `\n// Named exports for direct imports\nexport { ${componentNames.join(', ')} }`
|
|
144
|
-
: ''
|
|
145
|
-
|
|
146
|
-
// Runtime exports (Layout, props, etc.)
|
|
147
|
-
const runtimeExport = runtimeExports
|
|
148
|
-
? `\n// Runtime exports (Layout, props, etc.)\nexport { runtime }`
|
|
149
|
-
: `\n// No runtime exports provided\nexport const runtime = null`
|
|
150
|
-
|
|
151
|
-
return `// Auto-generated foundation entry point
|
|
152
|
-
// DO NOT EDIT - This file is regenerated during build
|
|
153
|
-
|
|
154
|
-
${imports.join('\n')}
|
|
155
|
-
|
|
156
|
-
${componentsObj}
|
|
157
|
-
${runtimeConfigBlock}
|
|
158
|
-
${exportFunctions}
|
|
159
|
-
${namedExports}
|
|
160
|
-
${runtimeExport}
|
|
161
|
-
`
|
|
147
|
+
lines.push('')
|
|
148
|
+
|
|
149
|
+
return lines.join('\n')
|
|
162
150
|
}
|
|
163
151
|
|
|
164
152
|
/**
|
|
@@ -178,7 +166,7 @@ function detectComponentExtension(srcDir, componentName) {
|
|
|
178
166
|
* Generate the foundation entry point file
|
|
179
167
|
*/
|
|
180
168
|
export async function generateEntryPoint(srcDir, outputPath = null) {
|
|
181
|
-
// Discover components
|
|
169
|
+
// Discover components (includes meta from meta.js files)
|
|
182
170
|
const components = await discoverComponents(srcDir)
|
|
183
171
|
const componentNames = Object.keys(components).sort()
|
|
184
172
|
|
|
@@ -192,32 +180,21 @@ export async function generateEntryPoint(srcDir, outputPath = null) {
|
|
|
192
180
|
componentExtensions[name] = detectComponentExtension(srcDir, name)
|
|
193
181
|
}
|
|
194
182
|
|
|
195
|
-
// Load foundation meta and build runtime config
|
|
196
|
-
const foundationMeta = await loadFoundationMeta(srcDir)
|
|
197
|
-
|
|
198
|
-
const runtimeConfig = {
|
|
199
|
-
foundation: extractRuntimeConfig(foundationMeta),
|
|
200
|
-
components: {},
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
for (const [name, meta] of Object.entries(components)) {
|
|
204
|
-
const config = extractRuntimeConfig(meta)
|
|
205
|
-
if (Object.keys(config).length > 0) {
|
|
206
|
-
runtimeConfig.components[name] = config
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
183
|
// Check for CSS file
|
|
211
184
|
const cssPath = detectCssFile(srcDir)
|
|
212
185
|
|
|
213
|
-
// Check for
|
|
214
|
-
const
|
|
186
|
+
// Check for foundation exports (custom Layout, props, etc.)
|
|
187
|
+
const foundationExports = detectFoundationExports(srcDir)
|
|
188
|
+
|
|
189
|
+
// Extract runtime-needed meta (form schemas, etc.)
|
|
190
|
+
const runtimeMeta = extractRuntimeMeta(components)
|
|
215
191
|
|
|
216
192
|
// Generate source
|
|
217
|
-
const source = generateEntrySource(componentNames,
|
|
193
|
+
const source = generateEntrySource(componentNames, {
|
|
218
194
|
cssPath,
|
|
219
195
|
componentExtensions,
|
|
220
|
-
|
|
196
|
+
foundationExports,
|
|
197
|
+
runtimeMeta,
|
|
221
198
|
})
|
|
222
199
|
|
|
223
200
|
// Write to file
|
|
@@ -227,15 +204,14 @@ export async function generateEntryPoint(srcDir, outputPath = null) {
|
|
|
227
204
|
|
|
228
205
|
console.log(`Generated entry point: ${output}`)
|
|
229
206
|
console.log(` - ${componentNames.length} components: ${componentNames.join(', ')}`)
|
|
230
|
-
if (
|
|
231
|
-
console.log(` -
|
|
207
|
+
if (foundationExports) {
|
|
208
|
+
console.log(` - Foundation exports found: ${foundationExports.path}`)
|
|
232
209
|
}
|
|
233
210
|
|
|
234
211
|
return {
|
|
235
212
|
outputPath: output,
|
|
236
213
|
componentNames,
|
|
237
|
-
|
|
238
|
-
runtimeExports,
|
|
214
|
+
foundationExports,
|
|
239
215
|
}
|
|
240
216
|
}
|
|
241
217
|
|
package/src/index.js
CHANGED
package/src/prerender.js
CHANGED
|
@@ -108,9 +108,9 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
108
108
|
const uniweb = createUniweb(siteContent)
|
|
109
109
|
uniweb.setFoundation(foundation)
|
|
110
110
|
|
|
111
|
-
//
|
|
112
|
-
if (foundation.runtime
|
|
113
|
-
uniweb.setFoundationConfig(foundation.runtime
|
|
111
|
+
// Set foundation config if provided
|
|
112
|
+
if (foundation.runtime) {
|
|
113
|
+
uniweb.setFoundationConfig(foundation.runtime)
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// Pre-render each page
|
|
@@ -185,11 +185,10 @@ function DefaultLayout({ header, body, footer }) {
|
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
187
|
* Layout component for SSG
|
|
188
|
-
* Supports foundation-provided custom Layout via
|
|
188
|
+
* Supports foundation-provided custom Layout via runtime.Layout
|
|
189
189
|
*/
|
|
190
190
|
function Layout({ page, website, foundation }) {
|
|
191
|
-
|
|
192
|
-
const RemoteLayout = foundation.runtime?.Layout || foundation.site?.Layout || null
|
|
191
|
+
const RemoteLayout = foundation.runtime?.Layout || null
|
|
193
192
|
|
|
194
193
|
// Get block groups from page
|
|
195
194
|
const headerBlocks = page.getHeaderBlocks()
|
package/src/schema.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schema Discovery and Loading Utilities
|
|
3
3
|
*
|
|
4
|
-
* Discovers component meta files
|
|
5
|
-
*
|
|
4
|
+
* Discovers component meta files and loads them for schema.json generation.
|
|
5
|
+
* Schema data is for editor-time only, not runtime.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { readdir } from 'node:fs/promises'
|
|
@@ -13,9 +13,6 @@ import { pathToFileURL } from 'node:url'
|
|
|
13
13
|
// Meta file name (standardized to meta.js)
|
|
14
14
|
const META_FILE_NAME = 'meta.js'
|
|
15
15
|
|
|
16
|
-
// Keys that should be extracted for runtime (embedded in foundation.js)
|
|
17
|
-
const RUNTIME_KEYS = ['input', 'props']
|
|
18
|
-
|
|
19
16
|
/**
|
|
20
17
|
* Load a meta.js file via dynamic import
|
|
21
18
|
*/
|
|
@@ -95,22 +92,6 @@ export async function discoverComponents(srcDir) {
|
|
|
95
92
|
return components
|
|
96
93
|
}
|
|
97
94
|
|
|
98
|
-
/**
|
|
99
|
-
* Extract runtime-relevant config from meta
|
|
100
|
-
* Only includes keys that are needed at render time
|
|
101
|
-
*/
|
|
102
|
-
export function extractRuntimeConfig(meta) {
|
|
103
|
-
if (!meta) return {}
|
|
104
|
-
|
|
105
|
-
const config = {}
|
|
106
|
-
for (const key of RUNTIME_KEYS) {
|
|
107
|
-
if (meta[key] !== undefined) {
|
|
108
|
-
config[key] = meta[key]
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return config
|
|
112
|
-
}
|
|
113
|
-
|
|
114
95
|
/**
|
|
115
96
|
* Build complete schema for a foundation
|
|
116
97
|
* Returns { _self: foundationMeta, ComponentName: componentMeta, ... }
|
|
@@ -125,27 +106,6 @@ export async function buildSchema(srcDir) {
|
|
|
125
106
|
}
|
|
126
107
|
}
|
|
127
108
|
|
|
128
|
-
/**
|
|
129
|
-
* Build runtime config (minimal, for embedding in foundation.js)
|
|
130
|
-
*/
|
|
131
|
-
export async function buildRuntimeConfig(srcDir) {
|
|
132
|
-
const foundationMeta = await loadFoundationMeta(srcDir)
|
|
133
|
-
const components = await discoverComponents(srcDir)
|
|
134
|
-
|
|
135
|
-
const componentConfigs = {}
|
|
136
|
-
for (const [name, meta] of Object.entries(components)) {
|
|
137
|
-
const config = extractRuntimeConfig(meta)
|
|
138
|
-
if (Object.keys(config).length > 0) {
|
|
139
|
-
componentConfigs[name] = config
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
foundation: extractRuntimeConfig(foundationMeta),
|
|
145
|
-
components: componentConfigs,
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
109
|
/**
|
|
150
110
|
* Get list of exposed component names
|
|
151
111
|
*/
|
|
@@ -79,9 +79,14 @@ function isMarkdownFile(filename) {
|
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Parse numeric prefix from filename (e.g., "1-hero.md" → { prefix: "1", name: "hero" })
|
|
82
|
+
* Supports:
|
|
83
|
+
* - Simple: "1", "2", "3"
|
|
84
|
+
* - Decimal ordering: "1.5" (between 1 and 2), "2.5" (between 2 and 3)
|
|
85
|
+
* - Hierarchy via comma: "1,1" (child of 1), "1,2" (second child of 1)
|
|
86
|
+
* - Mixed: "1.5,1" (child of section 1.5)
|
|
82
87
|
*/
|
|
83
88
|
function parseNumericPrefix(filename) {
|
|
84
|
-
const match = filename.match(/^(\d+(
|
|
89
|
+
const match = filename.match(/^(\d+(?:[.,]\d+)*)-?(.*)$/)
|
|
85
90
|
if (match) {
|
|
86
91
|
return { prefix: match[1], name: match[2] || match[1] }
|
|
87
92
|
}
|
|
@@ -89,7 +94,9 @@ function parseNumericPrefix(filename) {
|
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
/**
|
|
92
|
-
* Compare filenames for sorting by numeric prefix
|
|
97
|
+
* Compare filenames for sorting by numeric prefix.
|
|
98
|
+
* Both . and , are treated as separators for sorting purposes.
|
|
99
|
+
* This ensures correct ordering: 1, 1,1, 1.5, 2, 2,1, etc.
|
|
93
100
|
*/
|
|
94
101
|
function compareFilenames(a, b) {
|
|
95
102
|
const { prefix: prefixA } = parseNumericPrefix(parse(a).name)
|
|
@@ -99,8 +106,8 @@ function compareFilenames(a, b) {
|
|
|
99
106
|
if (!prefixA) return 1
|
|
100
107
|
if (!prefixB) return -1
|
|
101
108
|
|
|
102
|
-
const partsA = prefixA.split(
|
|
103
|
-
const partsB = prefixB.split(
|
|
109
|
+
const partsA = prefixA.split(/[.,]/).map(Number)
|
|
110
|
+
const partsB = prefixB.split(/[.,]/).map(Number)
|
|
104
111
|
|
|
105
112
|
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
106
113
|
const numA = partsA[i] ?? 0
|
|
@@ -155,7 +162,11 @@ async function processMarkdownFile(filePath, id, siteRoot) {
|
|
|
155
162
|
}
|
|
156
163
|
|
|
157
164
|
/**
|
|
158
|
-
* Build section hierarchy from flat list
|
|
165
|
+
* Build section hierarchy from flat list.
|
|
166
|
+
* Hierarchy is determined by comma separators:
|
|
167
|
+
* - "1", "1.5", "2" → all top-level (dots are for ordering)
|
|
168
|
+
* - "1,1", "1,2" → children of section "1"
|
|
169
|
+
* - "1.5,1" → child of section "1.5"
|
|
159
170
|
*/
|
|
160
171
|
function buildSectionHierarchy(sections) {
|
|
161
172
|
const sectionMap = new Map()
|
|
@@ -166,15 +177,15 @@ function buildSectionHierarchy(sections) {
|
|
|
166
177
|
sectionMap.set(section.id, section)
|
|
167
178
|
}
|
|
168
179
|
|
|
169
|
-
// Second pass: build hierarchy
|
|
180
|
+
// Second pass: build hierarchy (comma = hierarchy)
|
|
170
181
|
for (const section of sections) {
|
|
171
|
-
if (!section.id.includes('
|
|
182
|
+
if (!section.id.includes(',')) {
|
|
172
183
|
topLevel.push(section)
|
|
173
184
|
continue
|
|
174
185
|
}
|
|
175
186
|
|
|
176
|
-
const parts = section.id.split('
|
|
177
|
-
const parentId = parts.slice(0, -1).join('
|
|
187
|
+
const parts = section.id.split(',')
|
|
188
|
+
const parentId = parts.slice(0, -1).join(',')
|
|
178
189
|
const parent = sectionMap.get(parentId)
|
|
179
190
|
|
|
180
191
|
if (parent) {
|
|
@@ -127,9 +127,9 @@ export function foundationDevPlugin(options = {}) {
|
|
|
127
127
|
server.ws.send({ type: 'full-reload' })
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
// Also regenerate if
|
|
131
|
-
if (file.endsWith('/
|
|
132
|
-
console.log('
|
|
130
|
+
// Also regenerate if exports.js changes
|
|
131
|
+
if (file.endsWith('/exports.js') || file.endsWith('/exports.jsx')) {
|
|
132
|
+
console.log('Foundation exports changed, regenerating entry...')
|
|
133
133
|
const entryPath = join(resolvedSrcDir, entryFileName)
|
|
134
134
|
await generateEntryPoint(resolvedSrcDir, entryPath)
|
|
135
135
|
server.ws.send({ type: 'full-reload' })
|